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
@@ -120,6 +120,7 @@ function rowToEngram(row) {
120
120
  recipeId: row.recipe_id ?? null,
121
121
  validFrom: row.valid_from ?? null,
122
122
  validTo: row.valid_to ?? null,
123
+ stageChangedAt: row.stage_changed_at ?? null,
123
124
  };
124
125
  }
125
126
  function rowToAssociation(row) {
@@ -279,7 +280,7 @@ export class PostgresEngramStore {
279
280
  // ALWAYS the pool, never this.q(): this background flush is fire-and-forget and must
280
281
  // not route to a transaction's client (a future caller that logs an activation inside a
281
282
  // withTransaction would otherwise collide with the tx's in-flight query on one pg client).
282
- await this.pool.query(`INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
283
+ await this.pool.query(`INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
283
284
  VALUES ${values.join(',')}`, params);
284
285
  }
285
286
  catch {
@@ -326,20 +327,20 @@ export class PostgresEngramStore {
326
327
  await this.readyPromise;
327
328
  const id = input.id ?? randomUUID();
328
329
  const now = new Date().toISOString();
329
- await this.q(`INSERT INTO engrams (
330
- id, agent_id, concept, content, embedding, embedding_model,
331
- confidence, salience, access_count, last_accessed, created_at,
332
- salience_features, reason_codes, stage, ttl, retracted,
333
- tags, memory_type, memory_class, supersedes, episode_id,
334
- task_status, task_priority, blocked_by, sequence, references_json,
335
- origin_class, writer_session, recipe_id, valid_from, valid_to
336
- ) VALUES (
337
- $1, $2, $3, $4, $5::vector, $6,
338
- $7, $8, 0, $9, $10,
339
- $11, $12, 'active', $13, FALSE,
340
- $14, $15, $16, $17, $18,
341
- $19, $20, $21, $22, $23,
342
- $24, $25, $26, $27, $28
330
+ await this.q(`INSERT INTO engrams (
331
+ id, agent_id, concept, content, embedding, embedding_model,
332
+ confidence, salience, access_count, last_accessed, created_at,
333
+ salience_features, reason_codes, stage, ttl, retracted,
334
+ tags, memory_type, memory_class, supersedes, episode_id,
335
+ task_status, task_priority, blocked_by, sequence, references_json,
336
+ origin_class, writer_session, recipe_id, valid_from, valid_to
337
+ ) VALUES (
338
+ $1, $2, $3, $4, $5::vector, $6,
339
+ $7, $8, 0, $9, $10,
340
+ $11, $12, 'active', $13, FALSE,
341
+ $14, $15, $16, $17, $18,
342
+ $19, $20, $21, $22, $23,
343
+ $24, $25, $26, $27, $28
343
344
  )`, [
344
345
  id,
345
346
  input.agentId,
@@ -491,11 +492,11 @@ export class PostgresEngramStore {
491
492
  await this.readyPromise;
492
493
  const rows = [];
493
494
  for (const agentId of agentIds) {
494
- const result = await this.q(`
495
- SELECT tags, access_count AS "accessCount", id FROM engrams
496
- WHERE agent_id = $1 AND stage = $2 AND retracted = FALSE
497
- ORDER BY access_count DESC, id
498
- LIMIT $3
495
+ const result = await this.q(`
496
+ SELECT tags, access_count AS "accessCount", id FROM engrams
497
+ WHERE agent_id = $1 AND stage = $2 AND retracted = FALSE
498
+ ORDER BY access_count DESC, id
499
+ LIMIT $3
499
500
  `, [agentId, stage, topN]);
500
501
  rows.push(...result.rows);
501
502
  }
@@ -535,15 +536,15 @@ export class PostgresEngramStore {
535
536
  }
536
537
  async touchEngram(id) {
537
538
  await this.readyPromise;
538
- await this.q(`UPDATE engrams
539
- SET access_count = access_count + 1,
540
- last_accessed = $1,
541
- confidence = LEAST(0.85, confidence + 0.02 / (1.0 + sqrt(access_count::float)))
539
+ await this.q(`UPDATE engrams
540
+ SET access_count = access_count + 1,
541
+ last_accessed = $1,
542
+ confidence = LEAST(0.85, confidence + 0.02 / (1.0 + sqrt(access_count::float)))
542
543
  WHERE id = $2`, [new Date().toISOString(), id]);
543
544
  }
544
545
  async updateStage(id, stage) {
545
546
  await this.readyPromise;
546
- await this.q(`UPDATE engrams SET stage = $1 WHERE id = $2`, [stage, id]);
547
+ await this.q(`UPDATE engrams SET stage = $1, stage_changed_at = $2 WHERE id = $3`, [stage, new Date().toISOString(), id]);
547
548
  }
548
549
  /**
549
550
  * Replace an engram's content. Used by the fade phase of consolidation
@@ -573,9 +574,20 @@ export class PostgresEngramStore {
573
574
  await this.readyPromise;
574
575
  await this.q(`UPDATE engrams SET retracted = TRUE, retracted_by = $1, retracted_at = $2 WHERE id = $3`, [retractedBy, new Date().toISOString(), id]);
575
576
  }
577
+ /**
578
+ * Delete an engram and the rows that point at it. Parity with the SQLite backend —
579
+ * see src/storage/sqlite.ts deleteEngram for the orphan counts that prompted this.
580
+ * activation_events is left alone on purpose: it records a recall that happened.
581
+ */
576
582
  async deleteEngram(id) {
577
583
  await this.readyPromise;
578
- await this.q(`DELETE FROM engrams WHERE id = $1`, [id]);
584
+ // One statement: a data-modifying CTE runs atomically, so a failure cannot leave the
585
+ // engram alive with its dependents gone. withTransaction is not re-entrant, and this
586
+ // can be called inside one (/memory/write-batch), so it does not open its own.
587
+ await this.q(`WITH f AS (DELETE FROM retrieval_feedback WHERE engram_id = $1),
588
+ m AS (DELETE FROM entity_mentions WHERE engram_id = $1),
589
+ s AS (DELETE FROM staging_events WHERE engram_id = $1)
590
+ DELETE FROM engrams WHERE id = $1`, [id]);
579
591
  }
580
592
  /**
581
593
  * Time warp - shift all timestamps backward by ms milliseconds.
@@ -584,9 +596,9 @@ export class PostgresEngramStore {
584
596
  async timeWarp(agentId, ms) {
585
597
  await this.readyPromise;
586
598
  const seconds = Math.round(ms / 1000);
587
- const r1 = await this.q(`UPDATE engrams SET
588
- created_at = to_char(($1::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
589
- last_accessed = to_char(($3::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
599
+ const r1 = await this.q(`UPDATE engrams SET
600
+ created_at = to_char(($1::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
601
+ last_accessed = to_char(($3::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
590
602
  WHERE agent_id = $4`, ['now', seconds, 'now', agentId]);
591
603
  // node-postgres reports the affected-row count as `rowCount` (PGlite called it
592
604
  // `affectedRows` — the one result-shape field that differs between the drivers).
@@ -616,13 +628,13 @@ export class PostgresEngramStore {
616
628
  // Restrict to active + fading. Faded engrams (Paper 1: storage degradation)
617
629
  // retain their embedding so they still participate in semantic recall, even
618
630
  // though their content has been trimmed. Excludes staging/consolidated/archived.
619
- const result = await this.q(`SELECT *, (embedding <=> $2::vector) AS distance
620
- FROM engrams
621
- WHERE agent_id = $1
622
- AND embedding IS NOT NULL
623
- AND retracted = FALSE
624
- AND stage IN ('active', 'fading')
625
- ORDER BY distance ASC
631
+ const result = await this.q(`SELECT *, (embedding <=> $2::vector) AS distance
632
+ FROM engrams
633
+ WHERE agent_id = $1
634
+ AND embedding IS NOT NULL
635
+ AND retracted = FALSE
636
+ AND stage IN ('active', 'fading')
637
+ ORDER BY distance ASC
626
638
  LIMIT $3`, [agentId, vectorToLiteral(vec), limit]);
627
639
  return result.rows.map((r) => ({ engram: rowToEngram(r), distance: r.distance }));
628
640
  }
@@ -641,11 +653,11 @@ export class PostgresEngramStore {
641
653
  if (tokens.length === 0)
642
654
  return [];
643
655
  const websearchQuery = tokens.join(' OR ');
644
- const result = await this.q(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
645
- FROM engrams
646
- WHERE agent_id = $1 AND retracted = FALSE
647
- AND fts @@ websearch_to_tsquery('english', $2)
648
- ORDER BY rank DESC
656
+ const result = await this.q(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
657
+ FROM engrams
658
+ WHERE agent_id = $1 AND retracted = FALSE
659
+ AND fts @@ websearch_to_tsquery('english', $2)
660
+ ORDER BY rank DESC
649
661
  LIMIT $3`, [agentId, websearchQuery, limit]);
650
662
  return result.rows.map((r) => ({ engram: rowToEngram(r), bm25Score: calibrateBm25(Number(r.rank)) }));
651
663
  }
@@ -659,11 +671,11 @@ export class PostgresEngramStore {
659
671
  if (tokens.length === 0)
660
672
  return [];
661
673
  const websearchQuery = tokens.join(' OR ');
662
- const result = await this.q(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
663
- FROM engrams
664
- WHERE agent_id = ANY($1::text[]) AND retracted = FALSE
665
- AND fts @@ websearch_to_tsquery('english', $2)
666
- ORDER BY rank DESC
674
+ const result = await this.q(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
675
+ FROM engrams
676
+ WHERE agent_id = ANY($1::text[]) AND retracted = FALSE
677
+ AND fts @@ websearch_to_tsquery('english', $2)
678
+ ORDER BY rank DESC
667
679
  LIMIT $3`, [agentIds, websearchQuery, limit]);
668
680
  return result.rows.map((r) => ({ engram: rowToEngram(r), bm25Score: calibrateBm25(Number(r.rank)) }));
669
681
  }
@@ -744,32 +756,32 @@ export class PostgresEngramStore {
744
756
  sql += ` AND task_status = $${params.length + 1}`;
745
757
  params.push(status);
746
758
  }
747
- sql += ` ORDER BY
748
- CASE task_priority
749
- WHEN 'urgent' THEN 0
750
- WHEN 'high' THEN 1
751
- WHEN 'medium' THEN 2
752
- WHEN 'low' THEN 3
753
- ELSE 4
754
- END,
759
+ sql += ` ORDER BY
760
+ CASE task_priority
761
+ WHEN 'urgent' THEN 0
762
+ WHEN 'high' THEN 1
763
+ WHEN 'medium' THEN 2
764
+ WHEN 'low' THEN 3
765
+ ELSE 4
766
+ END,
755
767
  created_at DESC`;
756
768
  const result = await this.q(sql, params);
757
769
  return result.rows.map(rowToEngram);
758
770
  }
759
771
  async getNextTask(agentId) {
760
772
  await this.readyPromise;
761
- const result = await this.q(`SELECT * FROM engrams
762
- WHERE agent_id = $1 AND task_status IN ('open', 'in_progress') AND retracted = FALSE
763
- ORDER BY
764
- CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
765
- CASE task_priority
766
- WHEN 'urgent' THEN 0
767
- WHEN 'high' THEN 1
768
- WHEN 'medium' THEN 2
769
- WHEN 'low' THEN 3
770
- ELSE 4
771
- END,
772
- created_at ASC
773
+ const result = await this.q(`SELECT * FROM engrams
774
+ WHERE agent_id = $1 AND task_status IN ('open', 'in_progress') AND retracted = FALSE
775
+ ORDER BY
776
+ CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
777
+ CASE task_priority
778
+ WHEN 'urgent' THEN 0
779
+ WHEN 'high' THEN 1
780
+ WHEN 'medium' THEN 2
781
+ WHEN 'low' THEN 3
782
+ ELSE 4
783
+ END,
784
+ created_at ASC
773
785
  LIMIT 1`, [agentId]);
774
786
  return result.rows.length > 0 ? rowToEngram(result.rows[0]) : null;
775
787
  }
@@ -783,11 +795,11 @@ export class PostgresEngramStore {
783
795
  }
784
796
  async findActiveMatchByConcept(agentId, concept, requiredTags) {
785
797
  await this.readyPromise;
786
- let sql = `SELECT * FROM engrams
787
- WHERE agent_id = $1
788
- AND LOWER(TRIM(concept)) = LOWER(TRIM($2))
789
- AND stage = 'active'
790
- AND retracted = FALSE
798
+ let sql = `SELECT * FROM engrams
799
+ WHERE agent_id = $1
800
+ AND LOWER(TRIM(concept)) = LOWER(TRIM($2))
801
+ AND stage = 'active'
802
+ AND retracted = FALSE
791
803
  AND superseded_by IS NULL`;
792
804
  const params = [agentId, concept];
793
805
  if (requiredTags && requiredTags.length > 0) {
@@ -824,12 +836,12 @@ export class PostgresEngramStore {
824
836
  await this.readyPromise;
825
837
  const id = randomUUID();
826
838
  const now = new Date().toISOString();
827
- await this.q(`INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
828
- VALUES ($1, $2, $3, $4, $5, $6, 0, $7, $7)
829
- ON CONFLICT (from_engram_id, to_engram_id) DO UPDATE SET
830
- weight = EXCLUDED.weight,
831
- confidence = EXCLUDED.confidence,
832
- last_activated = EXCLUDED.last_activated,
839
+ await this.q(`INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
840
+ VALUES ($1, $2, $3, $4, $5, $6, 0, $7, $7)
841
+ ON CONFLICT (from_engram_id, to_engram_id) DO UPDATE SET
842
+ weight = EXCLUDED.weight,
843
+ confidence = EXCLUDED.confidence,
844
+ last_activated = EXCLUDED.last_activated,
833
845
  activation_count = associations.activation_count + 1`, [id, fromId, toId, weight, confidence, type, now]);
834
846
  const assoc = await this.getAssociation(fromId, toId);
835
847
  if (!assoc)
@@ -851,12 +863,12 @@ export class PostgresEngramStore {
851
863
  if (engramIds.length === 0)
852
864
  return result;
853
865
  await this.readyPromise;
854
- const r = await this.q(`SELECT id, SUM(cnt) AS count, SUM(sw) AS sum_weight FROM (
855
- SELECT from_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE from_engram_id = ANY($1::text[])
856
- UNION ALL
857
- SELECT to_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE to_engram_id = ANY($1::text[])
858
- ) t
859
- WHERE id = ANY($1::text[])
866
+ const r = await this.q(`SELECT id, SUM(cnt) AS count, SUM(sw) AS sum_weight FROM (
867
+ SELECT from_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE from_engram_id = ANY($1::text[])
868
+ UNION ALL
869
+ SELECT to_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE to_engram_id = ANY($1::text[])
870
+ ) t
871
+ WHERE id = ANY($1::text[])
860
872
  GROUP BY id`, [engramIds]);
861
873
  for (const row of r.rows) {
862
874
  result.set(row.id, { count: Number(row.count), sumWeight: Number(row.sum_weight) });
@@ -872,7 +884,7 @@ export class PostgresEngramStore {
872
884
  if (engramIds.length === 0)
873
885
  return result;
874
886
  await this.readyPromise;
875
- const r = await this.q(`SELECT * FROM associations
887
+ const r = await this.q(`SELECT * FROM associations
876
888
  WHERE from_engram_id = ANY($1::text[]) OR to_engram_id = ANY($1::text[])`, [engramIds]);
877
889
  for (const row of r.rows) {
878
890
  const a = rowToAssociation(row);
@@ -912,8 +924,8 @@ export class PostgresEngramStore {
912
924
  }
913
925
  async getAllAssociations(agentId) {
914
926
  await this.readyPromise;
915
- const result = await this.q(`SELECT a.* FROM associations a
916
- JOIN engrams e ON a.from_engram_id = e.id
927
+ const result = await this.q(`SELECT a.* FROM associations a
928
+ JOIN engrams e ON a.from_engram_id = e.id
917
929
  WHERE e.agent_id = $1`, [agentId]);
918
930
  return result.rows.map(rowToAssociation);
919
931
  }
@@ -922,11 +934,11 @@ export class PostgresEngramStore {
922
934
  // ============================================================
923
935
  async getEvictionCandidates(agentId, limit) {
924
936
  await this.readyPromise;
925
- const result = await this.q(`SELECT * FROM engrams
926
- WHERE agent_id = $1 AND stage = 'active' AND retracted = FALSE
927
- ORDER BY (salience * 0.3 + confidence * 0.3
928
- + (access_count::float / (access_count + 5)) * 0.2
929
- + (1.0 / (1.0 + EXTRACT(EPOCH FROM (now() - last_accessed::timestamptz)) / 86400.0)) * 0.2) ASC
937
+ const result = await this.q(`SELECT * FROM engrams
938
+ WHERE agent_id = $1 AND stage = 'active' AND retracted = FALSE
939
+ ORDER BY (salience * 0.3 + confidence * 0.3
940
+ + (access_count::float / (access_count + 5)) * 0.2
941
+ + (1.0 / (1.0 + EXTRACT(EPOCH FROM (now() - last_accessed::timestamptz)) / 86400.0)) * 0.2) ASC
930
942
  LIMIT $2`, [agentId, limit]);
931
943
  return result.rows.map(rowToEngram);
932
944
  }
@@ -961,7 +973,7 @@ export class PostgresEngramStore {
961
973
  }
962
974
  async logStagingEvent(event) {
963
975
  await this.readyPromise;
964
- await this.q(`INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
976
+ await this.q(`INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
965
977
  VALUES ($1, $2, $3, $4, $5, $6)`, [
966
978
  event.engramId, event.agentId, event.action,
967
979
  event.resonanceScore, event.timestamp.toISOString(), event.ageMs,
@@ -969,18 +981,18 @@ export class PostgresEngramStore {
969
981
  }
970
982
  async logRetrievalFeedback(activationEventId, engramId, useful, context) {
971
983
  await this.readyPromise;
972
- await this.q(`INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
984
+ await this.q(`INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
973
985
  VALUES ($1, $2, $3, $4, $5, $6)`, [randomUUID(), activationEventId, engramId, useful, context, new Date().toISOString()]);
974
986
  }
975
987
  async getRetrievalPrecision(agentId, windowHours = 24) {
976
988
  await this.readyPromise;
977
989
  const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
978
- const result = await this.q(`SELECT
979
- COUNT(CASE WHEN useful = TRUE THEN 1 END) AS useful_count,
980
- COUNT(*) AS total_count
981
- FROM retrieval_feedback rf
982
- LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
983
- JOIN engrams e ON rf.engram_id = e.id
990
+ const result = await this.q(`SELECT
991
+ COUNT(CASE WHEN useful = TRUE THEN 1 END) AS useful_count,
992
+ COUNT(*) AS total_count
993
+ FROM retrieval_feedback rf
994
+ LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
995
+ JOIN engrams e ON rf.engram_id = e.id
984
996
  WHERE e.agent_id = $1 AND rf.timestamp > $2`, [agentId, since]);
985
997
  const row = result.rows[0];
986
998
  const total = Number(row?.total_count ?? 0);
@@ -989,10 +1001,10 @@ export class PostgresEngramStore {
989
1001
  }
990
1002
  async getStagingMetrics(agentId) {
991
1003
  await this.readyPromise;
992
- const result = await this.q(`SELECT
993
- COUNT(CASE WHEN action = 'promoted' THEN 1 END) AS promoted,
994
- COUNT(CASE WHEN action = 'discarded' THEN 1 END) AS discarded,
995
- COUNT(CASE WHEN action = 'expired' THEN 1 END) AS expired
1004
+ const result = await this.q(`SELECT
1005
+ COUNT(CASE WHEN action = 'promoted' THEN 1 END) AS promoted,
1006
+ COUNT(CASE WHEN action = 'discarded' THEN 1 END) AS discarded,
1007
+ COUNT(CASE WHEN action = 'expired' THEN 1 END) AS expired
996
1008
  FROM staging_events WHERE agent_id = $1`, [agentId]);
997
1009
  const row = result.rows[0] ?? { promoted: 0, discarded: 0, expired: 0 };
998
1010
  return {
@@ -1006,8 +1018,8 @@ export class PostgresEngramStore {
1006
1018
  // Flush any buffered activation events so stats reflect the latest writes.
1007
1019
  await this.flushActivationEvents();
1008
1020
  const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
1009
- const result = await this.q(`SELECT latency_ms FROM activation_events
1010
- WHERE agent_id = $1 AND timestamp > $2
1021
+ const result = await this.q(`SELECT latency_ms FROM activation_events
1022
+ WHERE agent_id = $1 AND timestamp > $2
1011
1023
  ORDER BY latency_ms ASC`, [agentId, since]);
1012
1024
  if (result.rows.length === 0)
1013
1025
  return { count: 0, avgLatencyMs: 0, p50LatencyMs: 0, p90LatencyMs: 0, p95LatencyMs: 0 };
@@ -1027,9 +1039,9 @@ export class PostgresEngramStore {
1027
1039
  async getLinkedFeedbackStats(agentId, windowHours = 24 * 7) {
1028
1040
  await this.readyPromise;
1029
1041
  const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
1030
- const result = await this.q(`SELECT COUNT(*) AS total, COUNT(CASE WHEN rf.useful = TRUE THEN 1 END) AS useful
1031
- FROM retrieval_feedback rf
1032
- JOIN activation_events ae ON ae.id = rf.activation_event_id
1042
+ const result = await this.q(`SELECT COUNT(*) AS total, COUNT(CASE WHEN rf.useful = TRUE THEN 1 END) AS useful
1043
+ FROM retrieval_feedback rf
1044
+ JOIN activation_events ae ON ae.id = rf.activation_event_id
1033
1045
  WHERE ae.agent_id = $1 AND rf.timestamp > $2`, [agentId, since]);
1034
1046
  const row = result.rows[0] ?? { total: 0, useful: 0 };
1035
1047
  return { total: Number(row.total), useful: Number(row.useful) };
@@ -1046,7 +1058,7 @@ export class PostgresEngramStore {
1046
1058
  await this.readyPromise;
1047
1059
  const id = randomUUID();
1048
1060
  const now = new Date().toISOString();
1049
- await this.q(`INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
1061
+ await this.q(`INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
1050
1062
  VALUES ($1, $2, $3, $4::vector, 0, $5, $5, $5)`, [id, input.agentId, input.label, vectorToLiteral(input.embedding ?? null), now]);
1051
1063
  const ep = await this.getEpisode(id);
1052
1064
  if (!ep)
@@ -1072,9 +1084,9 @@ export class PostgresEngramStore {
1072
1084
  async addEngramToEpisode(engramId, episodeId) {
1073
1085
  await this.readyPromise;
1074
1086
  await this.q(`UPDATE engrams SET episode_id = $1 WHERE id = $2`, [episodeId, engramId]);
1075
- await this.q(`UPDATE episodes SET
1076
- engram_count = engram_count + 1,
1077
- end_time = GREATEST(end_time, $1)
1087
+ await this.q(`UPDATE episodes SET
1088
+ engram_count = engram_count + 1,
1089
+ end_time = GREATEST(end_time, $1)
1078
1090
  WHERE id = $2`, [new Date().toISOString(), episodeId]);
1079
1091
  }
1080
1092
  async getEngramsByEpisode(episodeId) {
@@ -1113,44 +1125,44 @@ export class PostgresEngramStore {
1113
1125
  async updateAutoCheckpointWrite(agentId, engramId) {
1114
1126
  await this.readyPromise;
1115
1127
  const now = new Date().toISOString();
1116
- await this.q(`INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
1117
- VALUES ($1, $2, $3, 1, $3)
1118
- ON CONFLICT(agent_id) DO UPDATE SET
1119
- last_write_id = EXCLUDED.last_write_id,
1120
- last_activity_at = EXCLUDED.last_activity_at,
1121
- write_count_since_consolidation = conscious_state.write_count_since_consolidation + 1,
1128
+ await this.q(`INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
1129
+ VALUES ($1, $2, $3, 1, $3)
1130
+ ON CONFLICT(agent_id) DO UPDATE SET
1131
+ last_write_id = EXCLUDED.last_write_id,
1132
+ last_activity_at = EXCLUDED.last_activity_at,
1133
+ write_count_since_consolidation = conscious_state.write_count_since_consolidation + 1,
1122
1134
  updated_at = EXCLUDED.updated_at`, [agentId, engramId, now]);
1123
1135
  }
1124
1136
  async updateAutoCheckpointRecall(agentId, context, engramIds) {
1125
1137
  await this.readyPromise;
1126
1138
  const now = new Date().toISOString();
1127
- await this.q(`INSERT INTO conscious_state (agent_id, last_recall_context, last_recall_ids, last_activity_at, recall_count_since_consolidation, updated_at)
1128
- VALUES ($1, $2, $3, $4, 1, $4)
1129
- ON CONFLICT(agent_id) DO UPDATE SET
1130
- last_recall_context = EXCLUDED.last_recall_context,
1131
- last_recall_ids = EXCLUDED.last_recall_ids,
1132
- last_activity_at = EXCLUDED.last_activity_at,
1133
- recall_count_since_consolidation = conscious_state.recall_count_since_consolidation + 1,
1139
+ await this.q(`INSERT INTO conscious_state (agent_id, last_recall_context, last_recall_ids, last_activity_at, recall_count_since_consolidation, updated_at)
1140
+ VALUES ($1, $2, $3, $4, 1, $4)
1141
+ ON CONFLICT(agent_id) DO UPDATE SET
1142
+ last_recall_context = EXCLUDED.last_recall_context,
1143
+ last_recall_ids = EXCLUDED.last_recall_ids,
1144
+ last_activity_at = EXCLUDED.last_activity_at,
1145
+ recall_count_since_consolidation = conscious_state.recall_count_since_consolidation + 1,
1134
1146
  updated_at = EXCLUDED.updated_at`, [agentId, context, JSON.stringify(engramIds), now]);
1135
1147
  }
1136
1148
  async touchActivity(agentId) {
1137
1149
  await this.readyPromise;
1138
1150
  const now = new Date().toISOString();
1139
- await this.q(`INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
1140
- VALUES ($1, $2, $2)
1141
- ON CONFLICT(agent_id) DO UPDATE SET
1142
- last_activity_at = EXCLUDED.last_activity_at,
1151
+ await this.q(`INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
1152
+ VALUES ($1, $2, $2)
1153
+ ON CONFLICT(agent_id) DO UPDATE SET
1154
+ last_activity_at = EXCLUDED.last_activity_at,
1143
1155
  updated_at = EXCLUDED.updated_at`, [agentId, now]);
1144
1156
  }
1145
1157
  async saveCheckpoint(agentId, state) {
1146
1158
  await this.readyPromise;
1147
1159
  const now = new Date().toISOString();
1148
- await this.q(`INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
1149
- VALUES ($1, $2, $3, $3, $3)
1150
- ON CONFLICT(agent_id) DO UPDATE SET
1151
- execution_state = EXCLUDED.execution_state,
1152
- checkpoint_at = EXCLUDED.checkpoint_at,
1153
- last_activity_at = EXCLUDED.last_activity_at,
1160
+ await this.q(`INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
1161
+ VALUES ($1, $2, $3, $3, $3)
1162
+ ON CONFLICT(agent_id) DO UPDATE SET
1163
+ execution_state = EXCLUDED.execution_state,
1164
+ checkpoint_at = EXCLUDED.checkpoint_at,
1165
+ last_activity_at = EXCLUDED.last_activity_at,
1154
1166
  updated_at = EXCLUDED.updated_at`, [agentId, JSON.stringify(state), now]);
1155
1167
  }
1156
1168
  /** Distinct agent ids present in this store (D3 whoami; includes all stages). */
@@ -1169,9 +1181,9 @@ export class PostgresEngramStore {
1169
1181
  const t = term.toLowerCase().replace(/[%_]/g, '');
1170
1182
  if (!t)
1171
1183
  return [];
1172
- const res = await this.pool.query(`SELECT DISTINCT entity FROM (
1173
- SELECT entity FROM entity_mentions WHERE entity LIKE $1
1174
- UNION SELECT entity FROM entity_aliases WHERE alias LIKE $1
1184
+ const res = await this.pool.query(`SELECT DISTINCT entity FROM (
1185
+ SELECT entity FROM entity_mentions WHERE entity LIKE $1
1186
+ UNION SELECT entity FROM entity_aliases WHERE alias LIKE $1
1175
1187
  ) u LIMIT $2`, [`%${t}%`, limit]);
1176
1188
  return res.rows.map(r => r.entity);
1177
1189
  }
@@ -1213,13 +1225,13 @@ export class PostgresEngramStore {
1213
1225
  await this.q(`UPDATE conscious_state SET last_mini_consolidation_at = $1, updated_at = $1 WHERE agent_id = $2`, [now, agentId]);
1214
1226
  }
1215
1227
  else {
1216
- await this.q(`UPDATE conscious_state SET
1217
- last_consolidation_at = $1,
1218
- last_mini_consolidation_at = $1,
1219
- write_count_since_consolidation = 0,
1220
- recall_count_since_consolidation = 0,
1221
- consolidation_cycle_count = consolidation_cycle_count + 1,
1222
- updated_at = $1
1228
+ await this.q(`UPDATE conscious_state SET
1229
+ last_consolidation_at = $1,
1230
+ last_mini_consolidation_at = $1,
1231
+ write_count_since_consolidation = 0,
1232
+ recall_count_since_consolidation = 0,
1233
+ consolidation_cycle_count = consolidation_cycle_count + 1,
1234
+ updated_at = $1
1223
1235
  WHERE agent_id = $2`, [now, agentId]);
1224
1236
  }
1225
1237
  }
@@ -1244,10 +1256,10 @@ export class PostgresEngramStore {
1244
1256
  // ============================================================
1245
1257
  async getLatestByTag(opts) {
1246
1258
  await this.readyPromise;
1247
- let sql = `SELECT * FROM engrams
1248
- WHERE agent_id = $1
1249
- AND retracted = $2
1250
- AND stage = 'active'
1259
+ let sql = `SELECT * FROM engrams
1260
+ WHERE agent_id = $1
1261
+ AND retracted = $2
1262
+ AND stage = 'active'
1251
1263
  AND tags LIKE $3`;
1252
1264
  const params = [opts.agentId, opts.retracted ?? false, `%"${opts.tagKeyPrefix}%`];
1253
1265
  if (opts.scopeTagsAll && opts.scopeTagsAll.length > 0) {
@@ -1274,10 +1286,10 @@ export class PostgresEngramStore {
1274
1286
  }
1275
1287
  async getTopBy(opts) {
1276
1288
  await this.readyPromise;
1277
- let sql = `SELECT * FROM engrams
1278
- WHERE agent_id = $1
1279
- AND retracted = $2
1280
- AND stage = 'active'
1289
+ let sql = `SELECT * FROM engrams
1290
+ WHERE agent_id = $1
1291
+ AND retracted = $2
1292
+ AND stage = 'active'
1281
1293
  AND tags LIKE $3`;
1282
1294
  const params = [opts.agentId, opts.retracted ?? false, `%"${opts.sortField}%`];
1283
1295
  if (opts.filterTagsAll && opts.filterTagsAll.length > 0) {