agent-working-memory 0.17.0 → 0.19.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 (165) hide show
  1. package/README.md +8 -5
  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 -446
  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 +90 -13
  63. package/dist/core/reranker.js.map +1 -1
  64. package/dist/core/trust.d.ts +32 -0
  65. package/dist/core/trust.d.ts.map +1 -0
  66. package/dist/core/trust.js +66 -0
  67. package/dist/core/trust.js.map +1 -0
  68. package/dist/core/validity.d.ts +21 -0
  69. package/dist/core/validity.d.ts.map +1 -0
  70. package/dist/core/validity.js +41 -0
  71. package/dist/core/validity.js.map +1 -0
  72. package/dist/core/whoami.d.ts +3 -0
  73. package/dist/core/whoami.d.ts.map +1 -1
  74. package/dist/core/whoami.js +14 -0
  75. package/dist/core/whoami.js.map +1 -1
  76. package/dist/core/write-pipeline.d.ts +48 -0
  77. package/dist/core/write-pipeline.d.ts.map +1 -1
  78. package/dist/core/write-pipeline.js +80 -3
  79. package/dist/core/write-pipeline.js.map +1 -1
  80. package/dist/engine/activation.d.ts.map +1 -1
  81. package/dist/engine/activation.js +27 -4
  82. package/dist/engine/activation.js.map +1 -1
  83. package/dist/engine/consolidation.d.ts.map +1 -1
  84. package/dist/engine/consolidation.js +53 -4
  85. package/dist/engine/consolidation.js.map +1 -1
  86. package/dist/hooks/prime-query.d.ts +57 -0
  87. package/dist/hooks/prime-query.d.ts.map +1 -0
  88. package/dist/hooks/prime-query.js +388 -0
  89. package/dist/hooks/prime-query.js.map +1 -0
  90. package/dist/hooks/prime.d.ts +8 -0
  91. package/dist/hooks/prime.d.ts.map +1 -1
  92. package/dist/hooks/prime.js +21 -2
  93. package/dist/hooks/prime.js.map +1 -1
  94. package/dist/hooks/sidecar.d.ts +18 -0
  95. package/dist/hooks/sidecar.d.ts.map +1 -1
  96. package/dist/hooks/sidecar.js +58 -2
  97. package/dist/hooks/sidecar.js.map +1 -1
  98. package/dist/index.js +15 -3
  99. package/dist/index.js.map +1 -1
  100. package/dist/mcp.js +157 -104
  101. package/dist/mcp.js.map +1 -1
  102. package/dist/model-daemon.d.ts +8 -0
  103. package/dist/model-daemon.d.ts.map +1 -0
  104. package/dist/model-daemon.js +45 -0
  105. package/dist/model-daemon.js.map +1 -0
  106. package/dist/storage/factory.d.ts.map +1 -1
  107. package/dist/storage/factory.js +5 -0
  108. package/dist/storage/factory.js.map +1 -1
  109. package/dist/storage/pglite-schema.d.ts.map +1 -1
  110. package/dist/storage/pglite-schema.js +176 -170
  111. package/dist/storage/pglite-schema.js.map +1 -1
  112. package/dist/storage/pglite.d.ts +5 -0
  113. package/dist/storage/pglite.d.ts.map +1 -1
  114. package/dist/storage/pglite.js +165 -153
  115. package/dist/storage/pglite.js.map +1 -1
  116. package/dist/storage/postgres.d.ts +5 -0
  117. package/dist/storage/postgres.d.ts.map +1 -1
  118. package/dist/storage/postgres.js +165 -153
  119. package/dist/storage/postgres.js.map +1 -1
  120. package/dist/storage/sqlite.d.ts +22 -0
  121. package/dist/storage/sqlite.d.ts.map +1 -1
  122. package/dist/storage/sqlite.js +415 -363
  123. package/dist/storage/sqlite.js.map +1 -1
  124. package/dist/types/engram.d.ts +33 -0
  125. package/dist/types/engram.d.ts.map +1 -1
  126. package/dist/types/engram.js.map +1 -1
  127. package/package.json +1 -1
  128. package/plugin/.claude-plugin/plugin.json +1 -1
  129. package/plugin/README.md +1 -1
  130. package/plugin/bin/awm-mcp-launcher.cjs +2 -2
  131. package/plugin/skills/awm-memory/SKILL.md +11 -2
  132. package/src/adapters/common.ts +881 -802
  133. package/src/api/routes.ts +20 -0
  134. package/src/cli/migrate.ts +309 -307
  135. package/src/core/asked-value.ts +74 -0
  136. package/src/core/conflicts.ts +280 -0
  137. package/src/core/embeddings.ts +35 -10
  138. package/src/core/format-recall.ts +83 -25
  139. package/src/core/ml-worker.ts +458 -281
  140. package/src/core/model-daemon-client.ts +374 -0
  141. package/src/core/model-daemon-server.ts +344 -0
  142. package/src/core/model-daemon.ts +243 -0
  143. package/src/core/query-expander.ts +127 -126
  144. package/src/core/recall-config.ts +5 -0
  145. package/src/core/rerank-window.ts +164 -159
  146. package/src/core/rerank2.ts +143 -82
  147. package/src/core/reranker.ts +92 -12
  148. package/src/core/trust.ts +65 -0
  149. package/src/core/validity.ts +47 -0
  150. package/src/core/whoami.ts +136 -120
  151. package/src/core/write-pipeline.ts +119 -3
  152. package/src/engine/activation.ts +27 -4
  153. package/src/engine/consolidation.ts +950 -899
  154. package/src/hooks/prime-query.ts +375 -0
  155. package/src/hooks/prime.ts +25 -2
  156. package/src/hooks/sidecar.ts +57 -3
  157. package/src/index.ts +258 -248
  158. package/src/mcp.ts +1529 -1475
  159. package/src/model-daemon.ts +48 -0
  160. package/src/storage/factory.ts +25 -19
  161. package/src/storage/pglite-schema.ts +199 -193
  162. package/src/storage/pglite.ts +1513 -1498
  163. package/src/storage/postgres.ts +1613 -1598
  164. package/src/storage/sqlite.ts +2069 -2019
  165. package/src/types/engram.ts +30 -0
package/dist/mcp.js CHANGED
@@ -50,6 +50,14 @@ try {
50
50
  }
51
51
  }
52
52
  catch { /* No .env file */ }
53
+ // Shared model daemon ON by default for the MCP server (0.19). Measured in 0.18:
54
+ // results byte-identical to in-process, four sessions 4,825 MB → 1,730 MB, and
55
+ // the first recall of every session after the first drops from ~4 s (three
56
+ // models loading into a fresh process) to the warm ~0.6 s. The library default
57
+ // stays off so tests and embedded callers do not spawn daemons; the server
58
+ // entry points opt in here, and AWM_MODEL_DAEMON=0 turns it off.
59
+ if (process.env.AWM_MODEL_DAEMON === undefined)
60
+ process.env.AWM_MODEL_DAEMON = '1';
53
61
  // MCP uses stdout for JSON-RPC. Redirect console.log to stderr so engine
54
62
  // startup messages (ConsolidationScheduler, model loading, etc.) don't
55
63
  // corrupt the transport. This MUST happen before any engine imports.
@@ -71,6 +79,8 @@ import { DEFAULT_AGENT_CONFIG } from './types/agent.js';
71
79
  import { embed, getEmbedder } from './core/embeddings.js';
72
80
  import { getReranker } from './core/reranker.js';
73
81
  import { getExpander } from './core/query-expander.js';
82
+ import { warmModelDaemon } from './core/ml-worker.js';
83
+ import { isModelDaemonEnabled } from './core/model-daemon.js';
74
84
  import { startSidecar } from './hooks/sidecar.js';
75
85
  import { initLogger, log, getLogPath } from './core/logger.js';
76
86
  import { VERSION } from './version.js';
@@ -81,6 +91,8 @@ import { startLoopLagMonitor } from './core/write-telemetry.js';
81
91
  import { buildWhoami, formatWhoami } from './core/whoami.js';
82
92
  import { renderTaskEndInvitation, validateRecipeWrite, recipeSlug, getRecipe } from './recipes/index.js';
83
93
  import { formatRecallResultLine } from './core/format-recall.js';
94
+ import { newestConflicting } from './core/conflicts.js';
95
+ import { missingValueNote } from './core/asked-value.js';
84
96
  import { packRecallByBudget, formatTokenFooter, estimateTokens } from './core/token-budget.js';
85
97
  // --- Incognito Mode ---
86
98
  // When AWM_INCOGNITO=1, register zero tools. Claude won't see memory tools at all.
@@ -200,15 +212,15 @@ else {
200
212
  return 'unclassified';
201
213
  }
202
214
  // --- Tools ---
203
- server.tool('memory_write', `Store a memory. The salience filter decides whether it's worth keeping (active), needs more evidence (staging), or should be discarded.
204
-
205
- CALL THIS PROACTIVELY — do not wait to be asked. Write memories when you:
206
- - Discover something about the codebase, bugs, or architecture
207
- - Make a decision and want to remember why
208
- - Encounter and resolve an error
209
- - Learn a user preference or project pattern
210
- - Complete a significant piece of work
211
-
215
+ server.tool('memory_write', `Store a memory. The salience filter decides whether it's worth keeping (active), needs more evidence (staging), or should be discarded.
216
+
217
+ CALL THIS PROACTIVELY — do not wait to be asked. Write memories when you:
218
+ - Discover something about the codebase, bugs, or architecture
219
+ - Make a decision and want to remember why
220
+ - Encounter and resolve an error
221
+ - Learn a user preference or project pattern
222
+ - Complete a significant piece of work
223
+
212
224
  The concept should be a short label (3-8 words). The content should be the full detail.`, {
213
225
  concept: z.string().describe('Short label for this memory (3-8 words)'),
214
226
  content: z.string().describe('Full detail of what was learned'),
@@ -230,6 +242,8 @@ The concept should be a short label (3-8 words). The content should be the full
230
242
  .describe('Memory type: episodic (events/incidents), semantic (facts/decisions), procedural (how-to/steps). Auto-classified if omitted.'),
231
243
  supersedes: z.string().optional()
232
244
  .describe('ID of an older memory this one replaces. The old memory is down-ranked, not deleted.'),
245
+ correction: z.boolean().optional()
246
+ .describe('This write corrects a fact AWM already holds and you do not have its id. AWM supersedes the closest active memory about the same thing, stores the correction active at no less than that memory\'s confidence, and says what it replaced. Use it whenever a value changed (a time, a price, a status, a decision); written as a plain observation, a changed fact merges into or ranks below the stale version. If nothing close enough exists the write is stored as new and the reply says so. An explicit supersedes outranks this.'),
233
247
  // --- Agent-provided metadata (stored as searchable tags) ---
234
248
  project: z.string().optional()
235
249
  .describe('Project context (e.g., "EquiHub", "AWM"). Becomes a searchable tag.'),
@@ -307,11 +321,15 @@ The concept should be a short label (3-8 words). The content should be the full
307
321
  eventType: params.event_type,
308
322
  surprise: params.surprise,
309
323
  decisionMade: params.decision_made,
324
+ // Passed through so the salience filter sees a declared decision, not just an
325
+ // `intent=` tag. The mapping lives in write-pipeline.ts; see the measurement there.
326
+ intent: params.intent,
310
327
  causalDepth: params.causal_depth,
311
328
  resolutionEffort: params.resolution_effort,
312
329
  memoryClass: params.memory_class,
313
330
  memoryType,
314
331
  supersedes: params.supersedes,
332
+ correction: params.correction,
315
333
  originClass: params.origin_class,
316
334
  writerSession: params.session_id,
317
335
  recipeId: params.recipe_id,
@@ -336,9 +354,9 @@ The concept should be a short label (3-8 words). The content should be the full
336
354
  const salience = result.salience; // create/supersede always have salience
337
355
  const novelty = result.noveltyResult.novelty;
338
356
  const isLowSalience = salience.disposition === 'discard';
339
- // Decision propagation: when decision_made=true and coordination is enabled,
357
+ // Decision propagation: when decision_made=true or intent='decision', and coordination is enabled,
340
358
  // broadcast to coord_decisions so other agents can discover it
341
- if (params.decision_made && coordDb) {
359
+ if ((params.decision_made || params.intent === 'decision') && coordDb) {
342
360
  try {
343
361
  const agent = coordDb.prepare(`SELECT id, current_task FROM coord_agents WHERE name = ? AND status != 'dead' ORDER BY last_seen DESC LIMIT 1`).get(AGENT_ID);
344
362
  if (agent) {
@@ -349,29 +367,40 @@ The concept should be a short label (3-8 words). The content should be the full
349
367
  }
350
368
  const logDisposition = isLowSalience ? 'low-salience' : salience.disposition;
351
369
  log(AGENT_ID, `write:${logDisposition}`, `"${params.concept}" salience=${salience.score.toFixed(2)} novelty=${novelty.toFixed(1)} id=${engram.id}`);
370
+ // 2026-10-19: say what a correction replaced, or that it replaced nothing.
371
+ const correctionNote = params.correction
372
+ ? (result.correctionOf
373
+ ? `\nCorrected "${result.correctionOf.concept}" (${result.correctionOf.id}) — it is now superseded by this memory.`
374
+ : `\nNOTE: no existing memory was close enough to correct, so this was stored as new. If you know which memory it replaces, write again with supersedes: '<id>'.`)
375
+ : '';
376
+ const trustNote = result.trustBlocked
377
+ ? `\nNOTE: stored as its own memory, not merged into ${result.trustBlocked.matchedId} — this write is less trusted than that memory (src/core/trust.ts), so it must not replace or be merged into it.`
378
+ : '';
352
379
  return {
353
380
  content: [{
354
381
  type: 'text',
355
382
  text: `Stored (${salience.disposition}) "${params.concept}" [${salience.score.toFixed(2)}]\nID: ${engram.id}`
383
+ + correctionNote
384
+ + trustNote
356
385
  + (isLowSalience
357
386
  ? `\nNOTE: low salience — this memory is kept but demoted and may fade first. If it MUST survive and be recallable, retry with memory_class: 'canonical'. (Discards are audited: reason codes ${JSON.stringify(salience.reasonCodes.slice(0, 4))})`
358
387
  : ''),
359
388
  }],
360
389
  };
361
390
  });
362
- server.tool('memory_recall', `Recall memories relevant to a query. Uses cognitive activation — not keyword search.
363
-
364
- ALWAYS call this when:
365
- - Starting work on a project or topic (recall what you know)
366
- - Debugging (recall similar errors and solutions)
367
- - Making decisions (recall past decisions and outcomes)
368
- - The user mentions a topic you might have stored memories about
369
-
370
- Accepts either "query" or "context" parameter — both work identically.
391
+ server.tool('memory_recall', `Recall memories relevant to a query. Uses cognitive activation — not keyword search.
392
+
393
+ ALWAYS call this when:
394
+ - Starting work on a project or topic (recall what you know)
395
+ - Debugging (recall similar errors and solutions)
396
+ - Making decisions (recall past decisions and outcomes)
397
+ - The user mentions a topic you might have stored memories about
398
+
399
+ Accepts either "query" or "context" parameter — both work identically.
371
400
  Returns the most relevant memories ranked by text relevance, temporal recency, and associative strength.`, {
372
401
  query: z.string().optional().describe('What to search for — describe the situation, question, or topic'),
373
402
  context: z.string().optional().describe('Alias for query (either works)'),
374
- limit: z.number().optional().default(3).describe('Max memories to return (default 3). Measured on 400 real-store probes: k=3 is the only token-POSITIVE setting (+115 tok/recall, against -680 at k=5) and is 41% faster at p50, because `limit` sizes the cross-encoder rerank pool (min(limit*8, 40) — so 24 at k=3 against 40 at k=5) and rerank is 83% of warm recall time. It costs ~0.5pp success@1 versus k=5 — about 3 queries in 400, inside noise. Raise it explicitly when you want breadth for orientation rather than a specific fact; that is the case this default deliberately does not optimize for.'),
403
+ limit: z.number().optional().default(3).describe('Max memories to return (default 3). Measured on 400 real-store probes: k=3 is the only token-POSITIVE setting (+115 tok/recall, against -680 at k=5) and is 41% faster at p50, because `limit` sizes the cross-encoder rerank pool (max(limit*4, 40), so 40 at k=3 and at k=5 before the pool filter trims it, 24 at the median) and rerank is 83% of warm recall time. It costs ~0.5pp success@1 versus k=5 — about 3 queries in 400, inside noise. Raise it explicitly when you want breadth for orientation rather than a specific fact; that is the case this default deliberately does not optimize for.'),
375
404
  min_score: z.number().optional().default(0.05).describe('Minimum relevance score (default 0.05)'),
376
405
  include_staging: z.boolean().optional().default(false).describe('Include weak/unconfirmed memories?'),
377
406
  use_reranker: z.boolean().optional().default(true).describe('Use cross-encoder re-ranking for better relevance (default true)'),
@@ -458,8 +487,23 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
458
487
  // WHOLE response and not just the result lines. FOOTER_ALLOWANCE is a
459
488
  // fixed upper bound on the accounting line (measured at ~15-25 tokens; the
460
489
  // unit test asserts it stays under 40).
490
+ // 2026-10-19: recall confidence on every reply (it was printed only on
491
+ // abstention, so a caller could not tell a clear winner from a toss-up),
492
+ // and one line when the returned set holds two versions of a fact — see
493
+ // src/core/conflicts.ts. Both are reserved from the budget like the footer.
494
+ const topConfidence = results[0]?.confidence;
495
+ const confidenceFooter = topConfidence !== undefined ? `\n[confidence: ${topConfidence.toFixed(3)}]` : '';
496
+ const marked = results.filter(r => (r.conflictsWith ?? []).length > 0);
497
+ const newest = newestConflicting(results, results.map(r => r.conflictsWith ?? []));
498
+ const conflictFooter = marked.length > 0
499
+ ? `\n⚠ ${marked.length} of these memories disagree with each other${newest ? ` — the newest is ${newest}` : ''}. Confirm against the source before stating a value; supersede the stale one when you know which it is.`
500
+ : '';
501
+ // The near-miss (2026-10-19): memories plainly about the subject, none holding
502
+ // the kind of value asked for. That is the context shape that invites a made-up
503
+ // number; say so. See src/core/asked-value.ts.
504
+ const missingFooter = missingValueNote(queryText ?? '', results);
461
505
  const FOOTER_ALLOWANCE = 40;
462
- const reserved = estimateTokens(peerSuffix) + FOOTER_ALLOWANCE;
506
+ const reserved = estimateTokens(peerSuffix) + estimateTokens(confidenceFooter) + estimateTokens(conflictFooter) + estimateTokens(missingFooter) + FOOTER_ALLOWANCE;
463
507
  const packed = packRecallByBudget(results, formatRecallResultLine, params.max_tokens, reserved);
464
508
  // A budget too small to admit even the best match must not look like "no
465
509
  // memories found" — that is a different fact and would send the caller off
@@ -483,12 +527,12 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
483
527
  return {
484
528
  content: [{
485
529
  type: 'text',
486
- text: packed.lines.join('\n') + peerSuffix + formatTokenFooter(packed, params.max_tokens) + evFooter,
530
+ text: packed.lines.join('\n') + peerSuffix + conflictFooter + missingFooter + formatTokenFooter(packed, params.max_tokens) + confidenceFooter + evFooter,
487
531
  }],
488
532
  };
489
533
  });
490
- server.tool('memory_feedback', `Report whether a recalled memory was actually useful. This updates the memory's confidence score — useful memories become stronger, useless ones weaken.
491
-
534
+ server.tool('memory_feedback', `Report whether a recalled memory was actually useful. This updates the memory's confidence score — useful memories become stronger, useless ones weaken.
535
+
492
536
  Always call this after using a recalled memory so the system learns what's valuable.`, {
493
537
  engram_id: z.string().describe('ID of the memory (from memory_recall results)'),
494
538
  useful: z.boolean().describe('Was this memory actually helpful?'),
@@ -519,8 +563,8 @@ Always call this after using a recalled memory so the system learns what's valua
519
563
  }],
520
564
  };
521
565
  });
522
- server.tool('memory_retract', `Retract a memory that turned out to be wrong. Creates a correction and reduces confidence of related memories.
523
-
566
+ server.tool('memory_retract', `Retract a memory that turned out to be wrong. Creates a correction and reduces confidence of related memories.
567
+
524
568
  Use this when you discover a memory contains incorrect information.`, {
525
569
  engram_id: z.string().describe('ID of the wrong memory'),
526
570
  reason: z.string().describe('Why is this memory wrong?'),
@@ -544,13 +588,13 @@ Use this when you discover a memory contains incorrect information.`, {
544
588
  }],
545
589
  };
546
590
  });
547
- server.tool('memory_supersede', `Replace an outdated memory with a newer one. Unlike retraction (which marks memories as wrong), supersession marks the old memory as outdated but historically correct.
548
-
549
- Use this when:
550
- - A status or count has changed (e.g., "5 reviews done" → "7 reviews done")
551
- - Architecture or infrastructure evolved (e.g., "two-repo model" → "three-repo model")
552
- - A schedule or plan was updated
553
-
591
+ server.tool('memory_supersede', `Replace an outdated memory with a newer one. Unlike retraction (which marks memories as wrong), supersession marks the old memory as outdated but historically correct.
592
+
593
+ Use this when:
594
+ - A status or count has changed (e.g., "5 reviews done" → "7 reviews done")
595
+ - Architecture or infrastructure evolved (e.g., "two-repo model" → "three-repo model")
596
+ - A schedule or plan was updated
597
+
554
598
  The old memory stays in the database (searchable for history) but is heavily down-ranked in recall so the current version dominates.`, {
555
599
  old_engram_id: z.string().describe('ID of the outdated memory (from memory_recall results, or memory_write\'s own response if you just wrote it)'),
556
600
  new_engram_id: z.string().describe('ID of the replacement memory'),
@@ -581,7 +625,7 @@ The old memory stays in the database (searchable for history) but is heavily dow
581
625
  const info = await buildWhoami(store, AGENT_ID, 'mcp', sidecarHandle ? sidecarHandle.boundPort() : undefined);
582
626
  return { content: [{ type: 'text', text: formatWhoami(info) }] };
583
627
  });
584
- server.tool('memory_stats', `Get memory health stats — how many memories, confidence levels, association count, and system performance.
628
+ server.tool('memory_stats', `Get memory health stats — how many memories, confidence levels, association count, and system performance.
585
629
  Also shows the activity log path so the user can tail it to see what's happening.`, {}, async () => {
586
630
  const metrics = await evalEngine.computeMetrics(AGENT_ID);
587
631
  const checkpoint = await store.getCheckpoint(AGENT_ID);
@@ -623,13 +667,13 @@ Also shows the activity log path so the user can tail it to see what's happening
623
667
  };
624
668
  });
625
669
  // --- Checkpointing Tools ---
626
- server.tool('memory_checkpoint', `Save your current execution state so you can recover after context compaction.
627
-
628
- ALWAYS call this before:
629
- - Long operations (multi-file generation, large refactors, overnight work)
630
- - Anything that might fill the context window
631
- - Switching to a different task
632
-
670
+ server.tool('memory_checkpoint', `Save your current execution state so you can recover after context compaction.
671
+
672
+ ALWAYS call this before:
673
+ - Long operations (multi-file generation, large refactors, overnight work)
674
+ - Anything that might fill the context window
675
+ - Switching to a different task
676
+
633
677
  Also call periodically during long sessions to avoid losing state. The state is saved per-agent and overwrites any previous checkpoint.`, {
634
678
  current_task: z.string().describe('What you are currently working on'),
635
679
  decisions: z.array(z.string()).optional().default([])
@@ -663,14 +707,14 @@ Also call periodically during long sessions to avoid losing state. The state is
663
707
  }],
664
708
  };
665
709
  });
666
- server.tool('memory_restore', `Restore your previous execution state after context compaction or at session start.
667
-
668
- Returns:
669
- - Your saved execution state (task, decisions, next steps, files)
670
- - Recently recalled memories for context
671
- - Your last write for continuity
672
- - How long you were idle
673
-
710
+ server.tool('memory_restore', `Restore your previous execution state after context compaction or at session start.
711
+
712
+ Returns:
713
+ - Your saved execution state (task, decisions, next steps, files)
714
+ - Recently recalled memories for context
715
+ - Your last write for continuity
716
+ - How long you were idle
717
+
674
718
  Use this at the start of every session or after compaction to pick up where you left off.`, {}, async () => {
675
719
  const checkpoint = await store.getCheckpoint(AGENT_ID);
676
720
  // Cold-store nudge: an empty store means the agent has nothing to recall — offer to warm-start.
@@ -793,9 +837,9 @@ Use this at the start of every session or after compaction to pick up where you
793
837
  if (coordDb) {
794
838
  try {
795
839
  const myAgent = coordDb.prepare(`SELECT id FROM coord_agents WHERE name = ? AND status != 'dead' ORDER BY last_seen DESC LIMIT 1`).get(AGENT_ID);
796
- const peerDecisions = coordDb.prepare(`SELECT d.summary, a.name AS author_name, d.created_at
797
- FROM coord_decisions d JOIN coord_agents a ON d.author_id = a.id
798
- WHERE d.author_id != ? AND d.created_at > datetime('now', '-30 minutes')
840
+ const peerDecisions = coordDb.prepare(`SELECT d.summary, a.name AS author_name, d.created_at
841
+ FROM coord_decisions d JOIN coord_agents a ON d.author_id = a.id
842
+ WHERE d.author_id != ? AND d.created_at > datetime('now', '-30 minutes')
799
843
  ORDER BY d.created_at DESC LIMIT 10`).all(myAgent?.id ?? '');
800
844
  if (peerDecisions.length > 0) {
801
845
  parts.push(`\n**Peer decisions (last 30 min):**`);
@@ -814,11 +858,11 @@ Use this at the start of every session or after compaction to pick up where you
814
858
  };
815
859
  });
816
860
  // --- Onboarding Tools (warm-start a cold store) ---
817
- server.tool('onboard_scan', `Scan a project's documentation + repository and return CANDIDATE memories to seed a cold store.
818
-
819
- Use this when the store is empty / you're new to a project. The scan is deterministic
820
- (real file contents, not guesses) — YOUR job is to refine the candidates into atomic,
821
- recall-shaped memories, run the interview (onboard_questions), confirm with the user, then
861
+ server.tool('onboard_scan', `Scan a project's documentation + repository and return CANDIDATE memories to seed a cold store.
862
+
863
+ Use this when the store is empty / you're new to a project. The scan is deterministic
864
+ (real file contents, not guesses) — YOUR job is to refine the candidates into atomic,
865
+ recall-shaped memories, run the interview (onboard_questions), confirm with the user, then
822
866
  save the good ones with memory_write (memory_class="canonical"). Nothing is saved by this tool.`, {
823
867
  docs: z.array(z.string()).optional()
824
868
  .describe('Doc files/dirs to scan (Markdown/text). Defaults to the repo (or cwd).'),
@@ -843,18 +887,18 @@ save the good ones with memory_write (memory_class="canonical"). Nothing is save
843
887
  ].join('\n');
844
888
  return { content: [{ type: 'text', text }] };
845
889
  });
846
- server.tool('onboard_questions', `Return the onboarding interview questions. Ask the user ONE at a time, starting with the
890
+ server.tool('onboard_questions', `Return the onboarding interview questions. Ask the user ONE at a time, starting with the
847
891
  goal of the memory system, and ask follow-ups for clarity. Turn each answer into a canonical memory.`, {}, async () => ({
848
892
  content: [{ type: 'text', text: INTERVIEW_QUESTIONS.map((q, i) => `${i + 1}. ${q}`).join('\n') }],
849
893
  }));
850
894
  // --- Task Management Tools ---
851
- server.tool('memory_task_add', `Create a task that you need to come back to. Tasks are memories with status and priority tracking.
852
-
853
- Use this when:
854
- - You identify work that needs doing but can't do it right now
855
- - The user mentions something to do later
856
- - You want to park a sub-task while focusing on something more urgent
857
-
895
+ server.tool('memory_task_add', `Create a task that you need to come back to. Tasks are memories with status and priority tracking.
896
+
897
+ Use this when:
898
+ - You identify work that needs doing but can't do it right now
899
+ - The user mentions something to do later
900
+ - You want to park a sub-task while focusing on something more urgent
901
+
858
902
  Tasks automatically get high salience so they won't be discarded.`, {
859
903
  concept: z.string().describe('Short task title (3-10 words)'),
860
904
  content: z.string().describe('Full task description — what needs doing, context, acceptance criteria'),
@@ -894,11 +938,11 @@ Tasks automatically get high salience so they won't be discarded.`, {
894
938
  }],
895
939
  };
896
940
  });
897
- server.tool('memory_task_update', `Update a task's status or priority. Use this to:
898
- - Start working on a task (open → in_progress)
899
- - Mark a task done (→ done)
900
- - Block a task on another (→ blocked)
901
- - Reprioritize (change priority)
941
+ server.tool('memory_task_update', `Update a task's status or priority. Use this to:
942
+ - Start working on a task (open → in_progress)
943
+ - Mark a task done (→ done)
944
+ - Block a task on another (→ blocked)
945
+ - Reprioritize (change priority)
902
946
  - Unblock a task (clear blocked_by)`, {
903
947
  task_id: z.string().describe('ID of the task to update'),
904
948
  status: z.enum(['open', 'in_progress', 'blocked', 'done']).optional()
@@ -928,8 +972,8 @@ Tasks automatically get high salience so they won't be discarded.`, {
928
972
  }],
929
973
  };
930
974
  });
931
- server.tool('memory_task_list', `List tasks with optional status filter. Shows tasks ordered by priority (urgent first).
932
-
975
+ server.tool('memory_task_list', `List tasks with optional status filter. Shows tasks ordered by priority (urgent first).
976
+
933
977
  Use at the start of a session to see what's pending, or to check blocked/done tasks.`, {
934
978
  status: z.enum(['open', 'in_progress', 'blocked', 'done']).optional()
935
979
  .describe('Filter by status (omit to see all active tasks)'),
@@ -955,10 +999,10 @@ Use at the start of a session to see what's pending, or to check blocked/done ta
955
999
  }],
956
1000
  };
957
1001
  });
958
- server.tool('memory_task_next', `Get the single most important task to work on next.
959
-
960
- Prioritizes: in_progress tasks first (finish what you started), then by priority level, then oldest first. Skips blocked and done tasks.
961
-
1002
+ server.tool('memory_task_next', `Get the single most important task to work on next.
1003
+
1004
+ Prioritizes: in_progress tasks first (finish what you started), then by priority level, then oldest first. Skips blocked and done tasks.
1005
+
962
1006
  Use this when you finish a task or need to decide what to do next.`, {}, async () => {
963
1007
  const next = await store.getNextTask(AGENT_ID);
964
1008
  if (!next) {
@@ -974,13 +1018,13 @@ Use this when you finish a task or need to decide what to do next.`, {}, async (
974
1018
  };
975
1019
  });
976
1020
  // --- Task Bracket Tools ---
977
- server.tool('memory_task_begin', `Signal that you're starting a significant task. Auto-checkpoints current state and recalls relevant memories.
978
-
979
- CALL THIS when starting:
980
- - A multi-step operation (doc generation, large refactor, migration)
981
- - Work on a new topic or project area
982
- - Anything that might fill the context window
983
-
1021
+ server.tool('memory_task_begin', `Signal that you're starting a significant task. Auto-checkpoints current state and recalls relevant memories.
1022
+
1023
+ CALL THIS when starting:
1024
+ - A multi-step operation (doc generation, large refactor, migration)
1025
+ - Work on a new topic or project area
1026
+ - Anything that might fill the context window
1027
+
984
1028
  This ensures your state is saved before you start, and primes recall with relevant context.`, {
985
1029
  topic: z.string().describe('What task are you starting? (3-15 words)'),
986
1030
  files: z.array(z.string()).optional().default([])
@@ -1032,13 +1076,13 @@ This ensures your state is saved before you start, and primes recall with releva
1032
1076
  }],
1033
1077
  };
1034
1078
  });
1035
- server.tool('memory_task_end', `Signal that you've finished a significant task. Writes a summary memory and auto-checkpoints.
1036
-
1037
- CALL THIS when you finish:
1038
- - A multi-step operation
1039
- - Before switching to a different topic
1040
- - At the end of a work session
1041
-
1079
+ server.tool('memory_task_end', `Signal that you've finished a significant task. Writes a summary memory and auto-checkpoints.
1080
+
1081
+ CALL THIS when you finish:
1082
+ - A multi-step operation
1083
+ - Before switching to a different topic
1084
+ - At the end of a work session
1085
+
1042
1086
  This captures what was accomplished so future sessions can recall it.`, {
1043
1087
  summary: z.string().describe('What was accomplished? Include key outcomes, decisions, and any issues.'),
1044
1088
  tags: z.array(z.string()).optional().default([])
@@ -1114,14 +1158,14 @@ This captures what was accomplished so future sessions can recall it.`, {
1114
1158
  }],
1115
1159
  };
1116
1160
  });
1117
- server.tool('compress_output', `Compress a STRUCTURED tool output (JSON object/array, query rows, log records) into TOON —
1118
- a compact, schema-aware tabular encoding — before putting it in your context. Cuts ~50-65%
1119
- of the tokens on uniform arrays at zero comprehension cost (validated: models read TOON as
1120
- accurately as JSON). Use this on large tool results you need to keep in context.
1121
-
1122
- Output-only and safe: it never changes the data. Non-JSON / prose is returned unchanged.
1123
- TOON is only emitted when it reproduces the input exactly (self-verified round-trip);
1124
- otherwise you get plain JSON back. When compressed, you also get a 'ref' — call
1161
+ server.tool('compress_output', `Compress a STRUCTURED tool output (JSON object/array, query rows, log records) into TOON —
1162
+ a compact, schema-aware tabular encoding — before putting it in your context. Cuts ~50-65%
1163
+ of the tokens on uniform arrays at zero comprehension cost (validated: models read TOON as
1164
+ accurately as JSON). Use this on large tool results you need to keep in context.
1165
+
1166
+ Output-only and safe: it never changes the data. Non-JSON / prose is returned unchanged.
1167
+ TOON is only emitted when it reproduces the input exactly (self-verified round-trip);
1168
+ otherwise you get plain JSON back. When compressed, you also get a 'ref' — call
1125
1169
  retrieve_original(ref) to get the verbatim source back if you ever need it.`, {
1126
1170
  output: z.string().describe('The tool output to compress — JSON text (preferred) or any string. Non-JSON is returned unchanged.'),
1127
1171
  min_saving_chars: z.number().optional().describe('Only emit TOON if it saves at least this many characters (default 40).'),
@@ -1135,8 +1179,8 @@ retrieve_original(ref) to get the verbatim source back if you ever need it.`, {
1135
1179
  content: [{ type: 'text', text: header + r.text }],
1136
1180
  };
1137
1181
  });
1138
- server.tool('retrieve_original', `Retrieve the verbatim original text for a 'ref' returned by compress_output. Use this when
1139
- you need the exact, uncompressed source (e.g. to pass it to another tool unchanged). Returns
1182
+ server.tool('retrieve_original', `Retrieve the verbatim original text for a 'ref' returned by compress_output. Use this when
1183
+ you need the exact, uncompressed source (e.g. to pass it to another tool unchanged). Returns
1140
1184
  an error if the ref has expired (originals are kept for the most recent compressions only).`, {
1141
1185
  ref: z.string().describe('The ref handle returned by compress_output (e.g. "awm_orig_12").'),
1142
1186
  }, async (params) => {
@@ -1184,11 +1228,14 @@ an error if the ref has expired (originals are kept for the most recent compress
1184
1228
  ? r.engram.createdAt.toISOString()
1185
1229
  : r.engram.createdAt,
1186
1230
  memoryClass: r.engram.memoryClass,
1231
+ validFrom: r.engram.validFrom,
1187
1232
  validTo: r.engram.validTo,
1233
+ supersededBy: r.engram.supersededBy,
1188
1234
  },
1189
1235
  score: r.score,
1190
1236
  summary: r.summary,
1191
1237
  confidence: r.confidence,
1238
+ conflictsWith: r.conflictsWith,
1192
1239
  }));
1193
1240
  },
1194
1241
  onConsolidate: async (agentId, reason) => {
@@ -1206,9 +1253,15 @@ an error if the ref has expired (originals are kept for the most recent compress
1206
1253
  // All output is stderr-safe (stdout carries JSON-RPC frames).
1207
1254
  // Escape hatch: AWM_NO_EAGER_WARM=1 restores lazy loading.
1208
1255
  if (process.env.AWM_NO_EAGER_WARM !== '1') {
1209
- getEmbedder().catch(err => console.error('Embedding model unavailable:', err.message));
1210
- getReranker().catch(err => console.error('Reranker model unavailable:', err.message));
1211
- getExpander().catch(err => console.error('Query expander model unavailable:', err.message));
1256
+ // With the shared model daemon on, the daemon loads them and this process loads none.
1257
+ if (isModelDaemonEnabled()) {
1258
+ void warmModelDaemon();
1259
+ }
1260
+ else {
1261
+ getEmbedder().catch(err => console.error('Embedding model unavailable:', err.message));
1262
+ getReranker().catch(err => console.error('Reranker model unavailable:', err.message));
1263
+ getExpander().catch(err => console.error('Query expander model unavailable:', err.message));
1264
+ }
1212
1265
  if (BACKEND === 'sqlite') {
1213
1266
  setImmediate(() => {
1214
1267
  try {