agentdb 1.2.0 → 1.3.1

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.
Files changed (66) hide show
  1. package/README.md +180 -33
  2. package/dist/cli/agentdb-cli.d.ts +1 -0
  3. package/dist/cli/agentdb-cli.d.ts.map +1 -1
  4. package/dist/cli/agentdb-cli.js +108 -134
  5. package/dist/cli/agentdb-cli.js.map +1 -1
  6. package/dist/controllers/CausalMemoryGraph.d.ts.map +1 -1
  7. package/dist/controllers/CausalMemoryGraph.js +3 -3
  8. package/dist/controllers/CausalMemoryGraph.js.map +1 -1
  9. package/dist/controllers/CausalRecall.d.ts +25 -0
  10. package/dist/controllers/CausalRecall.d.ts.map +1 -1
  11. package/dist/controllers/CausalRecall.js +44 -1
  12. package/dist/controllers/CausalRecall.js.map +1 -1
  13. package/dist/controllers/EmbeddingService.d.ts.map +1 -1
  14. package/dist/controllers/EmbeddingService.js +4 -0
  15. package/dist/controllers/EmbeddingService.js.map +1 -1
  16. package/dist/controllers/ExplainableRecall.js +1 -1
  17. package/dist/controllers/LearningSystem.d.ts +194 -0
  18. package/dist/controllers/LearningSystem.d.ts.map +1 -0
  19. package/dist/controllers/LearningSystem.js +929 -0
  20. package/dist/controllers/LearningSystem.js.map +1 -0
  21. package/dist/controllers/NightlyLearner.d.ts.map +1 -1
  22. package/dist/controllers/NightlyLearner.js +9 -1
  23. package/dist/controllers/NightlyLearner.js.map +1 -1
  24. package/dist/controllers/ReasoningBank.d.ts +96 -0
  25. package/dist/controllers/ReasoningBank.d.ts.map +1 -0
  26. package/dist/controllers/ReasoningBank.js +302 -0
  27. package/dist/controllers/ReasoningBank.js.map +1 -0
  28. package/dist/controllers/ReflexionMemory.d.ts.map +1 -1
  29. package/dist/controllers/ReflexionMemory.js +4 -0
  30. package/dist/controllers/ReflexionMemory.js.map +1 -1
  31. package/dist/controllers/SkillLibrary.d.ts +37 -3
  32. package/dist/controllers/SkillLibrary.d.ts.map +1 -1
  33. package/dist/controllers/SkillLibrary.js +196 -15
  34. package/dist/controllers/SkillLibrary.js.map +1 -1
  35. package/dist/mcp/agentdb-mcp-server.d.ts +8 -0
  36. package/dist/mcp/agentdb-mcp-server.d.ts.map +1 -0
  37. package/dist/mcp/agentdb-mcp-server.js +1485 -352
  38. package/dist/mcp/agentdb-mcp-server.js.map +1 -0
  39. package/dist/mcp/learning-tools-handlers.d.ts +16 -0
  40. package/dist/mcp/learning-tools-handlers.d.ts.map +1 -0
  41. package/dist/mcp/learning-tools-handlers.js +105 -0
  42. package/dist/mcp/learning-tools-handlers.js.map +1 -0
  43. package/dist/optimizations/QueryOptimizer.d.ts.map +1 -1
  44. package/dist/optimizations/QueryOptimizer.js +3 -1
  45. package/dist/optimizations/QueryOptimizer.js.map +1 -1
  46. package/package.json +1 -1
  47. package/src/cli/agentdb-cli.ts +136 -51
  48. package/src/controllers/CausalMemoryGraph.ts +2 -3
  49. package/src/controllers/CausalRecall.ts +73 -1
  50. package/src/controllers/EmbeddingService.ts +6 -1
  51. package/src/controllers/ExplainableRecall.ts +1 -1
  52. package/src/controllers/LearningSystem.ts +1286 -0
  53. package/src/controllers/NightlyLearner.ts +11 -1
  54. package/src/controllers/ReasoningBank.ts +411 -0
  55. package/src/controllers/ReflexionMemory.ts +4 -0
  56. package/src/controllers/SkillLibrary.ts +254 -16
  57. package/src/mcp/agentdb-mcp-server.ts +1710 -0
  58. package/src/mcp/learning-tools-handlers.ts +106 -0
  59. package/src/optimizations/QueryOptimizer.ts +4 -2
  60. package/dist/benchmarks/comprehensive-benchmark.js +0 -664
  61. package/dist/benchmarks/frontier-benchmark.js +0 -419
  62. package/dist/benchmarks/reflexion-benchmark.js +0 -370
  63. package/dist/cli/agentdb-cli.js.backup +0 -718
  64. package/dist/schemas/frontier-schema.sql +0 -341
  65. package/dist/schemas/schema.sql +0 -382
  66. package/dist/tests/frontier-features.test.js +0 -665
@@ -14,7 +14,7 @@ import { CausalMemoryGraph } from '../controllers/CausalMemoryGraph.js';
14
14
  import { CausalRecall } from '../controllers/CausalRecall.js';
15
15
  import { ExplainableRecall } from '../controllers/ExplainableRecall.js';
16
16
  import { NightlyLearner } from '../controllers/NightlyLearner.js';
17
- import { ReflexionMemory, Episode, ReflexionQuery, ReflexionCritiqueSummary, ReflexionPruneConfig } from '../controllers/ReflexionMemory.js';
17
+ import { ReflexionMemory, Episode, ReflexionQuery } from '../controllers/ReflexionMemory.js';
18
18
  import { SkillLibrary, Skill, SkillQuery } from '../controllers/SkillLibrary.js';
19
19
  import { EmbeddingService } from '../controllers/EmbeddingService.js';
20
20
  import * as fs from 'fs';
@@ -76,8 +76,13 @@ class AgentDBCLI {
76
76
  // Initialize controllers
77
77
  this.causalGraph = new CausalMemoryGraph(this.db);
78
78
  this.explainableRecall = new ExplainableRecall(this.db);
79
- this.causalRecall = new CausalRecall(this.db, this.embedder, this.causalGraph, this.explainableRecall);
80
- this.nightlyLearner = new NightlyLearner(this.db, this.embedder, this.causalGraph);
79
+ this.causalRecall = new CausalRecall(this.db, this.embedder, {
80
+ alpha: 0.7,
81
+ beta: 0.2,
82
+ gamma: 0.1,
83
+ minConfidence: 0.6
84
+ });
85
+ this.nightlyLearner = new NightlyLearner(this.db, this.embedder);
81
86
  this.reflexion = new ReflexionMemory(this.db, this.embedder);
82
87
  this.skills = new SkillLibrary(this.db, this.embedder);
83
88
  }
@@ -100,12 +105,16 @@ class AgentDBCLI {
100
105
  log.info(`Effect: ${params.effect}`);
101
106
  log.info(`Uplift: ${params.uplift}`);
102
107
 
103
- const edgeId = this.causalGraph.addEdge({
104
- cause: params.cause,
105
- effect: params.effect,
108
+ const edgeId = this.causalGraph.addCausalEdge({
109
+ fromMemoryId: 1,
110
+ fromMemoryType: 'episode',
111
+ toMemoryId: 2,
112
+ toMemoryType: 'episode',
113
+ similarity: 0.9,
106
114
  uplift: params.uplift,
107
115
  confidence: params.confidence || 0.95,
108
116
  sampleSize: params.sampleSize || 0,
117
+ mechanism: `${params.cause} → ${params.effect}`,
109
118
  evidenceIds: []
110
119
  });
111
120
 
@@ -126,8 +135,14 @@ class AgentDBCLI {
126
135
 
127
136
  const expId = this.causalGraph.createExperiment({
128
137
  name: params.name,
129
- cause: params.cause,
130
- effect: params.effect
138
+ hypothesis: `Does ${params.cause} causally affect ${params.effect}?`,
139
+ treatmentId: 0,
140
+ treatmentType: params.cause,
141
+ controlId: undefined,
142
+ startTime: Math.floor(Date.now() / 1000),
143
+ sampleSize: 0,
144
+ status: 'running',
145
+ metadata: { effect: params.effect }
131
146
  });
132
147
 
133
148
  log.success(`Created experiment #${expId}`);
@@ -142,11 +157,20 @@ class AgentDBCLI {
142
157
  }): Promise<void> {
143
158
  if (!this.causalGraph) throw new Error('Not initialized');
144
159
 
160
+ // Create a dummy episode to get an episode ID
161
+ const insertResult = this.db!.prepare('INSERT INTO episodes (session_id, task, reward, success, created_at) VALUES (?, ?, ?, ?, ?)').run('cli-session', 'experiment', params.outcome, 1, Math.floor(Date.now() / 1000));
162
+ if (!insertResult || !insertResult.lastInsertRowid) {
163
+ throw new Error('Failed to create episode');
164
+ }
165
+ const episodeId = Number(insertResult.lastInsertRowid);
166
+
145
167
  this.causalGraph.recordObservation({
146
168
  experimentId: params.experimentId,
169
+ episodeId: episodeId,
147
170
  isTreatment: params.isTreatment,
148
- outcome: params.outcome,
149
- context: params.context || '{}'
171
+ outcomeValue: params.outcome,
172
+ outcomeType: 'reward',
173
+ context: params.context ? JSON.parse(params.context) : undefined
150
174
  });
151
175
 
152
176
  log.success(`Recorded ${params.isTreatment ? 'treatment' : 'control'} observation: ${params.outcome}`);
@@ -159,14 +183,31 @@ class AgentDBCLI {
159
183
 
160
184
  const result = this.causalGraph.calculateUplift(experimentId);
161
185
 
162
- log.info(`Treatment Mean: ${result.treatmentMean.toFixed(3)}`);
163
- log.info(`Control Mean: ${result.controlMean.toFixed(3)}`);
164
- log.success(`Uplift: ${result.uplift.toFixed(3)}`);
165
- log.info(`95% CI: [${result.confidenceLower.toFixed(3)}, ${result.confidenceUpper.toFixed(3)}]`);
166
- log.info(`p-value: ${result.pValue.toFixed(4)}`);
167
- log.info(`Sample Sizes: ${result.treatmentN} treatment, ${result.controlN} control`);
186
+ // Fetch experiment details (now includes calculated means)
187
+ const experiment = this.db!.prepare('SELECT * FROM causal_experiments WHERE id = ?').get(experimentId) as any;
188
+ if (!experiment) {
189
+ throw new Error(`Experiment ${experimentId} not found`);
190
+ }
191
+
192
+ log.info(`Experiment: ${experiment.hypothesis || 'Unknown'}`);
193
+ log.info(`Treatment Mean: ${experiment.treatment_mean?.toFixed(3) || 'N/A'}`);
194
+ log.info(`Control Mean: ${experiment.control_mean?.toFixed(3) || 'N/A'}`);
195
+ log.success(`Uplift: ${result?.uplift?.toFixed(3) || 'N/A'}`);
196
+ if (result?.confidenceInterval && result.confidenceInterval.length === 2) {
197
+ log.info(`95% CI: [${result.confidenceInterval[0]?.toFixed(3) || 'N/A'}, ${result.confidenceInterval[1]?.toFixed(3) || 'N/A'}]`);
198
+ }
199
+ if (result?.pValue !== undefined) {
200
+ log.info(`p-value: ${result.pValue.toFixed(4)}`);
201
+ }
168
202
 
169
- if (result.pValue < 0.05) {
203
+ // Get sample sizes from observations
204
+ const counts = this.db!.prepare('SELECT COUNT(*) as total, SUM(is_treatment) as treatment FROM causal_observations WHERE experiment_id = ?').get(experimentId) as any;
205
+ if (!counts) {
206
+ throw new Error(`Failed to get observation counts for experiment ${experimentId}`);
207
+ }
208
+ log.info(`Sample Sizes: ${counts.treatment || 0} treatment, ${(counts.total || 0) - (counts.treatment || 0)} control`);
209
+
210
+ if (result && result.pValue !== undefined && result.pValue < 0.05) {
170
211
  log.success('Result is statistically significant (p < 0.05)');
171
212
  } else {
172
213
  log.warning('Result is not statistically significant');
@@ -184,9 +225,10 @@ class AgentDBCLI {
184
225
 
185
226
  log.header('\n🔍 Querying Causal Edges');
186
227
 
187
- const edges = this.causalGraph.getCausalEffects({
188
- cause: params.cause,
189
- effect: params.effect,
228
+ const edges = this.causalGraph.queryCausalEffects({
229
+ interventionMemoryId: 0,
230
+ interventionMemoryType: params.cause || '',
231
+ outcomeMemoryId: params.effect ? 0 : undefined,
190
232
  minConfidence: params.minConfidence || 0.7,
191
233
  minUplift: params.minUplift || 0.1
192
234
  });
@@ -198,8 +240,8 @@ class AgentDBCLI {
198
240
 
199
241
  console.log('\n' + '═'.repeat(80));
200
242
  edges.slice(0, params.limit || 10).forEach((edge, i) => {
201
- console.log(`${colors.bright}#${i + 1}: ${edge.cause} → ${edge.effect}${colors.reset}`);
202
- console.log(` Uplift: ${colors.green}${edge.uplift.toFixed(3)}${colors.reset}`);
243
+ console.log(`${colors.bright}#${i + 1}: ${edge.fromMemoryType} → ${edge.toMemoryType}${colors.reset}`);
244
+ console.log(` Uplift: ${colors.green}${(edge.uplift || 0).toFixed(3)}${colors.reset}`);
203
245
  console.log(` Confidence: ${edge.confidence.toFixed(2)} (n=${edge.sampleSize})`);
204
246
  console.log('─'.repeat(80));
205
247
  });
@@ -226,30 +268,26 @@ class AgentDBCLI {
226
268
 
227
269
  const startTime = Date.now();
228
270
 
229
- const result = await this.causalRecall.recall({
230
- qid: 'cli-' + Date.now(),
231
- query: params.query,
232
- k: params.k || 12,
233
- weights: {
234
- alpha: params.alpha || 0.7,
235
- beta: params.beta || 0.2,
236
- gamma: params.gamma || 0.1
237
- }
238
- });
271
+ const result = await this.causalRecall.recall(
272
+ 'cli-' + Date.now(),
273
+ params.query,
274
+ params.k || 12,
275
+ undefined,
276
+ 'internal'
277
+ );
239
278
 
240
279
  const duration = Date.now() - startTime;
241
280
 
242
281
  console.log('\n' + '═'.repeat(80));
243
- console.log(`${colors.bright}Results (${result.results.length})${colors.reset}`);
282
+ console.log(`${colors.bright}Results (${result.candidates.length})${colors.reset}`);
244
283
  console.log('═'.repeat(80));
245
284
 
246
- result.results.slice(0, 5).forEach((r, i) => {
247
- console.log(`\n${colors.bright}#${i + 1}: Episode ${r.episode.id}${colors.reset}`);
248
- console.log(` Task: ${r.episode.task}`);
285
+ result.candidates.slice(0, 5).forEach((r, i) => {
286
+ console.log(`\n${colors.bright}#${i + 1}: ${r.type} ${r.id}${colors.reset}`);
287
+ console.log(` Content: ${r.content.substring(0, 50)}...`);
249
288
  console.log(` Similarity: ${colors.cyan}${r.similarity.toFixed(3)}${colors.reset}`);
250
289
  console.log(` Uplift: ${colors.green}${r.uplift?.toFixed(3) || 'N/A'}${colors.reset}`);
251
- console.log(` Utility: ${colors.yellow}${r.utility.toFixed(3)}${colors.reset}`);
252
- console.log(` Reward: ${r.episode.reward.toFixed(2)}`);
290
+ console.log(` Utility: ${colors.yellow}${r.utilityScore.toFixed(3)}${colors.reset}`);
253
291
  });
254
292
 
255
293
  console.log('\n' + '═'.repeat(80));
@@ -309,9 +347,16 @@ class AgentDBCLI {
309
347
 
310
348
  log.header('\n🧹 Pruning Low-Quality Edges');
311
349
 
312
- const pruned = await this.nightlyLearner.pruneEdges(params);
350
+ // Update config and run pruning
351
+ this.nightlyLearner.updateConfig({
352
+ confidenceThreshold: params.minConfidence || 0.6,
353
+ upliftThreshold: params.minUplift || 0.05,
354
+ edgeMaxAgeDays: params.maxAgeDays || 90
355
+ });
313
356
 
314
- log.success(`Pruned ${pruned} edges`);
357
+ const report = await this.nightlyLearner.run();
358
+
359
+ log.success(`Pruned ${report.edgesPruned} edges`);
315
360
  }
316
361
 
317
362
  // ============================================================================
@@ -399,7 +444,7 @@ class AgentDBCLI {
399
444
 
400
445
  const summary = await this.reflexion.getCritiqueSummary({
401
446
  task: params.task,
402
- k: params.k || 5
447
+ k: params.k
403
448
  });
404
449
 
405
450
  console.log('\n' + '═'.repeat(80));
@@ -417,7 +462,7 @@ class AgentDBCLI {
417
462
 
418
463
  log.header('\n🧹 Pruning Episodes');
419
464
 
420
- const pruned = await this.reflexion.pruneEpisodes({
465
+ const pruned = this.reflexion.pruneEpisodes({
421
466
  minReward: params.minReward || 0.3,
422
467
  maxAgeDays: params.maxAgeDays || 30,
423
468
  keepMinPerTask: params.keepMinPerTask || 5
@@ -498,21 +543,57 @@ class AgentDBCLI {
498
543
  minAttempts?: number;
499
544
  minReward?: number;
500
545
  timeWindowDays?: number;
546
+ extractPatterns?: boolean;
501
547
  }): Promise<void> {
502
548
  if (!this.skills) throw new Error('Not initialized');
503
549
 
504
- log.header('\n🔄 Consolidating Episodes into Skills');
550
+ log.header('\n🔄 Consolidating Episodes into Skills with Pattern Extraction');
505
551
  log.info(`Min Attempts: ${params.minAttempts || 3}`);
506
552
  log.info(`Min Reward: ${params.minReward || 0.7}`);
507
553
  log.info(`Time Window: ${params.timeWindowDays || 7} days`);
554
+ log.info(`Pattern Extraction: ${params.extractPatterns !== false ? 'Enabled' : 'Disabled'}`);
555
+
556
+ const startTime = Date.now();
508
557
 
509
- const created = this.skills.consolidateEpisodesIntoSkills({
558
+ const result = await this.skills.consolidateEpisodesIntoSkills({
510
559
  minAttempts: params.minAttempts || 3,
511
560
  minReward: params.minReward || 0.7,
512
- timeWindowDays: params.timeWindowDays || 7
561
+ timeWindowDays: params.timeWindowDays || 7,
562
+ extractPatterns: params.extractPatterns !== false
513
563
  });
514
564
 
515
- log.success(`Created ${created} new skills from successful episodes`);
565
+ const duration = Date.now() - startTime;
566
+
567
+ log.success(`Created ${result.created} new skills, updated ${result.updated} existing skills in ${duration}ms`);
568
+
569
+ // Display extracted patterns if available
570
+ if (result.patterns.length > 0) {
571
+ console.log('\n' + '═'.repeat(80));
572
+ console.log(`${colors.bright}${colors.cyan}Extracted Patterns:${colors.reset}`);
573
+ console.log('═'.repeat(80));
574
+
575
+ result.patterns.forEach((pattern, i) => {
576
+ console.log(`\n${colors.bright}#${i + 1}: ${pattern.task}${colors.reset}`);
577
+ console.log(` Avg Reward: ${colors.green}${pattern.avgReward.toFixed(2)}${colors.reset}`);
578
+
579
+ if (pattern.commonPatterns.length > 0) {
580
+ console.log(` ${colors.cyan}Common Patterns:${colors.reset}`);
581
+ pattern.commonPatterns.forEach(p => console.log(` • ${p}`));
582
+ }
583
+
584
+ if (pattern.successIndicators.length > 0) {
585
+ console.log(` ${colors.yellow}Success Indicators:${colors.reset}`);
586
+ pattern.successIndicators.forEach(s => console.log(` • ${s}`));
587
+ }
588
+
589
+ console.log('─'.repeat(80));
590
+ });
591
+ }
592
+
593
+ if (result.created === 0 && result.updated === 0) {
594
+ log.warning('No episodes met the criteria for skill consolidation');
595
+ log.info('Try lowering minReward or increasing timeWindowDays');
596
+ }
516
597
  }
517
598
 
518
599
  async skillPrune(params: {
@@ -700,7 +781,7 @@ async function handleReflexionCommands(cli: AgentDBCLI, subcommand: string, args
700
781
  } else if (subcommand === 'critique-summary') {
701
782
  await cli.reflexionGetCritiqueSummary({
702
783
  task: args[0],
703
- onlyFailures: args[1] === 'true'
784
+ k: args[1] ? parseInt(args[1]) : undefined
704
785
  });
705
786
  } else if (subcommand === 'prune') {
706
787
  await cli.reflexionPrune({
@@ -729,7 +810,8 @@ async function handleSkillCommands(cli: AgentDBCLI, subcommand: string, args: st
729
810
  await cli.skillConsolidate({
730
811
  minAttempts: args[0] ? parseInt(args[0]) : undefined,
731
812
  minReward: args[1] ? parseFloat(args[1]) : undefined,
732
- timeWindowDays: args[2] ? parseInt(args[2]) : undefined
813
+ timeWindowDays: args[2] ? parseInt(args[2]) : undefined,
814
+ extractPatterns: args[3] !== 'false' // Default true, set to false if explicitly passed
733
815
  });
734
816
  } else if (subcommand === 'prune') {
735
817
  await cli.skillPrune({
@@ -812,8 +894,10 @@ ${colors.bright}SKILL COMMANDS:${colors.reset}
812
894
  agentdb skill search <query> [k]
813
895
  Find applicable skills by similarity
814
896
 
815
- agentdb skill consolidate [min-attempts] [min-reward] [time-window-days]
816
- Auto-create skills from successful episodes (defaults: 3, 0.7, 7)
897
+ agentdb skill consolidate [min-attempts] [min-reward] [time-window-days] [extract-patterns]
898
+ Auto-create skills from successful episodes with ML pattern extraction
899
+ Defaults: min-attempts=3, min-reward=0.7, time-window-days=7, extract-patterns=true
900
+ Analyzes: keyword frequency, critique patterns, reward distribution, metadata, learning curves
817
901
 
818
902
  agentdb skill prune [min-uses] [min-success-rate] [max-age-days]
819
903
  Remove underperforming skills (defaults: 3, 0.4, 60)
@@ -834,7 +918,8 @@ ${colors.bright}EXAMPLES:${colors.reset}
834
918
  # Skills: Create and search
835
919
  agentdb skill create "jwt_auth" "Generate JWT tokens" "code here..."
836
920
  agentdb skill search "authentication" 5
837
- agentdb skill consolidate 3 0.7 7
921
+ agentdb skill consolidate 3 0.7 7 true # With pattern extraction
922
+ agentdb skill consolidate 5 0.8 14 # Higher thresholds, longer window
838
923
 
839
924
  # Causal: Add edges and run experiments
840
925
  agentdb causal add-edge "add_tests" "code_quality" 0.25 0.95 100
@@ -92,9 +92,9 @@ export class CausalMemoryGraph {
92
92
  INSERT INTO causal_edges (
93
93
  from_memory_id, from_memory_type, to_memory_id, to_memory_type,
94
94
  similarity, uplift, confidence, sample_size,
95
- evidence_ids, experiment_ids, confounder_score,
95
+ evidence_ids, confounder_score,
96
96
  mechanism, metadata
97
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
97
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
98
98
  `);
99
99
 
100
100
  const result = stmt.run(
@@ -107,7 +107,6 @@ export class CausalMemoryGraph {
107
107
  edge.confidence,
108
108
  edge.sampleSize || null,
109
109
  edge.evidenceIds ? JSON.stringify(edge.evidenceIds) : null,
110
- edge.experimentIds ? JSON.stringify(edge.experimentIds) : null,
111
110
  edge.confounderScore || null,
112
111
  edge.mechanism || null,
113
112
  edge.metadata ? JSON.stringify(edge.metadata) : null
@@ -161,7 +161,8 @@ export class CausalRecall {
161
161
 
162
162
  for (const ep of episodes) {
163
163
  const episodeRow = ep as any;
164
- const embedding = new Float32Array(JSON.parse(episodeRow.embedding));
164
+ // Embeddings are stored as Buffer, not JSON
165
+ const embedding = this.deserializeEmbedding(episodeRow.embedding);
165
166
  const similarity = this.cosineSimilarity(queryEmbedding, embedding);
166
167
  results.push({
167
168
  id: episodeRow.id.toString(),
@@ -313,6 +314,13 @@ export class CausalRecall {
313
314
  return [...new Set(words)];
314
315
  }
315
316
 
317
+ /**
318
+ * Deserialize embedding from Buffer
319
+ */
320
+ private deserializeEmbedding(buffer: Buffer): Float32Array {
321
+ return new Float32Array(buffer.buffer, buffer.byteOffset, buffer.length / 4);
322
+ }
323
+
316
324
  /**
317
325
  * Cosine similarity between two vectors
318
326
  */
@@ -392,4 +400,68 @@ export class CausalRecall {
392
400
  updateConfig(config: Partial<RerankConfig>): void {
393
401
  this.config = { ...this.config, ...config };
394
402
  }
403
+
404
+ /**
405
+ * Search for memories with semantic similarity and causal utility ranking
406
+ *
407
+ * @param params Search parameters
408
+ * @returns Array of ranked search results with similarity and causal uplift scores
409
+ */
410
+ async search(params: {
411
+ query: string;
412
+ k?: number;
413
+ includeEvidence?: boolean;
414
+ alpha?: number;
415
+ beta?: number;
416
+ gamma?: number;
417
+ }): Promise<Array<{
418
+ id: number;
419
+ type: string;
420
+ content: string;
421
+ similarity: number;
422
+ causalUplift: number;
423
+ utilityScore: number;
424
+ }>> {
425
+ const {
426
+ query,
427
+ k = 12,
428
+ includeEvidence = false,
429
+ alpha = this.config.alpha,
430
+ beta = this.config.beta,
431
+ gamma = this.config.gamma
432
+ } = params;
433
+
434
+ // Temporarily override config for this search
435
+ const originalConfig = { ...this.config };
436
+ this.config = { ...this.config, alpha, beta, gamma };
437
+
438
+ try {
439
+ // Step 1: Generate query embedding
440
+ const queryEmbedding = await this.embedder.embed(query);
441
+
442
+ // Step 2: Vector similarity search
443
+ const candidates = await this.vectorSearch(queryEmbedding, k * 2);
444
+
445
+ // Step 3: Load causal edges for uplift scoring
446
+ const causalEdges = await this.loadCausalEdges(candidates.map(c => c.id));
447
+
448
+ // Step 4: Rerank by utility
449
+ const reranked = this.rerankByUtility(candidates, causalEdges);
450
+
451
+ // Step 5: Format results for search interface
452
+ const results = reranked.slice(0, k).map(candidate => ({
453
+ id: parseInt(candidate.id),
454
+ type: candidate.type,
455
+ content: candidate.content,
456
+ similarity: candidate.similarity,
457
+ causalUplift: candidate.uplift || 0,
458
+ utilityScore: candidate.utilityScore
459
+ }));
460
+
461
+ return results;
462
+ } finally {
463
+ // Restore original config
464
+ this.config = originalConfig;
465
+ }
466
+ }
395
467
  }
@@ -104,7 +104,7 @@ export class EmbeddingService {
104
104
  })
105
105
  });
106
106
 
107
- const data = await response.json();
107
+ const data: any = await response.json();
108
108
  return new Float32Array(data.data[0].embedding);
109
109
  }
110
110
 
@@ -112,6 +112,11 @@ export class EmbeddingService {
112
112
  // Simple deterministic mock embedding for testing
113
113
  const embedding = new Float32Array(this.config.dimension);
114
114
 
115
+ // Handle null/undefined/empty text
116
+ if (!text || text.length === 0) {
117
+ return new Array(this.config.dimension).fill(0) as any as Float32Array;
118
+ }
119
+
115
120
  // Use simple hash-based generation
116
121
  let hash = 0;
117
122
  for (let i = 0; i < text.length; i++) {
@@ -428,7 +428,7 @@ export class ExplainableRecall {
428
428
  const contentHash = this.getContentHash(sourceType, sourceId);
429
429
 
430
430
  this.db.prepare(`
431
- INSERT INTO provenance_sources (source_type, source_id, content_hash, creator)
431
+ INSERT OR IGNORE INTO provenance_sources (source_type, source_id, content_hash, creator)
432
432
  VALUES (?, ?, ?, ?)
433
433
  `).run(sourceType, sourceId, contentHash, 'system');
434
434