agent-working-memory 0.10.0 → 0.12.0

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 (136) hide show
  1. package/README.md +118 -19
  2. package/dist/adapters/claude-code.d.ts.map +1 -1
  3. package/dist/adapters/claude-code.js +63 -3
  4. package/dist/adapters/claude-code.js.map +1 -1
  5. package/dist/adapters/common.d.ts.map +1 -1
  6. package/dist/adapters/common.js +329 -302
  7. package/dist/adapters/common.js.map +1 -1
  8. package/dist/api/routes.d.ts.map +1 -1
  9. package/dist/api/routes.js +31 -8
  10. package/dist/api/routes.js.map +1 -1
  11. package/dist/cli/migrate.js +29 -29
  12. package/dist/cli.js +82 -2
  13. package/dist/cli.js.map +1 -1
  14. package/dist/coordination/circuit-breaker.js +23 -23
  15. package/dist/coordination/routes.d.ts.map +1 -1
  16. package/dist/coordination/routes.js +174 -170
  17. package/dist/coordination/routes.js.map +1 -1
  18. package/dist/core/embeddings.d.ts.map +1 -1
  19. package/dist/core/embeddings.js +3 -0
  20. package/dist/core/embeddings.js.map +1 -1
  21. package/dist/core/entity-extract.d.ts +3 -0
  22. package/dist/core/entity-extract.d.ts.map +1 -0
  23. package/dist/core/entity-extract.js +47 -0
  24. package/dist/core/entity-extract.js.map +1 -0
  25. package/dist/core/salience.d.ts.map +1 -1
  26. package/dist/core/salience.js +14 -2
  27. package/dist/core/salience.js.map +1 -1
  28. package/dist/core/whoami.d.ts +24 -0
  29. package/dist/core/whoami.d.ts.map +1 -0
  30. package/dist/core/whoami.js +66 -0
  31. package/dist/core/whoami.js.map +1 -0
  32. package/dist/core/write-pipeline.d.ts +9 -0
  33. package/dist/core/write-pipeline.d.ts.map +1 -1
  34. package/dist/core/write-pipeline.js +109 -68
  35. package/dist/core/write-pipeline.js.map +1 -1
  36. package/dist/core/write-telemetry.d.ts +33 -0
  37. package/dist/core/write-telemetry.d.ts.map +1 -0
  38. package/dist/core/write-telemetry.js +110 -0
  39. package/dist/core/write-telemetry.js.map +1 -0
  40. package/dist/engine/activation.d.ts +22 -12
  41. package/dist/engine/activation.d.ts.map +1 -1
  42. package/dist/engine/activation.js +133 -17
  43. package/dist/engine/activation.js.map +1 -1
  44. package/dist/engine/consolidation-scheduler.d.ts +1 -1
  45. package/dist/engine/consolidation-scheduler.js +1 -1
  46. package/dist/engine/consolidation.d.ts +1 -0
  47. package/dist/engine/consolidation.d.ts.map +1 -1
  48. package/dist/engine/consolidation.js +18 -0
  49. package/dist/engine/consolidation.js.map +1 -1
  50. package/dist/engine/eval.d.ts.map +1 -1
  51. package/dist/engine/eval.js +5 -1
  52. package/dist/engine/eval.js.map +1 -1
  53. package/dist/index.js +21 -2
  54. package/dist/index.js.map +1 -1
  55. package/dist/mcp.d.ts +2 -1
  56. package/dist/mcp.d.ts.map +1 -1
  57. package/dist/mcp.js +212 -97
  58. package/dist/mcp.js.map +1 -1
  59. package/dist/onboard/index.d.ts +68 -0
  60. package/dist/onboard/index.d.ts.map +1 -0
  61. package/dist/onboard/index.js +265 -0
  62. package/dist/onboard/index.js.map +1 -0
  63. package/dist/recipes/index.d.ts +57 -0
  64. package/dist/recipes/index.d.ts.map +1 -0
  65. package/dist/recipes/index.js +81 -0
  66. package/dist/recipes/index.js.map +1 -0
  67. package/dist/storage/pglite-schema.d.ts.map +1 -1
  68. package/dist/storage/pglite-schema.js +170 -143
  69. package/dist/storage/pglite-schema.js.map +1 -1
  70. package/dist/storage/pglite.d.ts +5 -0
  71. package/dist/storage/pglite.d.ts.map +1 -1
  72. package/dist/storage/pglite.js +180 -138
  73. package/dist/storage/pglite.js.map +1 -1
  74. package/dist/storage/postgres.d.ts +5 -0
  75. package/dist/storage/postgres.d.ts.map +1 -1
  76. package/dist/storage/postgres.js +44 -2
  77. package/dist/storage/postgres.js.map +1 -1
  78. package/dist/storage/sqlite.d.ts +9 -0
  79. package/dist/storage/sqlite.d.ts.map +1 -1
  80. package/dist/storage/sqlite.js +394 -326
  81. package/dist/storage/sqlite.js.map +1 -1
  82. package/dist/types/engram.d.ts +14 -0
  83. package/dist/types/engram.d.ts.map +1 -1
  84. package/dist/types/engram.js.map +1 -1
  85. package/dist/version.d.ts +2 -0
  86. package/dist/version.d.ts.map +1 -0
  87. package/dist/version.js +27 -0
  88. package/dist/version.js.map +1 -0
  89. package/package.json +9 -1
  90. package/src/adapters/claude-code.ts +66 -3
  91. package/src/adapters/common.ts +538 -511
  92. package/src/api/index.ts +3 -3
  93. package/src/api/routes.ts +999 -970
  94. package/src/cli/migrate.ts +307 -307
  95. package/src/cli.ts +77 -2
  96. package/src/coordination/circuit-breaker.ts +83 -83
  97. package/src/coordination/failure-modes.ts +50 -50
  98. package/src/coordination/routes.ts +2155 -2150
  99. package/src/core/decay.ts +63 -63
  100. package/src/core/embeddings.ts +113 -110
  101. package/src/core/entity-extract.ts +47 -0
  102. package/src/core/index.ts +5 -5
  103. package/src/core/logger.ts +36 -36
  104. package/src/core/ml-worker-entry.ts +194 -194
  105. package/src/core/ml-worker.ts +281 -281
  106. package/src/core/query-expander.ts +122 -122
  107. package/src/core/reranker.ts +119 -119
  108. package/src/core/salience.ts +529 -514
  109. package/src/core/whoami.ts +92 -0
  110. package/src/core/write-pipeline.ts +60 -8
  111. package/src/core/write-telemetry.ts +131 -0
  112. package/src/engine/activation.ts +1468 -1369
  113. package/src/engine/confidence.ts +120 -120
  114. package/src/engine/consolidation-scheduler.ts +242 -242
  115. package/src/engine/consolidation.ts +887 -869
  116. package/src/engine/eval.ts +107 -102
  117. package/src/engine/eviction.ts +101 -101
  118. package/src/engine/index.ts +8 -8
  119. package/src/engine/retraction.ts +366 -366
  120. package/src/engine/staging.ts +74 -74
  121. package/src/index.ts +248 -226
  122. package/src/mcp.ts +1341 -1211
  123. package/src/onboard/index.ts +298 -0
  124. package/src/recipes/index.ts +125 -0
  125. package/src/storage/index.ts +3 -3
  126. package/src/storage/pglite-schema.ts +193 -166
  127. package/src/storage/pglite.ts +1420 -1372
  128. package/src/storage/postgres.ts +50 -2
  129. package/src/storage/sqlite.ts +1936 -1861
  130. package/src/storage/store.ts +80 -80
  131. package/src/types/agent.ts +67 -67
  132. package/src/types/checkpoint.ts +46 -46
  133. package/src/types/engram.ts +22 -0
  134. package/src/types/eval.ts +100 -100
  135. package/src/types/index.ts +6 -6
  136. package/src/version.ts +26 -0
@@ -92,6 +92,11 @@ function rowToEngram(row) {
92
92
  blockedBy: row.blocked_by ?? null,
93
93
  sequence: row.sequence == null ? null : Number(row.sequence),
94
94
  references: row.references_json ? JSON.parse(row.references_json) : null,
95
+ originClass: row.origin_class ?? null,
96
+ writerSession: row.writer_session ?? null,
97
+ recipeId: row.recipe_id ?? null,
98
+ validFrom: row.valid_from ?? null,
99
+ validTo: row.valid_to ?? null,
95
100
  };
96
101
  }
97
102
  function rowToAssociation(row) {
@@ -185,7 +190,7 @@ export class PGliteEngramStore {
185
190
  params.push(e.id, e.agentId, e.timestamp.toISOString(), e.context, e.resultsReturned, e.topScore, e.latencyMs, JSON.stringify(e.engramIds));
186
191
  }
187
192
  try {
188
- await this.db.query(`INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
193
+ await this.db.query(`INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
189
194
  VALUES ${values.join(',')}`, params);
190
195
  }
191
196
  catch {
@@ -223,18 +228,20 @@ export class PGliteEngramStore {
223
228
  await this.readyPromise;
224
229
  const id = input.id ?? randomUUID();
225
230
  const now = new Date().toISOString();
226
- await this.db.query(`INSERT INTO engrams (
227
- id, agent_id, concept, content, embedding, embedding_model,
228
- confidence, salience, access_count, last_accessed, created_at,
229
- salience_features, reason_codes, stage, ttl, retracted,
230
- tags, memory_type, memory_class, supersedes, episode_id,
231
- task_status, task_priority, blocked_by, sequence, references_json
232
- ) VALUES (
233
- $1, $2, $3, $4, $5::vector, $6,
234
- $7, $8, 0, $9, $10,
235
- $11, $12, 'active', $13, FALSE,
236
- $14, $15, $16, $17, $18,
237
- $19, $20, $21, $22, $23
231
+ await this.db.query(`INSERT INTO engrams (
232
+ id, agent_id, concept, content, embedding, embedding_model,
233
+ confidence, salience, access_count, last_accessed, created_at,
234
+ salience_features, reason_codes, stage, ttl, retracted,
235
+ tags, memory_type, memory_class, supersedes, episode_id,
236
+ task_status, task_priority, blocked_by, sequence, references_json,
237
+ origin_class, writer_session, recipe_id, valid_from, valid_to
238
+ ) VALUES (
239
+ $1, $2, $3, $4, $5::vector, $6,
240
+ $7, $8, 0, $9, $10,
241
+ $11, $12, 'active', $13, FALSE,
242
+ $14, $15, $16, $17, $18,
243
+ $19, $20, $21, $22, $23,
244
+ $24, $25, $26, $27, $28
238
245
  )`, [
239
246
  id,
240
247
  input.agentId,
@@ -259,6 +266,11 @@ export class PGliteEngramStore {
259
266
  input.sequence ?? null,
260
267
  input.references && input.references.length > 0
261
268
  ? JSON.stringify(input.references) : null,
269
+ input.originClass ?? null,
270
+ input.writerSession ?? null,
271
+ input.recipeId ?? null,
272
+ input.validFrom ?? null,
273
+ input.validTo ?? null,
262
274
  ]);
263
275
  const row = await this.getEngram(id);
264
276
  if (!row)
@@ -370,10 +382,10 @@ export class PGliteEngramStore {
370
382
  }
371
383
  async touchEngram(id) {
372
384
  await this.readyPromise;
373
- await this.db.query(`UPDATE engrams
374
- SET access_count = access_count + 1,
375
- last_accessed = $1,
376
- confidence = LEAST(0.85, confidence + 0.02 / (1.0 + sqrt(access_count::float)))
385
+ await this.db.query(`UPDATE engrams
386
+ SET access_count = access_count + 1,
387
+ last_accessed = $1,
388
+ confidence = LEAST(0.85, confidence + 0.02 / (1.0 + sqrt(access_count::float)))
377
389
  WHERE id = $2`, [new Date().toISOString(), id]);
378
390
  }
379
391
  async updateStage(id, stage) {
@@ -419,9 +431,9 @@ export class PGliteEngramStore {
419
431
  async timeWarp(agentId, ms) {
420
432
  await this.readyPromise;
421
433
  const seconds = Math.round(ms / 1000);
422
- const r1 = await this.db.query(`UPDATE engrams SET
423
- created_at = to_char(($1::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
424
- last_accessed = to_char(($3::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
434
+ const r1 = await this.db.query(`UPDATE engrams SET
435
+ created_at = to_char(($1::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
436
+ last_accessed = to_char(($3::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
425
437
  WHERE agent_id = $4`, ['now', seconds, 'now', agentId]);
426
438
  // Simpler: just update with relative arithmetic on stored ISO strings.
427
439
  // PGlite doesn't support all date ops cleanly; fall through to JS-side calculation.
@@ -447,13 +459,13 @@ export class PGliteEngramStore {
447
459
  // Restrict to active + fading. Faded engrams (Paper 1: storage degradation)
448
460
  // retain their embedding so they still participate in semantic recall, even
449
461
  // though their content has been trimmed. Excludes staging/consolidated/archived.
450
- const result = await this.db.query(`SELECT *, (embedding <=> $2::vector) AS distance
451
- FROM engrams
452
- WHERE agent_id = $1
453
- AND embedding IS NOT NULL
454
- AND retracted = FALSE
455
- AND stage IN ('active', 'fading')
456
- ORDER BY distance ASC
462
+ const result = await this.db.query(`SELECT *, (embedding <=> $2::vector) AS distance
463
+ FROM engrams
464
+ WHERE agent_id = $1
465
+ AND embedding IS NOT NULL
466
+ AND retracted = FALSE
467
+ AND stage IN ('active', 'fading')
468
+ ORDER BY distance ASC
457
469
  LIMIT $3`, [agentId, vectorToLiteral(vec), limit]);
458
470
  return result.rows.map((r) => ({ engram: rowToEngram(r), distance: r.distance }));
459
471
  }
@@ -472,11 +484,11 @@ export class PGliteEngramStore {
472
484
  if (tokens.length === 0)
473
485
  return [];
474
486
  const websearchQuery = tokens.join(' OR ');
475
- const result = await this.db.query(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
476
- FROM engrams
477
- WHERE agent_id = $1 AND retracted = FALSE
478
- AND fts @@ websearch_to_tsquery('english', $2)
479
- ORDER BY rank DESC
487
+ const result = await this.db.query(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
488
+ FROM engrams
489
+ WHERE agent_id = $1 AND retracted = FALSE
490
+ AND fts @@ websearch_to_tsquery('english', $2)
491
+ ORDER BY rank DESC
480
492
  LIMIT $3`, [agentId, websearchQuery, limit]);
481
493
  return result.rows.map((r) => ({ engram: rowToEngram(r), bm25Score: calibrateBm25(Number(r.rank)) }));
482
494
  }
@@ -490,11 +502,11 @@ export class PGliteEngramStore {
490
502
  if (tokens.length === 0)
491
503
  return [];
492
504
  const websearchQuery = tokens.join(' OR ');
493
- const result = await this.db.query(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
494
- FROM engrams
495
- WHERE agent_id = ANY($1::text[]) AND retracted = FALSE
496
- AND fts @@ websearch_to_tsquery('english', $2)
497
- ORDER BY rank DESC
505
+ const result = await this.db.query(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
506
+ FROM engrams
507
+ WHERE agent_id = ANY($1::text[]) AND retracted = FALSE
508
+ AND fts @@ websearch_to_tsquery('english', $2)
509
+ ORDER BY rank DESC
498
510
  LIMIT $3`, [agentIds, websearchQuery, limit]);
499
511
  return result.rows.map((r) => ({ engram: rowToEngram(r), bm25Score: calibrateBm25(Number(r.rank)) }));
500
512
  }
@@ -575,32 +587,32 @@ export class PGliteEngramStore {
575
587
  sql += ` AND task_status = $${params.length + 1}`;
576
588
  params.push(status);
577
589
  }
578
- sql += ` ORDER BY
579
- CASE task_priority
580
- WHEN 'urgent' THEN 0
581
- WHEN 'high' THEN 1
582
- WHEN 'medium' THEN 2
583
- WHEN 'low' THEN 3
584
- ELSE 4
585
- END,
590
+ sql += ` ORDER BY
591
+ CASE task_priority
592
+ WHEN 'urgent' THEN 0
593
+ WHEN 'high' THEN 1
594
+ WHEN 'medium' THEN 2
595
+ WHEN 'low' THEN 3
596
+ ELSE 4
597
+ END,
586
598
  created_at DESC`;
587
599
  const result = await this.db.query(sql, params);
588
600
  return result.rows.map(rowToEngram);
589
601
  }
590
602
  async getNextTask(agentId) {
591
603
  await this.readyPromise;
592
- const result = await this.db.query(`SELECT * FROM engrams
593
- WHERE agent_id = $1 AND task_status IN ('open', 'in_progress') AND retracted = FALSE
594
- ORDER BY
595
- CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
596
- CASE task_priority
597
- WHEN 'urgent' THEN 0
598
- WHEN 'high' THEN 1
599
- WHEN 'medium' THEN 2
600
- WHEN 'low' THEN 3
601
- ELSE 4
602
- END,
603
- created_at ASC
604
+ const result = await this.db.query(`SELECT * FROM engrams
605
+ WHERE agent_id = $1 AND task_status IN ('open', 'in_progress') AND retracted = FALSE
606
+ ORDER BY
607
+ CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
608
+ CASE task_priority
609
+ WHEN 'urgent' THEN 0
610
+ WHEN 'high' THEN 1
611
+ WHEN 'medium' THEN 2
612
+ WHEN 'low' THEN 3
613
+ ELSE 4
614
+ END,
615
+ created_at ASC
604
616
  LIMIT 1`, [agentId]);
605
617
  return result.rows.length > 0 ? rowToEngram(result.rows[0]) : null;
606
618
  }
@@ -614,11 +626,11 @@ export class PGliteEngramStore {
614
626
  }
615
627
  async findActiveMatchByConcept(agentId, concept, requiredTags) {
616
628
  await this.readyPromise;
617
- let sql = `SELECT * FROM engrams
618
- WHERE agent_id = $1
619
- AND LOWER(TRIM(concept)) = LOWER(TRIM($2))
620
- AND stage = 'active'
621
- AND retracted = FALSE
629
+ let sql = `SELECT * FROM engrams
630
+ WHERE agent_id = $1
631
+ AND LOWER(TRIM(concept)) = LOWER(TRIM($2))
632
+ AND stage = 'active'
633
+ AND retracted = FALSE
622
634
  AND superseded_by IS NULL`;
623
635
  const params = [agentId, concept];
624
636
  if (requiredTags && requiredTags.length > 0) {
@@ -651,12 +663,12 @@ export class PGliteEngramStore {
651
663
  await this.readyPromise;
652
664
  const id = randomUUID();
653
665
  const now = new Date().toISOString();
654
- await this.db.query(`INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
655
- VALUES ($1, $2, $3, $4, $5, $6, 0, $7, $7)
656
- ON CONFLICT (from_engram_id, to_engram_id) DO UPDATE SET
657
- weight = EXCLUDED.weight,
658
- confidence = EXCLUDED.confidence,
659
- last_activated = EXCLUDED.last_activated,
666
+ await this.db.query(`INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
667
+ VALUES ($1, $2, $3, $4, $5, $6, 0, $7, $7)
668
+ ON CONFLICT (from_engram_id, to_engram_id) DO UPDATE SET
669
+ weight = EXCLUDED.weight,
670
+ confidence = EXCLUDED.confidence,
671
+ last_activated = EXCLUDED.last_activated,
660
672
  activation_count = associations.activation_count + 1`, [id, fromId, toId, weight, confidence, type, now]);
661
673
  const assoc = await this.getAssociation(fromId, toId);
662
674
  if (!assoc)
@@ -678,12 +690,12 @@ export class PGliteEngramStore {
678
690
  if (engramIds.length === 0)
679
691
  return result;
680
692
  await this.readyPromise;
681
- const r = await this.db.query(`SELECT id, SUM(cnt) AS count, SUM(sw) AS sum_weight FROM (
682
- SELECT from_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE from_engram_id = ANY($1::text[])
683
- UNION ALL
684
- SELECT to_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE to_engram_id = ANY($1::text[])
685
- ) t
686
- WHERE id = ANY($1::text[])
693
+ const r = await this.db.query(`SELECT id, SUM(cnt) AS count, SUM(sw) AS sum_weight FROM (
694
+ SELECT from_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE from_engram_id = ANY($1::text[])
695
+ UNION ALL
696
+ SELECT to_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE to_engram_id = ANY($1::text[])
697
+ ) t
698
+ WHERE id = ANY($1::text[])
687
699
  GROUP BY id`, [engramIds]);
688
700
  for (const row of r.rows) {
689
701
  result.set(row.id, { count: Number(row.count), sumWeight: Number(row.sum_weight) });
@@ -699,7 +711,7 @@ export class PGliteEngramStore {
699
711
  if (engramIds.length === 0)
700
712
  return result;
701
713
  await this.readyPromise;
702
- const r = await this.db.query(`SELECT * FROM associations
714
+ const r = await this.db.query(`SELECT * FROM associations
703
715
  WHERE from_engram_id = ANY($1::text[]) OR to_engram_id = ANY($1::text[])`, [engramIds]);
704
716
  for (const row of r.rows) {
705
717
  const a = rowToAssociation(row);
@@ -739,8 +751,8 @@ export class PGliteEngramStore {
739
751
  }
740
752
  async getAllAssociations(agentId) {
741
753
  await this.readyPromise;
742
- const result = await this.db.query(`SELECT a.* FROM associations a
743
- JOIN engrams e ON a.from_engram_id = e.id
754
+ const result = await this.db.query(`SELECT a.* FROM associations a
755
+ JOIN engrams e ON a.from_engram_id = e.id
744
756
  WHERE e.agent_id = $1`, [agentId]);
745
757
  return result.rows.map(rowToAssociation);
746
758
  }
@@ -749,11 +761,11 @@ export class PGliteEngramStore {
749
761
  // ============================================================
750
762
  async getEvictionCandidates(agentId, limit) {
751
763
  await this.readyPromise;
752
- const result = await this.db.query(`SELECT * FROM engrams
753
- WHERE agent_id = $1 AND stage = 'active' AND retracted = FALSE
754
- ORDER BY (salience * 0.3 + confidence * 0.3
755
- + (access_count::float / (access_count + 5)) * 0.2
756
- + (1.0 / (1.0 + EXTRACT(EPOCH FROM (now() - last_accessed::timestamptz)) / 86400.0)) * 0.2) ASC
764
+ const result = await this.db.query(`SELECT * FROM engrams
765
+ WHERE agent_id = $1 AND stage = 'active' AND retracted = FALSE
766
+ ORDER BY (salience * 0.3 + confidence * 0.3
767
+ + (access_count::float / (access_count + 5)) * 0.2
768
+ + (1.0 / (1.0 + EXTRACT(EPOCH FROM (now() - last_accessed::timestamptz)) / 86400.0)) * 0.2) ASC
757
769
  LIMIT $2`, [agentId, limit]);
758
770
  return result.rows.map(rowToEngram);
759
771
  }
@@ -788,7 +800,7 @@ export class PGliteEngramStore {
788
800
  }
789
801
  async logStagingEvent(event) {
790
802
  await this.readyPromise;
791
- await this.db.query(`INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
803
+ await this.db.query(`INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
792
804
  VALUES ($1, $2, $3, $4, $5, $6)`, [
793
805
  event.engramId, event.agentId, event.action,
794
806
  event.resonanceScore, event.timestamp.toISOString(), event.ageMs,
@@ -796,18 +808,18 @@ export class PGliteEngramStore {
796
808
  }
797
809
  async logRetrievalFeedback(activationEventId, engramId, useful, context) {
798
810
  await this.readyPromise;
799
- await this.db.query(`INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
811
+ await this.db.query(`INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
800
812
  VALUES ($1, $2, $3, $4, $5, $6)`, [randomUUID(), activationEventId, engramId, useful, context, new Date().toISOString()]);
801
813
  }
802
814
  async getRetrievalPrecision(agentId, windowHours = 24) {
803
815
  await this.readyPromise;
804
816
  const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
805
- const result = await this.db.query(`SELECT
806
- COUNT(CASE WHEN useful = TRUE THEN 1 END) AS useful_count,
807
- COUNT(*) AS total_count
808
- FROM retrieval_feedback rf
809
- LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
810
- JOIN engrams e ON rf.engram_id = e.id
817
+ const result = await this.db.query(`SELECT
818
+ COUNT(CASE WHEN useful = TRUE THEN 1 END) AS useful_count,
819
+ COUNT(*) AS total_count
820
+ FROM retrieval_feedback rf
821
+ LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
822
+ JOIN engrams e ON rf.engram_id = e.id
811
823
  WHERE e.agent_id = $1 AND rf.timestamp > $2`, [agentId, since]);
812
824
  const row = result.rows[0];
813
825
  const total = Number(row?.total_count ?? 0);
@@ -816,10 +828,10 @@ export class PGliteEngramStore {
816
828
  }
817
829
  async getStagingMetrics(agentId) {
818
830
  await this.readyPromise;
819
- const result = await this.db.query(`SELECT
820
- COUNT(CASE WHEN action = 'promoted' THEN 1 END) AS promoted,
821
- COUNT(CASE WHEN action = 'discarded' THEN 1 END) AS discarded,
822
- COUNT(CASE WHEN action = 'expired' THEN 1 END) AS expired
831
+ const result = await this.db.query(`SELECT
832
+ COUNT(CASE WHEN action = 'promoted' THEN 1 END) AS promoted,
833
+ COUNT(CASE WHEN action = 'discarded' THEN 1 END) AS discarded,
834
+ COUNT(CASE WHEN action = 'expired' THEN 1 END) AS expired
823
835
  FROM staging_events WHERE agent_id = $1`, [agentId]);
824
836
  const row = result.rows[0] ?? { promoted: 0, discarded: 0, expired: 0 };
825
837
  return {
@@ -833,8 +845,8 @@ export class PGliteEngramStore {
833
845
  // Flush any buffered activation events so stats reflect the latest writes.
834
846
  await this.flushActivationEvents();
835
847
  const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
836
- const result = await this.db.query(`SELECT latency_ms FROM activation_events
837
- WHERE agent_id = $1 AND timestamp > $2
848
+ const result = await this.db.query(`SELECT latency_ms FROM activation_events
849
+ WHERE agent_id = $1 AND timestamp > $2
838
850
  ORDER BY latency_ms ASC`, [agentId, since]);
839
851
  if (result.rows.length === 0)
840
852
  return { count: 0, avgLatencyMs: 0, p95LatencyMs: 0 };
@@ -859,7 +871,7 @@ export class PGliteEngramStore {
859
871
  await this.readyPromise;
860
872
  const id = randomUUID();
861
873
  const now = new Date().toISOString();
862
- await this.db.query(`INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
874
+ await this.db.query(`INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
863
875
  VALUES ($1, $2, $3, $4::vector, 0, $5, $5, $5)`, [id, input.agentId, input.label, vectorToLiteral(input.embedding ?? null), now]);
864
876
  const ep = await this.getEpisode(id);
865
877
  if (!ep)
@@ -885,9 +897,9 @@ export class PGliteEngramStore {
885
897
  async addEngramToEpisode(engramId, episodeId) {
886
898
  await this.readyPromise;
887
899
  await this.db.query(`UPDATE engrams SET episode_id = $1 WHERE id = $2`, [episodeId, engramId]);
888
- await this.db.query(`UPDATE episodes SET
889
- engram_count = engram_count + 1,
890
- end_time = GREATEST(end_time, $1)
900
+ await this.db.query(`UPDATE episodes SET
901
+ engram_count = engram_count + 1,
902
+ end_time = GREATEST(end_time, $1)
891
903
  WHERE id = $2`, [new Date().toISOString(), episodeId]);
892
904
  }
893
905
  async getEngramsByEpisode(episodeId) {
@@ -926,46 +938,76 @@ export class PGliteEngramStore {
926
938
  async updateAutoCheckpointWrite(agentId, engramId) {
927
939
  await this.readyPromise;
928
940
  const now = new Date().toISOString();
929
- await this.db.query(`INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
930
- VALUES ($1, $2, $3, 1, $3)
931
- ON CONFLICT(agent_id) DO UPDATE SET
932
- last_write_id = EXCLUDED.last_write_id,
933
- last_activity_at = EXCLUDED.last_activity_at,
934
- write_count_since_consolidation = conscious_state.write_count_since_consolidation + 1,
941
+ await this.db.query(`INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
942
+ VALUES ($1, $2, $3, 1, $3)
943
+ ON CONFLICT(agent_id) DO UPDATE SET
944
+ last_write_id = EXCLUDED.last_write_id,
945
+ last_activity_at = EXCLUDED.last_activity_at,
946
+ write_count_since_consolidation = conscious_state.write_count_since_consolidation + 1,
935
947
  updated_at = EXCLUDED.updated_at`, [agentId, engramId, now]);
936
948
  }
937
949
  async updateAutoCheckpointRecall(agentId, context, engramIds) {
938
950
  await this.readyPromise;
939
951
  const now = new Date().toISOString();
940
- await this.db.query(`INSERT INTO conscious_state (agent_id, last_recall_context, last_recall_ids, last_activity_at, recall_count_since_consolidation, updated_at)
941
- VALUES ($1, $2, $3, $4, 1, $4)
942
- ON CONFLICT(agent_id) DO UPDATE SET
943
- last_recall_context = EXCLUDED.last_recall_context,
944
- last_recall_ids = EXCLUDED.last_recall_ids,
945
- last_activity_at = EXCLUDED.last_activity_at,
946
- recall_count_since_consolidation = conscious_state.recall_count_since_consolidation + 1,
952
+ await this.db.query(`INSERT INTO conscious_state (agent_id, last_recall_context, last_recall_ids, last_activity_at, recall_count_since_consolidation, updated_at)
953
+ VALUES ($1, $2, $3, $4, 1, $4)
954
+ ON CONFLICT(agent_id) DO UPDATE SET
955
+ last_recall_context = EXCLUDED.last_recall_context,
956
+ last_recall_ids = EXCLUDED.last_recall_ids,
957
+ last_activity_at = EXCLUDED.last_activity_at,
958
+ recall_count_since_consolidation = conscious_state.recall_count_since_consolidation + 1,
947
959
  updated_at = EXCLUDED.updated_at`, [agentId, context, JSON.stringify(engramIds), now]);
948
960
  }
949
961
  async touchActivity(agentId) {
950
962
  await this.readyPromise;
951
963
  const now = new Date().toISOString();
952
- await this.db.query(`INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
953
- VALUES ($1, $2, $2)
954
- ON CONFLICT(agent_id) DO UPDATE SET
955
- last_activity_at = EXCLUDED.last_activity_at,
964
+ await this.db.query(`INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
965
+ VALUES ($1, $2, $2)
966
+ ON CONFLICT(agent_id) DO UPDATE SET
967
+ last_activity_at = EXCLUDED.last_activity_at,
956
968
  updated_at = EXCLUDED.updated_at`, [agentId, now]);
957
969
  }
958
970
  async saveCheckpoint(agentId, state) {
959
971
  await this.readyPromise;
960
972
  const now = new Date().toISOString();
961
- await this.db.query(`INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
962
- VALUES ($1, $2, $3, $3, $3)
963
- ON CONFLICT(agent_id) DO UPDATE SET
964
- execution_state = EXCLUDED.execution_state,
965
- checkpoint_at = EXCLUDED.checkpoint_at,
966
- last_activity_at = EXCLUDED.last_activity_at,
973
+ await this.db.query(`INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
974
+ VALUES ($1, $2, $3, $3, $3)
975
+ ON CONFLICT(agent_id) DO UPDATE SET
976
+ execution_state = EXCLUDED.execution_state,
977
+ checkpoint_at = EXCLUDED.checkpoint_at,
978
+ last_activity_at = EXCLUDED.last_activity_at,
967
979
  updated_at = EXCLUDED.updated_at`, [agentId, JSON.stringify(state), now]);
968
980
  }
981
+ /** Distinct agent ids present in this store (D3 whoami; includes all stages). */
982
+ async listAgentIds() {
983
+ const res = await this.db.query('SELECT DISTINCT agent_id FROM engrams ORDER BY agent_id');
984
+ return res.rows.map(r => r.agent_id);
985
+ }
986
+ async recordEntityMentions(engramId, agentId, entities) {
987
+ if (!entities || entities.length === 0)
988
+ return;
989
+ for (const entity of entities) {
990
+ await this.db.query('INSERT INTO entity_mentions (entity, engram_id, agent_id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING', [entity, engramId, agentId]);
991
+ }
992
+ }
993
+ async searchEntities(term, limit = 8) {
994
+ const t = term.toLowerCase().replace(/[%_]/g, '');
995
+ if (!t)
996
+ return [];
997
+ const res = await this.db.query(`SELECT DISTINCT entity FROM (
998
+ SELECT entity FROM entity_mentions WHERE entity LIKE $1
999
+ UNION SELECT entity FROM entity_aliases WHERE alias LIKE $1
1000
+ ) u LIMIT $2`, [`%${t}%`, limit]);
1001
+ return res.rows.map(r => r.entity);
1002
+ }
1003
+ async getEngramIdsByEntity(entity, agentId) {
1004
+ const alias = await this.db.query('SELECT entity FROM entity_aliases WHERE alias = $1', [entity.toLowerCase()]);
1005
+ const resolved = alias.rows[0]?.entity ?? entity.toLowerCase();
1006
+ const res = agentId
1007
+ ? await this.db.query('SELECT engram_id FROM entity_mentions WHERE entity = $1 AND agent_id = $2', [resolved, agentId])
1008
+ : await this.db.query('SELECT engram_id FROM entity_mentions WHERE entity = $1', [resolved]);
1009
+ return res.rows.map(r => r.engram_id);
1010
+ }
969
1011
  async getCheckpoint(agentId) {
970
1012
  await this.readyPromise;
971
1013
  const result = await this.db.query(`SELECT * FROM conscious_state WHERE agent_id = $1`, [agentId]);
@@ -996,13 +1038,13 @@ export class PGliteEngramStore {
996
1038
  await this.db.query(`UPDATE conscious_state SET last_mini_consolidation_at = $1, updated_at = $1 WHERE agent_id = $2`, [now, agentId]);
997
1039
  }
998
1040
  else {
999
- await this.db.query(`UPDATE conscious_state SET
1000
- last_consolidation_at = $1,
1001
- last_mini_consolidation_at = $1,
1002
- write_count_since_consolidation = 0,
1003
- recall_count_since_consolidation = 0,
1004
- consolidation_cycle_count = consolidation_cycle_count + 1,
1005
- updated_at = $1
1041
+ await this.db.query(`UPDATE conscious_state SET
1042
+ last_consolidation_at = $1,
1043
+ last_mini_consolidation_at = $1,
1044
+ write_count_since_consolidation = 0,
1045
+ recall_count_since_consolidation = 0,
1046
+ consolidation_cycle_count = consolidation_cycle_count + 1,
1047
+ updated_at = $1
1006
1048
  WHERE agent_id = $2`, [now, agentId]);
1007
1049
  }
1008
1050
  }
@@ -1027,10 +1069,10 @@ export class PGliteEngramStore {
1027
1069
  // ============================================================
1028
1070
  async getLatestByTag(opts) {
1029
1071
  await this.readyPromise;
1030
- let sql = `SELECT * FROM engrams
1031
- WHERE agent_id = $1
1032
- AND retracted = $2
1033
- AND stage = 'active'
1072
+ let sql = `SELECT * FROM engrams
1073
+ WHERE agent_id = $1
1074
+ AND retracted = $2
1075
+ AND stage = 'active'
1034
1076
  AND tags LIKE $3`;
1035
1077
  const params = [opts.agentId, opts.retracted ?? false, `%"${opts.tagKeyPrefix}%`];
1036
1078
  if (opts.scopeTagsAll && opts.scopeTagsAll.length > 0) {
@@ -1057,10 +1099,10 @@ export class PGliteEngramStore {
1057
1099
  }
1058
1100
  async getTopBy(opts) {
1059
1101
  await this.readyPromise;
1060
- let sql = `SELECT * FROM engrams
1061
- WHERE agent_id = $1
1062
- AND retracted = $2
1063
- AND stage = 'active'
1102
+ let sql = `SELECT * FROM engrams
1103
+ WHERE agent_id = $1
1104
+ AND retracted = $2
1105
+ AND stage = 'active'
1064
1106
  AND tags LIKE $3`;
1065
1107
  const params = [opts.agentId, opts.retracted ?? false, `%"${opts.sortField}%`];
1066
1108
  if (opts.filterTagsAll && opts.filterTagsAll.length > 0) {