agent-working-memory 0.16.1 → 0.18.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 (170) hide show
  1. package/README.md +321 -318
  2. package/dist/adapters/common.d.ts +34 -0
  3. package/dist/adapters/common.d.ts.map +1 -1
  4. package/dist/adapters/common.js +528 -444
  5. package/dist/adapters/common.js.map +1 -1
  6. package/dist/api/routes.d.ts.map +1 -1
  7. package/dist/api/routes.js +19 -0
  8. package/dist/api/routes.js.map +1 -1
  9. package/dist/cli/migrate.d.ts.map +1 -1
  10. package/dist/cli/migrate.js +31 -29
  11. package/dist/cli/migrate.js.map +1 -1
  12. package/dist/core/asked-value.d.ts +37 -0
  13. package/dist/core/asked-value.d.ts.map +1 -0
  14. package/dist/core/asked-value.js +77 -0
  15. package/dist/core/asked-value.js.map +1 -0
  16. package/dist/core/conflicts.d.ts +89 -0
  17. package/dist/core/conflicts.d.ts.map +1 -0
  18. package/dist/core/conflicts.js +267 -0
  19. package/dist/core/conflicts.js.map +1 -0
  20. package/dist/core/embeddings.d.ts +14 -2
  21. package/dist/core/embeddings.d.ts.map +1 -1
  22. package/dist/core/embeddings.js +32 -10
  23. package/dist/core/embeddings.js.map +1 -1
  24. package/dist/core/format-recall.d.ts +21 -1
  25. package/dist/core/format-recall.d.ts.map +1 -1
  26. package/dist/core/format-recall.js +60 -5
  27. package/dist/core/format-recall.js.map +1 -1
  28. package/dist/core/ml-worker.d.ts +24 -1
  29. package/dist/core/ml-worker.d.ts.map +1 -1
  30. package/dist/core/ml-worker.js +165 -4
  31. package/dist/core/ml-worker.js.map +1 -1
  32. package/dist/core/model-daemon-client.d.ts +77 -0
  33. package/dist/core/model-daemon-client.d.ts.map +1 -0
  34. package/dist/core/model-daemon-client.js +383 -0
  35. package/dist/core/model-daemon-client.js.map +1 -0
  36. package/dist/core/model-daemon-server.d.ts +71 -0
  37. package/dist/core/model-daemon-server.d.ts.map +1 -0
  38. package/dist/core/model-daemon-server.js +363 -0
  39. package/dist/core/model-daemon-server.js.map +1 -0
  40. package/dist/core/model-daemon.d.ts +137 -0
  41. package/dist/core/model-daemon.d.ts.map +1 -0
  42. package/dist/core/model-daemon.js +231 -0
  43. package/dist/core/model-daemon.js.map +1 -0
  44. package/dist/core/query-expander.d.ts +5 -0
  45. package/dist/core/query-expander.d.ts.map +1 -1
  46. package/dist/core/query-expander.js +3 -2
  47. package/dist/core/query-expander.js.map +1 -1
  48. package/dist/core/recall-config.d.ts +1 -1
  49. package/dist/core/recall-config.d.ts.map +1 -1
  50. package/dist/core/recall-config.js +5 -0
  51. package/dist/core/recall-config.js.map +1 -1
  52. package/dist/core/rerank-window.d.ts +7 -2
  53. package/dist/core/rerank-window.d.ts.map +1 -1
  54. package/dist/core/rerank-window.js +9 -4
  55. package/dist/core/rerank-window.js.map +1 -1
  56. package/dist/core/rerank2.d.ts +52 -2
  57. package/dist/core/rerank2.d.ts.map +1 -1
  58. package/dist/core/rerank2.js +60 -3
  59. package/dist/core/rerank2.js.map +1 -1
  60. package/dist/core/reranker.d.ts +19 -0
  61. package/dist/core/reranker.d.ts.map +1 -1
  62. package/dist/core/reranker.js +76 -12
  63. package/dist/core/reranker.js.map +1 -1
  64. package/dist/core/salience.d.ts +2 -0
  65. package/dist/core/salience.d.ts.map +1 -1
  66. package/dist/core/salience.js +45 -18
  67. package/dist/core/salience.js.map +1 -1
  68. package/dist/core/trust.d.ts +32 -0
  69. package/dist/core/trust.d.ts.map +1 -0
  70. package/dist/core/trust.js +66 -0
  71. package/dist/core/trust.js.map +1 -0
  72. package/dist/core/validity.d.ts +21 -0
  73. package/dist/core/validity.d.ts.map +1 -0
  74. package/dist/core/validity.js +41 -0
  75. package/dist/core/validity.js.map +1 -0
  76. package/dist/core/whoami.d.ts +3 -0
  77. package/dist/core/whoami.d.ts.map +1 -1
  78. package/dist/core/whoami.js +14 -0
  79. package/dist/core/whoami.js.map +1 -1
  80. package/dist/core/write-pipeline.d.ts +48 -0
  81. package/dist/core/write-pipeline.d.ts.map +1 -1
  82. package/dist/core/write-pipeline.js +104 -4
  83. package/dist/core/write-pipeline.js.map +1 -1
  84. package/dist/engine/activation.d.ts.map +1 -1
  85. package/dist/engine/activation.js +27 -4
  86. package/dist/engine/activation.js.map +1 -1
  87. package/dist/engine/consolidation.d.ts.map +1 -1
  88. package/dist/engine/consolidation.js +67 -5
  89. package/dist/engine/consolidation.js.map +1 -1
  90. package/dist/hooks/prime-query.d.ts +57 -0
  91. package/dist/hooks/prime-query.d.ts.map +1 -0
  92. package/dist/hooks/prime-query.js +388 -0
  93. package/dist/hooks/prime-query.js.map +1 -0
  94. package/dist/hooks/prime.d.ts +8 -0
  95. package/dist/hooks/prime.d.ts.map +1 -1
  96. package/dist/hooks/prime.js +21 -2
  97. package/dist/hooks/prime.js.map +1 -1
  98. package/dist/hooks/sidecar.d.ts +7 -0
  99. package/dist/hooks/sidecar.d.ts.map +1 -1
  100. package/dist/hooks/sidecar.js +10 -2
  101. package/dist/hooks/sidecar.js.map +1 -1
  102. package/dist/index.js +11 -3
  103. package/dist/index.js.map +1 -1
  104. package/dist/mcp.js +148 -103
  105. package/dist/mcp.js.map +1 -1
  106. package/dist/model-daemon.d.ts +8 -0
  107. package/dist/model-daemon.d.ts.map +1 -0
  108. package/dist/model-daemon.js +45 -0
  109. package/dist/model-daemon.js.map +1 -0
  110. package/dist/storage/factory.d.ts.map +1 -1
  111. package/dist/storage/factory.js +5 -0
  112. package/dist/storage/factory.js.map +1 -1
  113. package/dist/storage/pglite-schema.d.ts.map +1 -1
  114. package/dist/storage/pglite-schema.js +176 -170
  115. package/dist/storage/pglite-schema.js.map +1 -1
  116. package/dist/storage/pglite.d.ts +5 -0
  117. package/dist/storage/pglite.d.ts.map +1 -1
  118. package/dist/storage/pglite.js +165 -153
  119. package/dist/storage/pglite.js.map +1 -1
  120. package/dist/storage/postgres.d.ts +5 -0
  121. package/dist/storage/postgres.d.ts.map +1 -1
  122. package/dist/storage/postgres.js +165 -153
  123. package/dist/storage/postgres.js.map +1 -1
  124. package/dist/storage/sqlite.d.ts +22 -0
  125. package/dist/storage/sqlite.d.ts.map +1 -1
  126. package/dist/storage/sqlite.js +400 -363
  127. package/dist/storage/sqlite.js.map +1 -1
  128. package/dist/types/engram.d.ts +33 -0
  129. package/dist/types/engram.d.ts.map +1 -1
  130. package/dist/types/engram.js.map +1 -1
  131. package/package.json +106 -106
  132. package/plugin/.claude-plugin/plugin.json +1 -1
  133. package/plugin/README.md +1 -1
  134. package/plugin/bin/awm-mcp-launcher.cjs +2 -2
  135. package/plugin/skills/awm-memory/SKILL.md +16 -5
  136. package/src/adapters/common.ts +881 -800
  137. package/src/api/routes.ts +20 -0
  138. package/src/cli/migrate.ts +309 -307
  139. package/src/core/asked-value.ts +74 -0
  140. package/src/core/conflicts.ts +280 -0
  141. package/src/core/embeddings.ts +35 -10
  142. package/src/core/format-recall.ts +83 -25
  143. package/src/core/ml-worker.ts +458 -281
  144. package/src/core/model-daemon-client.ts +374 -0
  145. package/src/core/model-daemon-server.ts +344 -0
  146. package/src/core/model-daemon.ts +243 -0
  147. package/src/core/query-expander.ts +127 -126
  148. package/src/core/recall-config.ts +5 -0
  149. package/src/core/rerank-window.ts +164 -159
  150. package/src/core/rerank2.ts +143 -82
  151. package/src/core/reranker.ts +78 -11
  152. package/src/core/salience.ts +45 -18
  153. package/src/core/trust.ts +65 -0
  154. package/src/core/validity.ts +47 -0
  155. package/src/core/whoami.ts +136 -120
  156. package/src/core/write-pipeline.ts +143 -4
  157. package/src/engine/activation.ts +27 -4
  158. package/src/engine/consolidation.ts +950 -887
  159. package/src/hooks/prime-query.ts +375 -0
  160. package/src/hooks/prime.ts +25 -2
  161. package/src/hooks/sidecar.ts +13 -3
  162. package/src/index.ts +255 -248
  163. package/src/mcp.ts +1521 -1475
  164. package/src/model-daemon.ts +48 -0
  165. package/src/storage/factory.ts +25 -19
  166. package/src/storage/pglite-schema.ts +199 -193
  167. package/src/storage/pglite.ts +1513 -1498
  168. package/src/storage/postgres.ts +1613 -1598
  169. package/src/storage/sqlite.ts +2056 -2019
  170. package/src/types/engram.ts +30 -0
@@ -100,6 +100,7 @@ function rowToEngram(row) {
100
100
  recipeId: row.recipe_id ?? null,
101
101
  validFrom: row.valid_from ?? null,
102
102
  validTo: row.valid_to ?? null,
103
+ stageChangedAt: row.stage_changed_at ?? null,
103
104
  };
104
105
  }
105
106
  function rowToAssociation(row) {
@@ -193,7 +194,7 @@ export class PGliteEngramStore {
193
194
  params.push(e.id, e.agentId, e.timestamp.toISOString(), e.context, e.resultsReturned, e.topScore, e.latencyMs, JSON.stringify(e.engramIds));
194
195
  }
195
196
  try {
196
- await this.db.query(`INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
197
+ await this.db.query(`INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
197
198
  VALUES ${values.join(',')}`, params);
198
199
  }
199
200
  catch {
@@ -231,20 +232,20 @@ export class PGliteEngramStore {
231
232
  await this.readyPromise;
232
233
  const id = input.id ?? randomUUID();
233
234
  const now = new Date().toISOString();
234
- await this.db.query(`INSERT INTO engrams (
235
- id, agent_id, concept, content, embedding, embedding_model,
236
- confidence, salience, access_count, last_accessed, created_at,
237
- salience_features, reason_codes, stage, ttl, retracted,
238
- tags, memory_type, memory_class, supersedes, episode_id,
239
- task_status, task_priority, blocked_by, sequence, references_json,
240
- origin_class, writer_session, recipe_id, valid_from, valid_to
241
- ) VALUES (
242
- $1, $2, $3, $4, $5::vector, $6,
243
- $7, $8, 0, $9, $10,
244
- $11, $12, 'active', $13, FALSE,
245
- $14, $15, $16, $17, $18,
246
- $19, $20, $21, $22, $23,
247
- $24, $25, $26, $27, $28
235
+ await this.db.query(`INSERT INTO engrams (
236
+ id, agent_id, concept, content, embedding, embedding_model,
237
+ confidence, salience, access_count, last_accessed, created_at,
238
+ salience_features, reason_codes, stage, ttl, retracted,
239
+ tags, memory_type, memory_class, supersedes, episode_id,
240
+ task_status, task_priority, blocked_by, sequence, references_json,
241
+ origin_class, writer_session, recipe_id, valid_from, valid_to
242
+ ) VALUES (
243
+ $1, $2, $3, $4, $5::vector, $6,
244
+ $7, $8, 0, $9, $10,
245
+ $11, $12, 'active', $13, FALSE,
246
+ $14, $15, $16, $17, $18,
247
+ $19, $20, $21, $22, $23,
248
+ $24, $25, $26, $27, $28
248
249
  )`, [
249
250
  id,
250
251
  input.agentId,
@@ -396,11 +397,11 @@ export class PGliteEngramStore {
396
397
  await this.readyPromise;
397
398
  const rows = [];
398
399
  for (const agentId of agentIds) {
399
- const result = await this.db.query(`
400
- SELECT tags, access_count AS "accessCount", id FROM engrams
401
- WHERE agent_id = $1 AND stage = $2 AND retracted = FALSE
402
- ORDER BY access_count DESC, id
403
- LIMIT $3
400
+ const result = await this.db.query(`
401
+ SELECT tags, access_count AS "accessCount", id FROM engrams
402
+ WHERE agent_id = $1 AND stage = $2 AND retracted = FALSE
403
+ ORDER BY access_count DESC, id
404
+ LIMIT $3
404
405
  `, [agentId, stage, topN]);
405
406
  rows.push(...result.rows);
406
407
  }
@@ -440,15 +441,15 @@ export class PGliteEngramStore {
440
441
  }
441
442
  async touchEngram(id) {
442
443
  await this.readyPromise;
443
- await this.db.query(`UPDATE engrams
444
- SET access_count = access_count + 1,
445
- last_accessed = $1,
446
- confidence = LEAST(0.85, confidence + 0.02 / (1.0 + sqrt(access_count::float)))
444
+ await this.db.query(`UPDATE engrams
445
+ SET access_count = access_count + 1,
446
+ last_accessed = $1,
447
+ confidence = LEAST(0.85, confidence + 0.02 / (1.0 + sqrt(access_count::float)))
447
448
  WHERE id = $2`, [new Date().toISOString(), id]);
448
449
  }
449
450
  async updateStage(id, stage) {
450
451
  await this.readyPromise;
451
- await this.db.query(`UPDATE engrams SET stage = $1 WHERE id = $2`, [stage, id]);
452
+ await this.db.query(`UPDATE engrams SET stage = $1, stage_changed_at = $2 WHERE id = $3`, [stage, new Date().toISOString(), id]);
452
453
  }
453
454
  /**
454
455
  * Replace an engram's content. Used by the fade phase of consolidation
@@ -478,9 +479,20 @@ export class PGliteEngramStore {
478
479
  await this.readyPromise;
479
480
  await this.db.query(`UPDATE engrams SET retracted = TRUE, retracted_by = $1, retracted_at = $2 WHERE id = $3`, [retractedBy, new Date().toISOString(), id]);
480
481
  }
482
+ /**
483
+ * Delete an engram and the rows that point at it. Parity with the SQLite backend —
484
+ * see src/storage/sqlite.ts deleteEngram for the orphan counts that prompted this.
485
+ * activation_events is left alone on purpose: it records a recall that happened.
486
+ */
481
487
  async deleteEngram(id) {
482
488
  await this.readyPromise;
483
- await this.db.query(`DELETE FROM engrams WHERE id = $1`, [id]);
489
+ // One statement: a data-modifying CTE runs atomically, so a failure cannot leave the
490
+ // engram alive with its dependents gone. withTransaction is not re-entrant, and this
491
+ // can be called inside one (/memory/write-batch), so it does not open its own.
492
+ await this.db.query(`WITH f AS (DELETE FROM retrieval_feedback WHERE engram_id = $1),
493
+ m AS (DELETE FROM entity_mentions WHERE engram_id = $1),
494
+ s AS (DELETE FROM staging_events WHERE engram_id = $1)
495
+ DELETE FROM engrams WHERE id = $1`, [id]);
484
496
  }
485
497
  /**
486
498
  * Time warp - shift all timestamps backward by ms milliseconds.
@@ -489,9 +501,9 @@ export class PGliteEngramStore {
489
501
  async timeWarp(agentId, ms) {
490
502
  await this.readyPromise;
491
503
  const seconds = Math.round(ms / 1000);
492
- const r1 = await this.db.query(`UPDATE engrams SET
493
- created_at = to_char(($1::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
494
- last_accessed = to_char(($3::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
504
+ const r1 = await this.db.query(`UPDATE engrams SET
505
+ created_at = to_char(($1::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
506
+ last_accessed = to_char(($3::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
495
507
  WHERE agent_id = $4`, ['now', seconds, 'now', agentId]);
496
508
  // Simpler: just update with relative arithmetic on stored ISO strings.
497
509
  // PGlite doesn't support all date ops cleanly; fall through to JS-side calculation.
@@ -521,13 +533,13 @@ export class PGliteEngramStore {
521
533
  // Restrict to active + fading. Faded engrams (Paper 1: storage degradation)
522
534
  // retain their embedding so they still participate in semantic recall, even
523
535
  // though their content has been trimmed. Excludes staging/consolidated/archived.
524
- const result = await this.db.query(`SELECT *, (embedding <=> $2::vector) AS distance
525
- FROM engrams
526
- WHERE agent_id = $1
527
- AND embedding IS NOT NULL
528
- AND retracted = FALSE
529
- AND stage IN ('active', 'fading')
530
- ORDER BY distance ASC
536
+ const result = await this.db.query(`SELECT *, (embedding <=> $2::vector) AS distance
537
+ FROM engrams
538
+ WHERE agent_id = $1
539
+ AND embedding IS NOT NULL
540
+ AND retracted = FALSE
541
+ AND stage IN ('active', 'fading')
542
+ ORDER BY distance ASC
531
543
  LIMIT $3`, [agentId, vectorToLiteral(vec), limit]);
532
544
  return result.rows.map((r) => ({ engram: rowToEngram(r), distance: r.distance }));
533
545
  }
@@ -546,11 +558,11 @@ export class PGliteEngramStore {
546
558
  if (tokens.length === 0)
547
559
  return [];
548
560
  const websearchQuery = tokens.join(' OR ');
549
- const result = await this.db.query(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
550
- FROM engrams
551
- WHERE agent_id = $1 AND retracted = FALSE
552
- AND fts @@ websearch_to_tsquery('english', $2)
553
- ORDER BY rank DESC
561
+ const result = await this.db.query(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
562
+ FROM engrams
563
+ WHERE agent_id = $1 AND retracted = FALSE
564
+ AND fts @@ websearch_to_tsquery('english', $2)
565
+ ORDER BY rank DESC
554
566
  LIMIT $3`, [agentId, websearchQuery, limit]);
555
567
  return result.rows.map((r) => ({ engram: rowToEngram(r), bm25Score: calibrateBm25(Number(r.rank)) }));
556
568
  }
@@ -564,11 +576,11 @@ export class PGliteEngramStore {
564
576
  if (tokens.length === 0)
565
577
  return [];
566
578
  const websearchQuery = tokens.join(' OR ');
567
- const result = await this.db.query(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
568
- FROM engrams
569
- WHERE agent_id = ANY($1::text[]) AND retracted = FALSE
570
- AND fts @@ websearch_to_tsquery('english', $2)
571
- ORDER BY rank DESC
579
+ const result = await this.db.query(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
580
+ FROM engrams
581
+ WHERE agent_id = ANY($1::text[]) AND retracted = FALSE
582
+ AND fts @@ websearch_to_tsquery('english', $2)
583
+ ORDER BY rank DESC
572
584
  LIMIT $3`, [agentIds, websearchQuery, limit]);
573
585
  return result.rows.map((r) => ({ engram: rowToEngram(r), bm25Score: calibrateBm25(Number(r.rank)) }));
574
586
  }
@@ -649,32 +661,32 @@ export class PGliteEngramStore {
649
661
  sql += ` AND task_status = $${params.length + 1}`;
650
662
  params.push(status);
651
663
  }
652
- sql += ` ORDER BY
653
- CASE task_priority
654
- WHEN 'urgent' THEN 0
655
- WHEN 'high' THEN 1
656
- WHEN 'medium' THEN 2
657
- WHEN 'low' THEN 3
658
- ELSE 4
659
- END,
664
+ sql += ` ORDER BY
665
+ CASE task_priority
666
+ WHEN 'urgent' THEN 0
667
+ WHEN 'high' THEN 1
668
+ WHEN 'medium' THEN 2
669
+ WHEN 'low' THEN 3
670
+ ELSE 4
671
+ END,
660
672
  created_at DESC`;
661
673
  const result = await this.db.query(sql, params);
662
674
  return result.rows.map(rowToEngram);
663
675
  }
664
676
  async getNextTask(agentId) {
665
677
  await this.readyPromise;
666
- const result = await this.db.query(`SELECT * FROM engrams
667
- WHERE agent_id = $1 AND task_status IN ('open', 'in_progress') AND retracted = FALSE
668
- ORDER BY
669
- CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
670
- CASE task_priority
671
- WHEN 'urgent' THEN 0
672
- WHEN 'high' THEN 1
673
- WHEN 'medium' THEN 2
674
- WHEN 'low' THEN 3
675
- ELSE 4
676
- END,
677
- created_at ASC
678
+ const result = await this.db.query(`SELECT * FROM engrams
679
+ WHERE agent_id = $1 AND task_status IN ('open', 'in_progress') AND retracted = FALSE
680
+ ORDER BY
681
+ CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
682
+ CASE task_priority
683
+ WHEN 'urgent' THEN 0
684
+ WHEN 'high' THEN 1
685
+ WHEN 'medium' THEN 2
686
+ WHEN 'low' THEN 3
687
+ ELSE 4
688
+ END,
689
+ created_at ASC
678
690
  LIMIT 1`, [agentId]);
679
691
  return result.rows.length > 0 ? rowToEngram(result.rows[0]) : null;
680
692
  }
@@ -688,11 +700,11 @@ export class PGliteEngramStore {
688
700
  }
689
701
  async findActiveMatchByConcept(agentId, concept, requiredTags) {
690
702
  await this.readyPromise;
691
- let sql = `SELECT * FROM engrams
692
- WHERE agent_id = $1
693
- AND LOWER(TRIM(concept)) = LOWER(TRIM($2))
694
- AND stage = 'active'
695
- AND retracted = FALSE
703
+ let sql = `SELECT * FROM engrams
704
+ WHERE agent_id = $1
705
+ AND LOWER(TRIM(concept)) = LOWER(TRIM($2))
706
+ AND stage = 'active'
707
+ AND retracted = FALSE
696
708
  AND superseded_by IS NULL`;
697
709
  const params = [agentId, concept];
698
710
  if (requiredTags && requiredTags.length > 0) {
@@ -729,12 +741,12 @@ export class PGliteEngramStore {
729
741
  await this.readyPromise;
730
742
  const id = randomUUID();
731
743
  const now = new Date().toISOString();
732
- await this.db.query(`INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
733
- VALUES ($1, $2, $3, $4, $5, $6, 0, $7, $7)
734
- ON CONFLICT (from_engram_id, to_engram_id) DO UPDATE SET
735
- weight = EXCLUDED.weight,
736
- confidence = EXCLUDED.confidence,
737
- last_activated = EXCLUDED.last_activated,
744
+ await this.db.query(`INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
745
+ VALUES ($1, $2, $3, $4, $5, $6, 0, $7, $7)
746
+ ON CONFLICT (from_engram_id, to_engram_id) DO UPDATE SET
747
+ weight = EXCLUDED.weight,
748
+ confidence = EXCLUDED.confidence,
749
+ last_activated = EXCLUDED.last_activated,
738
750
  activation_count = associations.activation_count + 1`, [id, fromId, toId, weight, confidence, type, now]);
739
751
  const assoc = await this.getAssociation(fromId, toId);
740
752
  if (!assoc)
@@ -756,12 +768,12 @@ export class PGliteEngramStore {
756
768
  if (engramIds.length === 0)
757
769
  return result;
758
770
  await this.readyPromise;
759
- const r = await this.db.query(`SELECT id, SUM(cnt) AS count, SUM(sw) AS sum_weight FROM (
760
- SELECT from_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE from_engram_id = ANY($1::text[])
761
- UNION ALL
762
- SELECT to_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE to_engram_id = ANY($1::text[])
763
- ) t
764
- WHERE id = ANY($1::text[])
771
+ const r = await this.db.query(`SELECT id, SUM(cnt) AS count, SUM(sw) AS sum_weight FROM (
772
+ SELECT from_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE from_engram_id = ANY($1::text[])
773
+ UNION ALL
774
+ SELECT to_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE to_engram_id = ANY($1::text[])
775
+ ) t
776
+ WHERE id = ANY($1::text[])
765
777
  GROUP BY id`, [engramIds]);
766
778
  for (const row of r.rows) {
767
779
  result.set(row.id, { count: Number(row.count), sumWeight: Number(row.sum_weight) });
@@ -777,7 +789,7 @@ export class PGliteEngramStore {
777
789
  if (engramIds.length === 0)
778
790
  return result;
779
791
  await this.readyPromise;
780
- const r = await this.db.query(`SELECT * FROM associations
792
+ const r = await this.db.query(`SELECT * FROM associations
781
793
  WHERE from_engram_id = ANY($1::text[]) OR to_engram_id = ANY($1::text[])`, [engramIds]);
782
794
  for (const row of r.rows) {
783
795
  const a = rowToAssociation(row);
@@ -817,8 +829,8 @@ export class PGliteEngramStore {
817
829
  }
818
830
  async getAllAssociations(agentId) {
819
831
  await this.readyPromise;
820
- const result = await this.db.query(`SELECT a.* FROM associations a
821
- JOIN engrams e ON a.from_engram_id = e.id
832
+ const result = await this.db.query(`SELECT a.* FROM associations a
833
+ JOIN engrams e ON a.from_engram_id = e.id
822
834
  WHERE e.agent_id = $1`, [agentId]);
823
835
  return result.rows.map(rowToAssociation);
824
836
  }
@@ -827,11 +839,11 @@ export class PGliteEngramStore {
827
839
  // ============================================================
828
840
  async getEvictionCandidates(agentId, limit) {
829
841
  await this.readyPromise;
830
- const result = await this.db.query(`SELECT * FROM engrams
831
- WHERE agent_id = $1 AND stage = 'active' AND retracted = FALSE
832
- ORDER BY (salience * 0.3 + confidence * 0.3
833
- + (access_count::float / (access_count + 5)) * 0.2
834
- + (1.0 / (1.0 + EXTRACT(EPOCH FROM (now() - last_accessed::timestamptz)) / 86400.0)) * 0.2) ASC
842
+ const result = await this.db.query(`SELECT * FROM engrams
843
+ WHERE agent_id = $1 AND stage = 'active' AND retracted = FALSE
844
+ ORDER BY (salience * 0.3 + confidence * 0.3
845
+ + (access_count::float / (access_count + 5)) * 0.2
846
+ + (1.0 / (1.0 + EXTRACT(EPOCH FROM (now() - last_accessed::timestamptz)) / 86400.0)) * 0.2) ASC
835
847
  LIMIT $2`, [agentId, limit]);
836
848
  return result.rows.map(rowToEngram);
837
849
  }
@@ -866,7 +878,7 @@ export class PGliteEngramStore {
866
878
  }
867
879
  async logStagingEvent(event) {
868
880
  await this.readyPromise;
869
- await this.db.query(`INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
881
+ await this.db.query(`INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
870
882
  VALUES ($1, $2, $3, $4, $5, $6)`, [
871
883
  event.engramId, event.agentId, event.action,
872
884
  event.resonanceScore, event.timestamp.toISOString(), event.ageMs,
@@ -874,18 +886,18 @@ export class PGliteEngramStore {
874
886
  }
875
887
  async logRetrievalFeedback(activationEventId, engramId, useful, context) {
876
888
  await this.readyPromise;
877
- await this.db.query(`INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
889
+ await this.db.query(`INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
878
890
  VALUES ($1, $2, $3, $4, $5, $6)`, [randomUUID(), activationEventId, engramId, useful, context, new Date().toISOString()]);
879
891
  }
880
892
  async getRetrievalPrecision(agentId, windowHours = 24) {
881
893
  await this.readyPromise;
882
894
  const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
883
- const result = await this.db.query(`SELECT
884
- COUNT(CASE WHEN useful = TRUE THEN 1 END) AS useful_count,
885
- COUNT(*) AS total_count
886
- FROM retrieval_feedback rf
887
- LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
888
- JOIN engrams e ON rf.engram_id = e.id
895
+ const result = await this.db.query(`SELECT
896
+ COUNT(CASE WHEN useful = TRUE THEN 1 END) AS useful_count,
897
+ COUNT(*) AS total_count
898
+ FROM retrieval_feedback rf
899
+ LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
900
+ JOIN engrams e ON rf.engram_id = e.id
889
901
  WHERE e.agent_id = $1 AND rf.timestamp > $2`, [agentId, since]);
890
902
  const row = result.rows[0];
891
903
  const total = Number(row?.total_count ?? 0);
@@ -894,10 +906,10 @@ export class PGliteEngramStore {
894
906
  }
895
907
  async getStagingMetrics(agentId) {
896
908
  await this.readyPromise;
897
- const result = await this.db.query(`SELECT
898
- COUNT(CASE WHEN action = 'promoted' THEN 1 END) AS promoted,
899
- COUNT(CASE WHEN action = 'discarded' THEN 1 END) AS discarded,
900
- COUNT(CASE WHEN action = 'expired' THEN 1 END) AS expired
909
+ const result = await this.db.query(`SELECT
910
+ COUNT(CASE WHEN action = 'promoted' THEN 1 END) AS promoted,
911
+ COUNT(CASE WHEN action = 'discarded' THEN 1 END) AS discarded,
912
+ COUNT(CASE WHEN action = 'expired' THEN 1 END) AS expired
901
913
  FROM staging_events WHERE agent_id = $1`, [agentId]);
902
914
  const row = result.rows[0] ?? { promoted: 0, discarded: 0, expired: 0 };
903
915
  return {
@@ -911,8 +923,8 @@ export class PGliteEngramStore {
911
923
  // Flush any buffered activation events so stats reflect the latest writes.
912
924
  await this.flushActivationEvents();
913
925
  const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
914
- const result = await this.db.query(`SELECT latency_ms FROM activation_events
915
- WHERE agent_id = $1 AND timestamp > $2
926
+ const result = await this.db.query(`SELECT latency_ms FROM activation_events
927
+ WHERE agent_id = $1 AND timestamp > $2
916
928
  ORDER BY latency_ms ASC`, [agentId, since]);
917
929
  if (result.rows.length === 0)
918
930
  return { count: 0, avgLatencyMs: 0, p50LatencyMs: 0, p90LatencyMs: 0, p95LatencyMs: 0 };
@@ -932,9 +944,9 @@ export class PGliteEngramStore {
932
944
  async getLinkedFeedbackStats(agentId, windowHours = 24 * 7) {
933
945
  await this.readyPromise;
934
946
  const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
935
- const result = await this.db.query(`SELECT COUNT(*) AS total, COUNT(CASE WHEN rf.useful = TRUE THEN 1 END) AS useful
936
- FROM retrieval_feedback rf
937
- JOIN activation_events ae ON ae.id = rf.activation_event_id
947
+ const result = await this.db.query(`SELECT COUNT(*) AS total, COUNT(CASE WHEN rf.useful = TRUE THEN 1 END) AS useful
948
+ FROM retrieval_feedback rf
949
+ JOIN activation_events ae ON ae.id = rf.activation_event_id
938
950
  WHERE ae.agent_id = $1 AND rf.timestamp > $2`, [agentId, since]);
939
951
  const row = result.rows[0] ?? { total: 0, useful: 0 };
940
952
  return { total: Number(row.total), useful: Number(row.useful) };
@@ -951,7 +963,7 @@ export class PGliteEngramStore {
951
963
  await this.readyPromise;
952
964
  const id = randomUUID();
953
965
  const now = new Date().toISOString();
954
- await this.db.query(`INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
966
+ await this.db.query(`INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
955
967
  VALUES ($1, $2, $3, $4::vector, 0, $5, $5, $5)`, [id, input.agentId, input.label, vectorToLiteral(input.embedding ?? null), now]);
956
968
  const ep = await this.getEpisode(id);
957
969
  if (!ep)
@@ -977,9 +989,9 @@ export class PGliteEngramStore {
977
989
  async addEngramToEpisode(engramId, episodeId) {
978
990
  await this.readyPromise;
979
991
  await this.db.query(`UPDATE engrams SET episode_id = $1 WHERE id = $2`, [episodeId, engramId]);
980
- await this.db.query(`UPDATE episodes SET
981
- engram_count = engram_count + 1,
982
- end_time = GREATEST(end_time, $1)
992
+ await this.db.query(`UPDATE episodes SET
993
+ engram_count = engram_count + 1,
994
+ end_time = GREATEST(end_time, $1)
983
995
  WHERE id = $2`, [new Date().toISOString(), episodeId]);
984
996
  }
985
997
  async getEngramsByEpisode(episodeId) {
@@ -1018,44 +1030,44 @@ export class PGliteEngramStore {
1018
1030
  async updateAutoCheckpointWrite(agentId, engramId) {
1019
1031
  await this.readyPromise;
1020
1032
  const now = new Date().toISOString();
1021
- await this.db.query(`INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
1022
- VALUES ($1, $2, $3, 1, $3)
1023
- ON CONFLICT(agent_id) DO UPDATE SET
1024
- last_write_id = EXCLUDED.last_write_id,
1025
- last_activity_at = EXCLUDED.last_activity_at,
1026
- write_count_since_consolidation = conscious_state.write_count_since_consolidation + 1,
1033
+ await this.db.query(`INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
1034
+ VALUES ($1, $2, $3, 1, $3)
1035
+ ON CONFLICT(agent_id) DO UPDATE SET
1036
+ last_write_id = EXCLUDED.last_write_id,
1037
+ last_activity_at = EXCLUDED.last_activity_at,
1038
+ write_count_since_consolidation = conscious_state.write_count_since_consolidation + 1,
1027
1039
  updated_at = EXCLUDED.updated_at`, [agentId, engramId, now]);
1028
1040
  }
1029
1041
  async updateAutoCheckpointRecall(agentId, context, engramIds) {
1030
1042
  await this.readyPromise;
1031
1043
  const now = new Date().toISOString();
1032
- 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)
1033
- VALUES ($1, $2, $3, $4, 1, $4)
1034
- ON CONFLICT(agent_id) DO UPDATE SET
1035
- last_recall_context = EXCLUDED.last_recall_context,
1036
- last_recall_ids = EXCLUDED.last_recall_ids,
1037
- last_activity_at = EXCLUDED.last_activity_at,
1038
- recall_count_since_consolidation = conscious_state.recall_count_since_consolidation + 1,
1044
+ 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)
1045
+ VALUES ($1, $2, $3, $4, 1, $4)
1046
+ ON CONFLICT(agent_id) DO UPDATE SET
1047
+ last_recall_context = EXCLUDED.last_recall_context,
1048
+ last_recall_ids = EXCLUDED.last_recall_ids,
1049
+ last_activity_at = EXCLUDED.last_activity_at,
1050
+ recall_count_since_consolidation = conscious_state.recall_count_since_consolidation + 1,
1039
1051
  updated_at = EXCLUDED.updated_at`, [agentId, context, JSON.stringify(engramIds), now]);
1040
1052
  }
1041
1053
  async touchActivity(agentId) {
1042
1054
  await this.readyPromise;
1043
1055
  const now = new Date().toISOString();
1044
- await this.db.query(`INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
1045
- VALUES ($1, $2, $2)
1046
- ON CONFLICT(agent_id) DO UPDATE SET
1047
- last_activity_at = EXCLUDED.last_activity_at,
1056
+ await this.db.query(`INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
1057
+ VALUES ($1, $2, $2)
1058
+ ON CONFLICT(agent_id) DO UPDATE SET
1059
+ last_activity_at = EXCLUDED.last_activity_at,
1048
1060
  updated_at = EXCLUDED.updated_at`, [agentId, now]);
1049
1061
  }
1050
1062
  async saveCheckpoint(agentId, state) {
1051
1063
  await this.readyPromise;
1052
1064
  const now = new Date().toISOString();
1053
- await this.db.query(`INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
1054
- VALUES ($1, $2, $3, $3, $3)
1055
- ON CONFLICT(agent_id) DO UPDATE SET
1056
- execution_state = EXCLUDED.execution_state,
1057
- checkpoint_at = EXCLUDED.checkpoint_at,
1058
- last_activity_at = EXCLUDED.last_activity_at,
1065
+ await this.db.query(`INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
1066
+ VALUES ($1, $2, $3, $3, $3)
1067
+ ON CONFLICT(agent_id) DO UPDATE SET
1068
+ execution_state = EXCLUDED.execution_state,
1069
+ checkpoint_at = EXCLUDED.checkpoint_at,
1070
+ last_activity_at = EXCLUDED.last_activity_at,
1059
1071
  updated_at = EXCLUDED.updated_at`, [agentId, JSON.stringify(state), now]);
1060
1072
  }
1061
1073
  /** Distinct agent ids present in this store (D3 whoami; includes all stages). */
@@ -1074,9 +1086,9 @@ export class PGliteEngramStore {
1074
1086
  const t = term.toLowerCase().replace(/[%_]/g, '');
1075
1087
  if (!t)
1076
1088
  return [];
1077
- const res = await this.db.query(`SELECT DISTINCT entity FROM (
1078
- SELECT entity FROM entity_mentions WHERE entity LIKE $1
1079
- UNION SELECT entity FROM entity_aliases WHERE alias LIKE $1
1089
+ const res = await this.db.query(`SELECT DISTINCT entity FROM (
1090
+ SELECT entity FROM entity_mentions WHERE entity LIKE $1
1091
+ UNION SELECT entity FROM entity_aliases WHERE alias LIKE $1
1080
1092
  ) u LIMIT $2`, [`%${t}%`, limit]);
1081
1093
  return res.rows.map(r => r.entity);
1082
1094
  }
@@ -1118,13 +1130,13 @@ export class PGliteEngramStore {
1118
1130
  await this.db.query(`UPDATE conscious_state SET last_mini_consolidation_at = $1, updated_at = $1 WHERE agent_id = $2`, [now, agentId]);
1119
1131
  }
1120
1132
  else {
1121
- await this.db.query(`UPDATE conscious_state SET
1122
- last_consolidation_at = $1,
1123
- last_mini_consolidation_at = $1,
1124
- write_count_since_consolidation = 0,
1125
- recall_count_since_consolidation = 0,
1126
- consolidation_cycle_count = consolidation_cycle_count + 1,
1127
- updated_at = $1
1133
+ await this.db.query(`UPDATE conscious_state SET
1134
+ last_consolidation_at = $1,
1135
+ last_mini_consolidation_at = $1,
1136
+ write_count_since_consolidation = 0,
1137
+ recall_count_since_consolidation = 0,
1138
+ consolidation_cycle_count = consolidation_cycle_count + 1,
1139
+ updated_at = $1
1128
1140
  WHERE agent_id = $2`, [now, agentId]);
1129
1141
  }
1130
1142
  }
@@ -1149,10 +1161,10 @@ export class PGliteEngramStore {
1149
1161
  // ============================================================
1150
1162
  async getLatestByTag(opts) {
1151
1163
  await this.readyPromise;
1152
- let sql = `SELECT * FROM engrams
1153
- WHERE agent_id = $1
1154
- AND retracted = $2
1155
- AND stage = 'active'
1164
+ let sql = `SELECT * FROM engrams
1165
+ WHERE agent_id = $1
1166
+ AND retracted = $2
1167
+ AND stage = 'active'
1156
1168
  AND tags LIKE $3`;
1157
1169
  const params = [opts.agentId, opts.retracted ?? false, `%"${opts.tagKeyPrefix}%`];
1158
1170
  if (opts.scopeTagsAll && opts.scopeTagsAll.length > 0) {
@@ -1179,10 +1191,10 @@ export class PGliteEngramStore {
1179
1191
  }
1180
1192
  async getTopBy(opts) {
1181
1193
  await this.readyPromise;
1182
- let sql = `SELECT * FROM engrams
1183
- WHERE agent_id = $1
1184
- AND retracted = $2
1185
- AND stage = 'active'
1194
+ let sql = `SELECT * FROM engrams
1195
+ WHERE agent_id = $1
1196
+ AND retracted = $2
1197
+ AND stage = 'active'
1186
1198
  AND tags LIKE $3`;
1187
1199
  const params = [opts.agentId, opts.retracted ?? false, `%"${opts.sortField}%`];
1188
1200
  if (opts.filterTagsAll && opts.filterTagsAll.length > 0) {