agentic-flow 1.7.2 → 1.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/test-neural.md +0 -5
- package/.claude/answer.md +1 -0
- package/.claude/settings.json +19 -20
- package/CHANGELOG.md +0 -91
- package/README.md +17 -81
- package/dist/agentdb/benchmarks/comprehensive-benchmark.js +664 -0
- package/dist/agentdb/benchmarks/frontier-benchmark.js +419 -0
- package/dist/agentdb/benchmarks/reflexion-benchmark.js +370 -0
- package/dist/agentdb/cli/agentdb-cli.js +717 -0
- package/dist/agentdb/controllers/CausalMemoryGraph.js +322 -0
- package/dist/agentdb/controllers/CausalRecall.js +281 -0
- package/dist/agentdb/controllers/EmbeddingService.js +118 -0
- package/dist/agentdb/controllers/ExplainableRecall.js +387 -0
- package/dist/agentdb/controllers/NightlyLearner.js +382 -0
- package/dist/agentdb/controllers/ReflexionMemory.js +239 -0
- package/dist/agentdb/controllers/SkillLibrary.js +276 -0
- package/dist/agentdb/controllers/frontier-index.js +9 -0
- package/dist/agentdb/controllers/index.js +8 -0
- package/dist/agentdb/index.js +32 -0
- package/dist/agentdb/optimizations/BatchOperations.js +198 -0
- package/dist/agentdb/optimizations/QueryOptimizer.js +225 -0
- package/dist/agentdb/optimizations/index.js +7 -0
- package/dist/agentdb/tests/frontier-features.test.js +665 -0
- package/dist/cli/skills-manager.js +3 -1
- package/dist/cli-proxy.js +2 -33
- package/dist/mcp/standalone-stdio.js +200 -4
- package/dist/memory/SharedMemoryPool.js +211 -0
- package/dist/memory/index.js +6 -0
- package/dist/reasoningbank/AdvancedMemory.js +239 -0
- package/dist/reasoningbank/HybridBackend.js +305 -0
- package/dist/reasoningbank/index-new.js +87 -0
- package/dist/reasoningbank/index.js +23 -44
- package/dist/utils/cli.js +0 -22
- package/docs/AGENTDB_TESTING.md +411 -0
- package/docs/v1.7.1-QUICK-START.md +399 -0
- package/package.json +4 -4
- package/scripts/run-validation.sh +165 -0
- package/scripts/test-agentdb.sh +153 -0
- package/.claude/skills/agentdb-memory-patterns/SKILL.md +0 -166
- package/.claude/skills/agentdb-vector-search/SKILL.md +0 -126
- package/.claude/skills/agentic-flow/agentdb-memory-patterns/SKILL.md +0 -166
- package/.claude/skills/agentic-flow/agentdb-vector-search/SKILL.md +0 -126
- package/.claude/skills/agentic-flow/reasoningbank-intelligence/SKILL.md +0 -201
- package/.claude/skills/agentic-flow/swarm-orchestration/SKILL.md +0 -179
- package/.claude/skills/reasoningbank-intelligence/SKILL.md +0 -201
- package/.claude/skills/skill-builder/README.md +0 -308
- package/.claude/skills/skill-builder/SKILL.md +0 -910
- package/.claude/skills/skill-builder/docs/SPECIFICATION.md +0 -358
- package/.claude/skills/skill-builder/resources/schemas/skill-frontmatter.schema.json +0 -41
- package/.claude/skills/skill-builder/resources/templates/full-skill.template +0 -118
- package/.claude/skills/skill-builder/resources/templates/minimal-skill.template +0 -38
- package/.claude/skills/skill-builder/scripts/generate-skill.sh +0 -334
- package/.claude/skills/skill-builder/scripts/validate-skill.sh +0 -198
- package/.claude/skills/swarm-orchestration/SKILL.md +0 -179
- package/docs/AGENTDB_INTEGRATION.md +0 -379
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CausalMemoryGraph - Causal Reasoning over Agent Memories
|
|
3
|
+
*
|
|
4
|
+
* Implements intervention-based reasoning rather than correlation.
|
|
5
|
+
* Stores p(y|do(x)) estimates and tracks causal uplift across episodes.
|
|
6
|
+
*
|
|
7
|
+
* Based on:
|
|
8
|
+
* - Pearl's do-calculus and causal inference
|
|
9
|
+
* - Uplift modeling from A/B testing
|
|
10
|
+
* - Instrumental variable methods
|
|
11
|
+
*/
|
|
12
|
+
export class CausalMemoryGraph {
|
|
13
|
+
db;
|
|
14
|
+
constructor(db) {
|
|
15
|
+
this.db = db;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Add a causal edge between memories
|
|
19
|
+
*/
|
|
20
|
+
addCausalEdge(edge) {
|
|
21
|
+
const stmt = this.db.prepare(`
|
|
22
|
+
INSERT INTO causal_edges (
|
|
23
|
+
from_memory_id, from_memory_type, to_memory_id, to_memory_type,
|
|
24
|
+
similarity, uplift, confidence, sample_size,
|
|
25
|
+
evidence_ids, experiment_ids, confounder_score,
|
|
26
|
+
mechanism, metadata
|
|
27
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
28
|
+
`);
|
|
29
|
+
const result = stmt.run(edge.fromMemoryId, edge.fromMemoryType, edge.toMemoryId, edge.toMemoryType, edge.similarity, edge.uplift || null, edge.confidence, edge.sampleSize || null, edge.evidenceIds ? JSON.stringify(edge.evidenceIds) : null, edge.experimentIds ? JSON.stringify(edge.experimentIds) : null, edge.confounderScore || null, edge.mechanism || null, edge.metadata ? JSON.stringify(edge.metadata) : null);
|
|
30
|
+
return result.lastInsertRowid;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a causal experiment (A/B test)
|
|
34
|
+
*/
|
|
35
|
+
createExperiment(experiment) {
|
|
36
|
+
const stmt = this.db.prepare(`
|
|
37
|
+
INSERT INTO causal_experiments (
|
|
38
|
+
name, hypothesis, treatment_id, treatment_type, control_id,
|
|
39
|
+
start_time, sample_size, status, metadata
|
|
40
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
41
|
+
`);
|
|
42
|
+
const result = stmt.run(experiment.name, experiment.hypothesis, experiment.treatmentId, experiment.treatmentType, experiment.controlId || null, experiment.startTime, experiment.sampleSize, experiment.status, experiment.metadata ? JSON.stringify(experiment.metadata) : null);
|
|
43
|
+
return result.lastInsertRowid;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Record an observation in an experiment
|
|
47
|
+
*/
|
|
48
|
+
recordObservation(observation) {
|
|
49
|
+
const stmt = this.db.prepare(`
|
|
50
|
+
INSERT INTO causal_observations (
|
|
51
|
+
experiment_id, episode_id, is_treatment, outcome_value, outcome_type, context
|
|
52
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
53
|
+
`);
|
|
54
|
+
stmt.run(observation.experimentId, observation.episodeId, observation.isTreatment ? 1 : 0, observation.outcomeValue, observation.outcomeType, observation.context ? JSON.stringify(observation.context) : null);
|
|
55
|
+
// Update sample size
|
|
56
|
+
this.db.prepare(`
|
|
57
|
+
UPDATE causal_experiments
|
|
58
|
+
SET sample_size = sample_size + 1
|
|
59
|
+
WHERE id = ?
|
|
60
|
+
`).run(observation.experimentId);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Calculate uplift for an experiment
|
|
64
|
+
*/
|
|
65
|
+
calculateUplift(experimentId) {
|
|
66
|
+
// Get treatment and control observations
|
|
67
|
+
const observations = this.db.prepare(`
|
|
68
|
+
SELECT is_treatment, outcome_value
|
|
69
|
+
FROM causal_observations
|
|
70
|
+
WHERE experiment_id = ?
|
|
71
|
+
`).all(experimentId);
|
|
72
|
+
const treatmentValues = observations
|
|
73
|
+
.filter(o => o.is_treatment === 1)
|
|
74
|
+
.map(o => o.outcome_value);
|
|
75
|
+
const controlValues = observations
|
|
76
|
+
.filter(o => o.is_treatment === 0)
|
|
77
|
+
.map(o => o.outcome_value);
|
|
78
|
+
if (treatmentValues.length === 0 || controlValues.length === 0) {
|
|
79
|
+
return { uplift: 0, pValue: 1.0, confidenceInterval: [0, 0] };
|
|
80
|
+
}
|
|
81
|
+
// Calculate means
|
|
82
|
+
const treatmentMean = this.mean(treatmentValues);
|
|
83
|
+
const controlMean = this.mean(controlValues);
|
|
84
|
+
const uplift = treatmentMean - controlMean;
|
|
85
|
+
// Calculate standard errors
|
|
86
|
+
const treatmentSE = this.standardError(treatmentValues);
|
|
87
|
+
const controlSE = this.standardError(controlValues);
|
|
88
|
+
const pooledSE = Math.sqrt(treatmentSE ** 2 + controlSE ** 2);
|
|
89
|
+
// t-statistic and p-value (two-tailed)
|
|
90
|
+
const tStat = uplift / pooledSE;
|
|
91
|
+
const df = treatmentValues.length + controlValues.length - 2;
|
|
92
|
+
const pValue = 2 * (1 - this.tCDF(Math.abs(tStat), df));
|
|
93
|
+
// 95% confidence interval
|
|
94
|
+
const tCritical = this.tInverse(0.025, df);
|
|
95
|
+
const marginOfError = tCritical * pooledSE;
|
|
96
|
+
const confidenceInterval = [
|
|
97
|
+
uplift - marginOfError,
|
|
98
|
+
uplift + marginOfError
|
|
99
|
+
];
|
|
100
|
+
// Update experiment with results
|
|
101
|
+
this.db.prepare(`
|
|
102
|
+
UPDATE causal_experiments
|
|
103
|
+
SET treatment_mean = ?,
|
|
104
|
+
control_mean = ?,
|
|
105
|
+
uplift = ?,
|
|
106
|
+
p_value = ?,
|
|
107
|
+
confidence_interval_low = ?,
|
|
108
|
+
confidence_interval_high = ?,
|
|
109
|
+
status = 'completed'
|
|
110
|
+
WHERE id = ?
|
|
111
|
+
`).run(treatmentMean, controlMean, uplift, pValue, confidenceInterval[0], confidenceInterval[1], experimentId);
|
|
112
|
+
return { uplift, pValue, confidenceInterval };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Query causal effects
|
|
116
|
+
*/
|
|
117
|
+
queryCausalEffects(query) {
|
|
118
|
+
const { interventionMemoryId, interventionMemoryType, outcomeMemoryId, minConfidence = 0.5, minUplift = 0.0 } = query;
|
|
119
|
+
let sql = `
|
|
120
|
+
SELECT * FROM causal_edges
|
|
121
|
+
WHERE from_memory_id = ?
|
|
122
|
+
AND from_memory_type = ?
|
|
123
|
+
AND confidence >= ?
|
|
124
|
+
AND ABS(uplift) >= ?
|
|
125
|
+
`;
|
|
126
|
+
const params = [
|
|
127
|
+
interventionMemoryId,
|
|
128
|
+
interventionMemoryType,
|
|
129
|
+
minConfidence,
|
|
130
|
+
minUplift
|
|
131
|
+
];
|
|
132
|
+
if (outcomeMemoryId) {
|
|
133
|
+
sql += ' AND to_memory_id = ?';
|
|
134
|
+
params.push(outcomeMemoryId);
|
|
135
|
+
}
|
|
136
|
+
sql += ' ORDER BY ABS(uplift) * confidence DESC';
|
|
137
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
138
|
+
return rows.map(row => this.rowToCausalEdge(row));
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get causal chain (multi-hop reasoning)
|
|
142
|
+
*/
|
|
143
|
+
getCausalChain(fromMemoryId, toMemoryId, maxDepth = 5) {
|
|
144
|
+
// Use recursive CTE from view
|
|
145
|
+
const chains = this.db.prepare(`
|
|
146
|
+
WITH RECURSIVE chain(from_id, to_id, depth, path, total_uplift, min_confidence) AS (
|
|
147
|
+
SELECT
|
|
148
|
+
from_memory_id,
|
|
149
|
+
to_memory_id,
|
|
150
|
+
1,
|
|
151
|
+
from_memory_id || '->' || to_memory_id,
|
|
152
|
+
uplift,
|
|
153
|
+
confidence
|
|
154
|
+
FROM causal_edges
|
|
155
|
+
WHERE from_memory_id = ? AND confidence >= 0.5
|
|
156
|
+
|
|
157
|
+
UNION ALL
|
|
158
|
+
|
|
159
|
+
SELECT
|
|
160
|
+
chain.from_id,
|
|
161
|
+
ce.to_memory_id,
|
|
162
|
+
chain.depth + 1,
|
|
163
|
+
chain.path || '->' || ce.to_memory_id,
|
|
164
|
+
chain.total_uplift + ce.uplift,
|
|
165
|
+
MIN(chain.min_confidence, ce.confidence)
|
|
166
|
+
FROM chain
|
|
167
|
+
JOIN causal_edges ce ON chain.to_id = ce.from_memory_id
|
|
168
|
+
WHERE chain.depth < ?
|
|
169
|
+
AND ce.confidence >= 0.5
|
|
170
|
+
AND chain.path NOT LIKE '%' || ce.to_memory_id || '%'
|
|
171
|
+
)
|
|
172
|
+
SELECT path, total_uplift, min_confidence
|
|
173
|
+
FROM chain
|
|
174
|
+
WHERE to_id = ?
|
|
175
|
+
ORDER BY total_uplift DESC
|
|
176
|
+
LIMIT 10
|
|
177
|
+
`).all(fromMemoryId, maxDepth, toMemoryId);
|
|
178
|
+
return chains.map(row => ({
|
|
179
|
+
path: row.path.split('->').map(Number),
|
|
180
|
+
totalUplift: row.total_uplift,
|
|
181
|
+
confidence: row.min_confidence
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Calculate causal gain: E[outcome|do(treatment)] - E[outcome]
|
|
186
|
+
*/
|
|
187
|
+
calculateCausalGain(treatmentId, outcomeType) {
|
|
188
|
+
// Get episodes where treatment was applied
|
|
189
|
+
const withTreatment = this.db.prepare(`
|
|
190
|
+
SELECT AVG(CASE WHEN ? = 'reward' THEN reward
|
|
191
|
+
WHEN ? = 'success' THEN success
|
|
192
|
+
WHEN ? = 'latency' THEN latency_ms
|
|
193
|
+
END) as avg_outcome
|
|
194
|
+
FROM episodes
|
|
195
|
+
WHERE id IN (
|
|
196
|
+
SELECT to_memory_id FROM causal_edges
|
|
197
|
+
WHERE from_memory_id = ? AND confidence >= 0.6
|
|
198
|
+
)
|
|
199
|
+
`).get(outcomeType, outcomeType, outcomeType, treatmentId);
|
|
200
|
+
// Get baseline (no treatment)
|
|
201
|
+
const baseline = this.db.prepare(`
|
|
202
|
+
SELECT AVG(CASE WHEN ? = 'reward' THEN reward
|
|
203
|
+
WHEN ? = 'success' THEN success
|
|
204
|
+
WHEN ? = 'latency' THEN latency_ms
|
|
205
|
+
END) as avg_outcome
|
|
206
|
+
FROM episodes
|
|
207
|
+
WHERE id NOT IN (
|
|
208
|
+
SELECT to_memory_id FROM causal_edges
|
|
209
|
+
WHERE from_memory_id = ?
|
|
210
|
+
)
|
|
211
|
+
`).get(outcomeType, outcomeType, outcomeType, treatmentId);
|
|
212
|
+
const causalGain = (withTreatment?.avg_outcome || 0) - (baseline?.avg_outcome || 0);
|
|
213
|
+
// Get most confident edge for mechanism
|
|
214
|
+
const edge = this.db.prepare(`
|
|
215
|
+
SELECT mechanism, confidence
|
|
216
|
+
FROM causal_edges
|
|
217
|
+
WHERE from_memory_id = ?
|
|
218
|
+
ORDER BY confidence DESC
|
|
219
|
+
LIMIT 1
|
|
220
|
+
`).get(treatmentId);
|
|
221
|
+
return {
|
|
222
|
+
causalGain,
|
|
223
|
+
confidence: edge?.confidence || 0,
|
|
224
|
+
mechanism: edge?.mechanism || 'unknown'
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Detect confounders using correlation analysis
|
|
229
|
+
*/
|
|
230
|
+
detectConfounders(edgeId) {
|
|
231
|
+
const edge = this.db.prepare('SELECT * FROM causal_edges WHERE id = ?').get(edgeId);
|
|
232
|
+
if (!edge) {
|
|
233
|
+
return { confounders: [] };
|
|
234
|
+
}
|
|
235
|
+
// Find memories correlated with both treatment and outcome
|
|
236
|
+
// This is a simplified version - production would use proper statistical tests
|
|
237
|
+
const potentialConfounders = this.db.prepare(`
|
|
238
|
+
SELECT DISTINCT e.id, e.task
|
|
239
|
+
FROM episodes e
|
|
240
|
+
WHERE e.id != ? AND e.id != ?
|
|
241
|
+
AND e.session_id IN (
|
|
242
|
+
SELECT session_id FROM episodes WHERE id = ?
|
|
243
|
+
UNION
|
|
244
|
+
SELECT session_id FROM episodes WHERE id = ?
|
|
245
|
+
)
|
|
246
|
+
`).all(edge.from_memory_id, edge.to_memory_id, edge.from_memory_id, edge.to_memory_id);
|
|
247
|
+
const confounders = potentialConfounders.map((conf) => {
|
|
248
|
+
// Calculate correlation scores (simplified)
|
|
249
|
+
const treatmentCorr = this.calculateCorrelation(conf.id, edge.from_memory_id);
|
|
250
|
+
const outcomeCorr = this.calculateCorrelation(conf.id, edge.to_memory_id);
|
|
251
|
+
const confounderScore = Math.sqrt(treatmentCorr ** 2 * outcomeCorr ** 2);
|
|
252
|
+
return {
|
|
253
|
+
memoryId: conf.id,
|
|
254
|
+
correlationWithTreatment: treatmentCorr,
|
|
255
|
+
correlationWithOutcome: outcomeCorr,
|
|
256
|
+
confounderScore
|
|
257
|
+
};
|
|
258
|
+
}).filter(c => c.confounderScore > 0.3);
|
|
259
|
+
// Update edge with confounder score
|
|
260
|
+
if (confounders.length > 0) {
|
|
261
|
+
const maxConfounderScore = Math.max(...confounders.map(c => c.confounderScore));
|
|
262
|
+
this.db.prepare(`
|
|
263
|
+
UPDATE causal_edges
|
|
264
|
+
SET confounder_score = ?
|
|
265
|
+
WHERE id = ?
|
|
266
|
+
`).run(maxConfounderScore, edgeId);
|
|
267
|
+
}
|
|
268
|
+
return { confounders };
|
|
269
|
+
}
|
|
270
|
+
// ========================================================================
|
|
271
|
+
// Private Helper Methods
|
|
272
|
+
// ========================================================================
|
|
273
|
+
rowToCausalEdge(row) {
|
|
274
|
+
return {
|
|
275
|
+
id: row.id,
|
|
276
|
+
fromMemoryId: row.from_memory_id,
|
|
277
|
+
fromMemoryType: row.from_memory_type,
|
|
278
|
+
toMemoryId: row.to_memory_id,
|
|
279
|
+
toMemoryType: row.to_memory_type,
|
|
280
|
+
similarity: row.similarity,
|
|
281
|
+
uplift: row.uplift,
|
|
282
|
+
confidence: row.confidence,
|
|
283
|
+
sampleSize: row.sample_size,
|
|
284
|
+
evidenceIds: row.evidence_ids ? JSON.parse(row.evidence_ids) : undefined,
|
|
285
|
+
experimentIds: row.experiment_ids ? JSON.parse(row.experiment_ids) : undefined,
|
|
286
|
+
confounderScore: row.confounder_score,
|
|
287
|
+
mechanism: row.mechanism,
|
|
288
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
mean(values) {
|
|
292
|
+
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
293
|
+
}
|
|
294
|
+
variance(values) {
|
|
295
|
+
const avg = this.mean(values);
|
|
296
|
+
return values.reduce((sum, val) => sum + (val - avg) ** 2, 0) / values.length;
|
|
297
|
+
}
|
|
298
|
+
standardError(values) {
|
|
299
|
+
return Math.sqrt(this.variance(values) / values.length);
|
|
300
|
+
}
|
|
301
|
+
tCDF(t, df) {
|
|
302
|
+
// Simplified t-distribution CDF (use proper stats library in production)
|
|
303
|
+
// This is an approximation
|
|
304
|
+
return 0.5 + 0.5 * Math.sign(t) * (1 - Math.pow(1 + t * t / df, -df / 2));
|
|
305
|
+
}
|
|
306
|
+
tInverse(p, df) {
|
|
307
|
+
// Simplified inverse t-distribution (use proper stats library)
|
|
308
|
+
// Approximation for 95% CI
|
|
309
|
+
return 1.96; // Standard normal approximation
|
|
310
|
+
}
|
|
311
|
+
calculateCorrelation(id1, id2) {
|
|
312
|
+
// Simplified correlation calculation
|
|
313
|
+
// In production, use proper correlation metrics
|
|
314
|
+
const sharedSessions = this.db.prepare(`
|
|
315
|
+
SELECT COUNT(DISTINCT e1.session_id) as shared
|
|
316
|
+
FROM episodes e1
|
|
317
|
+
JOIN episodes e2 ON e1.session_id = e2.session_id
|
|
318
|
+
WHERE e1.id = ? AND e2.id = ?
|
|
319
|
+
`).get(id1, id2);
|
|
320
|
+
return Math.min(sharedSessions?.shared || 0, 1.0);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CausalRecall - Utility-Based Reranking + Certificate Issuer
|
|
3
|
+
*
|
|
4
|
+
* Combines:
|
|
5
|
+
* 1. Vector similarity search
|
|
6
|
+
* 2. Causal uplift from CausalMemoryGraph
|
|
7
|
+
* 3. Utility-based reranking: U = α*similarity + β*uplift − γ*latencyCost
|
|
8
|
+
* 4. Automatic certificate issuance via ExplainableRecall
|
|
9
|
+
*
|
|
10
|
+
* This is the main entry point for production retrieval with:
|
|
11
|
+
* - Causal-aware ranking
|
|
12
|
+
* - Explainable provenance
|
|
13
|
+
* - Policy compliance
|
|
14
|
+
*/
|
|
15
|
+
import { CausalMemoryGraph } from './CausalMemoryGraph.js';
|
|
16
|
+
import { ExplainableRecall } from './ExplainableRecall.js';
|
|
17
|
+
export class CausalRecall {
|
|
18
|
+
config;
|
|
19
|
+
db;
|
|
20
|
+
causalGraph;
|
|
21
|
+
explainableRecall;
|
|
22
|
+
embedder;
|
|
23
|
+
constructor(db, embedder, config = {
|
|
24
|
+
alpha: 0.7,
|
|
25
|
+
beta: 0.2,
|
|
26
|
+
gamma: 0.1,
|
|
27
|
+
minConfidence: 0.6
|
|
28
|
+
}) {
|
|
29
|
+
this.config = config;
|
|
30
|
+
this.db = db;
|
|
31
|
+
this.embedder = embedder;
|
|
32
|
+
this.causalGraph = new CausalMemoryGraph(db);
|
|
33
|
+
this.explainableRecall = new ExplainableRecall(db);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Main recall function with utility-based reranking and certificate issuance
|
|
37
|
+
*
|
|
38
|
+
* @param queryId Unique query identifier
|
|
39
|
+
* @param queryText Natural language query
|
|
40
|
+
* @param k Number of results to return (default: 12)
|
|
41
|
+
* @param requirements Optional list of requirements for completeness checking
|
|
42
|
+
* @param accessLevel Security access level for certificate
|
|
43
|
+
* @returns Reranked results with certificate
|
|
44
|
+
*/
|
|
45
|
+
async recall(queryId, queryText, k = 12, requirements, accessLevel = 'internal') {
|
|
46
|
+
const startTime = Date.now();
|
|
47
|
+
const metrics = {
|
|
48
|
+
vectorSearchMs: 0,
|
|
49
|
+
causalLookupMs: 0,
|
|
50
|
+
rerankMs: 0,
|
|
51
|
+
certificateMs: 0
|
|
52
|
+
};
|
|
53
|
+
// Step 1: Vector similarity search
|
|
54
|
+
const vectorStart = Date.now();
|
|
55
|
+
const queryEmbedding = await this.embedder.embed(queryText);
|
|
56
|
+
const candidates = await this.vectorSearch(queryEmbedding, k * 2); // Fetch 2k for reranking
|
|
57
|
+
metrics.vectorSearchMs = Date.now() - vectorStart;
|
|
58
|
+
// Step 2: Load causal edges for candidates
|
|
59
|
+
const causalStart = Date.now();
|
|
60
|
+
const causalEdges = await this.loadCausalEdges(candidates.map(c => c.id));
|
|
61
|
+
metrics.causalLookupMs = Date.now() - causalStart;
|
|
62
|
+
// Step 3: Rerank by utility
|
|
63
|
+
const rerankStart = Date.now();
|
|
64
|
+
const reranked = this.rerankByUtility(candidates, causalEdges);
|
|
65
|
+
const topK = reranked.slice(0, k);
|
|
66
|
+
metrics.rerankMs = Date.now() - rerankStart;
|
|
67
|
+
// Step 4: Issue certificate
|
|
68
|
+
const certStart = Date.now();
|
|
69
|
+
const certificate = this.issueCertificate({
|
|
70
|
+
queryId,
|
|
71
|
+
queryText,
|
|
72
|
+
candidates: topK,
|
|
73
|
+
requirements: requirements || this.extractRequirements(queryText),
|
|
74
|
+
accessLevel
|
|
75
|
+
});
|
|
76
|
+
metrics.certificateMs = Date.now() - certStart;
|
|
77
|
+
const totalLatencyMs = Date.now() - startTime;
|
|
78
|
+
return {
|
|
79
|
+
candidates: topK,
|
|
80
|
+
certificate,
|
|
81
|
+
queryId,
|
|
82
|
+
totalLatencyMs,
|
|
83
|
+
metrics
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Vector similarity search using cosine similarity
|
|
88
|
+
*/
|
|
89
|
+
async vectorSearch(queryEmbedding, k) {
|
|
90
|
+
const results = [];
|
|
91
|
+
// Search episode embeddings
|
|
92
|
+
const episodes = this.db.prepare(`
|
|
93
|
+
SELECT
|
|
94
|
+
e.id,
|
|
95
|
+
'episode' as type,
|
|
96
|
+
e.task || ' ' || COALESCE(e.output, '') as content,
|
|
97
|
+
ee.embedding,
|
|
98
|
+
e.latency_ms
|
|
99
|
+
FROM episodes e
|
|
100
|
+
JOIN episode_embeddings ee ON e.id = ee.episode_id
|
|
101
|
+
ORDER BY e.ts DESC
|
|
102
|
+
LIMIT ?
|
|
103
|
+
`).all(k * 2);
|
|
104
|
+
for (const ep of episodes) {
|
|
105
|
+
const episodeRow = ep;
|
|
106
|
+
const embedding = new Float32Array(JSON.parse(episodeRow.embedding));
|
|
107
|
+
const similarity = this.cosineSimilarity(queryEmbedding, embedding);
|
|
108
|
+
results.push({
|
|
109
|
+
id: episodeRow.id.toString(),
|
|
110
|
+
type: episodeRow.type,
|
|
111
|
+
content: episodeRow.content,
|
|
112
|
+
similarity,
|
|
113
|
+
latencyMs: episodeRow.latency_ms || 0
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// Sort by similarity and return top k
|
|
117
|
+
return results
|
|
118
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
119
|
+
.slice(0, k);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Load causal edges for candidates
|
|
123
|
+
*/
|
|
124
|
+
async loadCausalEdges(candidateIds) {
|
|
125
|
+
const edgeMap = new Map();
|
|
126
|
+
if (candidateIds.length === 0) {
|
|
127
|
+
return edgeMap;
|
|
128
|
+
}
|
|
129
|
+
const placeholders = candidateIds.map(() => '?').join(',');
|
|
130
|
+
const edges = this.db.prepare(`
|
|
131
|
+
SELECT * FROM causal_edges
|
|
132
|
+
WHERE from_memory_id IN (${placeholders})
|
|
133
|
+
AND confidence >= ?
|
|
134
|
+
`).all(...candidateIds.map(id => parseInt(id)), this.config.minConfidence || 0.6);
|
|
135
|
+
for (const edge of edges) {
|
|
136
|
+
const fromId = edge.from_memory_id.toString();
|
|
137
|
+
if (!edgeMap.has(fromId)) {
|
|
138
|
+
edgeMap.set(fromId, []);
|
|
139
|
+
}
|
|
140
|
+
edgeMap.get(fromId).push({
|
|
141
|
+
id: edge.id,
|
|
142
|
+
fromMemoryId: edge.from_memory_id,
|
|
143
|
+
fromMemoryType: edge.from_memory_type,
|
|
144
|
+
toMemoryId: edge.to_memory_id,
|
|
145
|
+
toMemoryType: edge.to_memory_type,
|
|
146
|
+
similarity: edge.similarity,
|
|
147
|
+
uplift: edge.uplift,
|
|
148
|
+
confidence: edge.confidence,
|
|
149
|
+
sampleSize: edge.sample_size,
|
|
150
|
+
evidenceIds: edge.evidence_ids ? JSON.parse(edge.evidence_ids) : undefined,
|
|
151
|
+
mechanism: edge.mechanism
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return edgeMap;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Rerank by utility: U = α*similarity + β*uplift − γ*latencyCost
|
|
158
|
+
*/
|
|
159
|
+
rerankByUtility(candidates, causalEdges) {
|
|
160
|
+
const { alpha, beta, gamma } = this.config;
|
|
161
|
+
const reranked = candidates.map(candidate => {
|
|
162
|
+
// Get causal uplift (average if multiple edges)
|
|
163
|
+
const edges = causalEdges.get(candidate.id) || [];
|
|
164
|
+
const avgUplift = edges.length > 0
|
|
165
|
+
? edges.reduce((sum, e) => sum + (e.uplift || 0), 0) / edges.length
|
|
166
|
+
: 0;
|
|
167
|
+
const avgConfidence = edges.length > 0
|
|
168
|
+
? edges.reduce((sum, e) => sum + e.confidence, 0) / edges.length
|
|
169
|
+
: 0;
|
|
170
|
+
// Normalize latency (assume max 1000ms)
|
|
171
|
+
const latencyCost = Math.min(candidate.latencyMs / 1000, 1.0);
|
|
172
|
+
// Calculate utility
|
|
173
|
+
const utilityScore = alpha * candidate.similarity + beta * avgUplift - gamma * latencyCost;
|
|
174
|
+
return {
|
|
175
|
+
id: candidate.id,
|
|
176
|
+
type: candidate.type,
|
|
177
|
+
content: candidate.content,
|
|
178
|
+
similarity: candidate.similarity,
|
|
179
|
+
uplift: avgUplift,
|
|
180
|
+
causalConfidence: avgConfidence,
|
|
181
|
+
latencyMs: candidate.latencyMs,
|
|
182
|
+
utilityScore,
|
|
183
|
+
rank: 0 // Will be set after sorting
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
// Sort by utility score descending
|
|
187
|
+
reranked.sort((a, b) => b.utilityScore - a.utilityScore);
|
|
188
|
+
// Assign ranks
|
|
189
|
+
reranked.forEach((candidate, idx) => {
|
|
190
|
+
candidate.rank = idx + 1;
|
|
191
|
+
});
|
|
192
|
+
return reranked;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Issue certificate for the retrieval
|
|
196
|
+
*/
|
|
197
|
+
issueCertificate(params) {
|
|
198
|
+
const { queryId, queryText, candidates, requirements, accessLevel } = params;
|
|
199
|
+
const chunks = candidates.map(c => ({
|
|
200
|
+
id: c.id,
|
|
201
|
+
type: c.type,
|
|
202
|
+
content: c.content,
|
|
203
|
+
relevance: c.similarity
|
|
204
|
+
}));
|
|
205
|
+
return this.explainableRecall.createCertificate({
|
|
206
|
+
queryId,
|
|
207
|
+
queryText,
|
|
208
|
+
chunks,
|
|
209
|
+
requirements,
|
|
210
|
+
accessLevel
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Extract requirements from query text (simple keyword extraction)
|
|
215
|
+
*/
|
|
216
|
+
extractRequirements(queryText) {
|
|
217
|
+
// Simple extraction: split on common words and filter
|
|
218
|
+
const stopWords = new Set(['a', 'an', 'the', 'is', 'are', 'was', 'were', 'to', 'from', 'for', 'with', 'how', 'what', 'where', 'when', 'why', 'who']);
|
|
219
|
+
const words = queryText
|
|
220
|
+
.toLowerCase()
|
|
221
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
222
|
+
.split(/\s+/)
|
|
223
|
+
.filter(w => w.length > 3 && !stopWords.has(w));
|
|
224
|
+
// Return unique words
|
|
225
|
+
return [...new Set(words)];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Cosine similarity between two vectors
|
|
229
|
+
*/
|
|
230
|
+
cosineSimilarity(a, b) {
|
|
231
|
+
if (a.length !== b.length) {
|
|
232
|
+
throw new Error('Vector dimensions must match');
|
|
233
|
+
}
|
|
234
|
+
let dotProduct = 0;
|
|
235
|
+
let magnitudeA = 0;
|
|
236
|
+
let magnitudeB = 0;
|
|
237
|
+
for (let i = 0; i < a.length; i++) {
|
|
238
|
+
dotProduct += a[i] * b[i];
|
|
239
|
+
magnitudeA += a[i] * a[i];
|
|
240
|
+
magnitudeB += b[i] * b[i];
|
|
241
|
+
}
|
|
242
|
+
const magnitude = Math.sqrt(magnitudeA) * Math.sqrt(magnitudeB);
|
|
243
|
+
return magnitude === 0 ? 0 : dotProduct / magnitude;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Batch recall for multiple queries
|
|
247
|
+
*/
|
|
248
|
+
async batchRecall(queries, requirements, accessLevel = 'internal') {
|
|
249
|
+
const results = [];
|
|
250
|
+
for (const query of queries) {
|
|
251
|
+
const result = await this.recall(query.queryId, query.queryText, query.k || 12, requirements, accessLevel);
|
|
252
|
+
results.push(result);
|
|
253
|
+
}
|
|
254
|
+
return results;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get recall statistics
|
|
258
|
+
*/
|
|
259
|
+
getStats() {
|
|
260
|
+
const causalEdges = this.db.prepare('SELECT COUNT(*) as count FROM causal_edges').get();
|
|
261
|
+
const certificates = this.db.prepare('SELECT COUNT(*) as count FROM recall_certificates').get();
|
|
262
|
+
const avgStats = this.db.prepare(`
|
|
263
|
+
SELECT
|
|
264
|
+
AVG(redundancy_ratio) as avg_redundancy,
|
|
265
|
+
AVG(completeness_score) as avg_completeness
|
|
266
|
+
FROM recall_certificates
|
|
267
|
+
`).get();
|
|
268
|
+
return {
|
|
269
|
+
totalCausalEdges: causalEdges.count,
|
|
270
|
+
totalCertificates: certificates.count,
|
|
271
|
+
avgRedundancyRatio: avgStats?.avg_redundancy || 0,
|
|
272
|
+
avgCompletenessScore: avgStats?.avg_completeness || 0
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Update rerank configuration
|
|
277
|
+
*/
|
|
278
|
+
updateConfig(config) {
|
|
279
|
+
this.config = { ...this.config, ...config };
|
|
280
|
+
}
|
|
281
|
+
}
|