agent-working-memory 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +29 -0
  2. package/dist/adapters/claude-code.d.ts.map +1 -1
  3. package/dist/adapters/claude-code.js +63 -3
  4. package/dist/adapters/claude-code.js.map +1 -1
  5. package/dist/adapters/common.d.ts.map +1 -1
  6. package/dist/adapters/common.js +329 -306
  7. package/dist/adapters/common.js.map +1 -1
  8. package/dist/api/routes.d.ts.map +1 -1
  9. package/dist/api/routes.js +29 -7
  10. package/dist/api/routes.js.map +1 -1
  11. package/dist/coordination/routes.d.ts.map +1 -1
  12. package/dist/coordination/routes.js +174 -170
  13. package/dist/coordination/routes.js.map +1 -1
  14. package/dist/core/embeddings.d.ts.map +1 -1
  15. package/dist/core/embeddings.js +3 -0
  16. package/dist/core/embeddings.js.map +1 -1
  17. package/dist/core/entity-extract.d.ts +3 -0
  18. package/dist/core/entity-extract.d.ts.map +1 -0
  19. package/dist/core/entity-extract.js +47 -0
  20. package/dist/core/entity-extract.js.map +1 -0
  21. package/dist/core/salience.d.ts.map +1 -1
  22. package/dist/core/salience.js +14 -2
  23. package/dist/core/salience.js.map +1 -1
  24. package/dist/core/whoami.d.ts +24 -0
  25. package/dist/core/whoami.d.ts.map +1 -0
  26. package/dist/core/whoami.js +66 -0
  27. package/dist/core/whoami.js.map +1 -0
  28. package/dist/core/write-pipeline.d.ts +9 -0
  29. package/dist/core/write-pipeline.d.ts.map +1 -1
  30. package/dist/core/write-pipeline.js +109 -68
  31. package/dist/core/write-pipeline.js.map +1 -1
  32. package/dist/core/write-telemetry.d.ts +33 -0
  33. package/dist/core/write-telemetry.d.ts.map +1 -0
  34. package/dist/core/write-telemetry.js +110 -0
  35. package/dist/core/write-telemetry.js.map +1 -0
  36. package/dist/engine/activation.d.ts +22 -12
  37. package/dist/engine/activation.d.ts.map +1 -1
  38. package/dist/engine/activation.js +133 -17
  39. package/dist/engine/activation.js.map +1 -1
  40. package/dist/engine/consolidation-scheduler.d.ts +1 -1
  41. package/dist/engine/consolidation-scheduler.js +1 -1
  42. package/dist/engine/consolidation.d.ts +1 -0
  43. package/dist/engine/consolidation.d.ts.map +1 -1
  44. package/dist/engine/consolidation.js +18 -0
  45. package/dist/engine/consolidation.js.map +1 -1
  46. package/dist/engine/eval.d.ts.map +1 -1
  47. package/dist/engine/eval.js +5 -1
  48. package/dist/engine/eval.js.map +1 -1
  49. package/dist/index.js +20 -2
  50. package/dist/index.js.map +1 -1
  51. package/dist/mcp.d.ts +2 -1
  52. package/dist/mcp.d.ts.map +1 -1
  53. package/dist/mcp.js +168 -100
  54. package/dist/mcp.js.map +1 -1
  55. package/dist/recipes/index.d.ts +57 -0
  56. package/dist/recipes/index.d.ts.map +1 -0
  57. package/dist/recipes/index.js +81 -0
  58. package/dist/recipes/index.js.map +1 -0
  59. package/dist/storage/pglite-schema.d.ts.map +1 -1
  60. package/dist/storage/pglite-schema.js +27 -0
  61. package/dist/storage/pglite-schema.js.map +1 -1
  62. package/dist/storage/pglite.d.ts +5 -0
  63. package/dist/storage/pglite.d.ts.map +1 -1
  64. package/dist/storage/pglite.js +180 -138
  65. package/dist/storage/pglite.js.map +1 -1
  66. package/dist/storage/postgres.d.ts +5 -0
  67. package/dist/storage/postgres.d.ts.map +1 -1
  68. package/dist/storage/postgres.js +180 -138
  69. package/dist/storage/postgres.js.map +1 -1
  70. package/dist/storage/sqlite.d.ts +9 -0
  71. package/dist/storage/sqlite.d.ts.map +1 -1
  72. package/dist/storage/sqlite.js +394 -326
  73. package/dist/storage/sqlite.js.map +1 -1
  74. package/dist/types/engram.d.ts +14 -0
  75. package/dist/types/engram.d.ts.map +1 -1
  76. package/dist/types/engram.js.map +1 -1
  77. package/package.json +1 -1
  78. package/src/adapters/claude-code.ts +66 -3
  79. package/src/adapters/common.ts +538 -515
  80. package/src/api/routes.ts +999 -971
  81. package/src/coordination/routes.ts +2155 -2150
  82. package/src/core/embeddings.ts +3 -0
  83. package/src/core/entity-extract.ts +47 -0
  84. package/src/core/salience.ts +529 -514
  85. package/src/core/whoami.ts +92 -0
  86. package/src/core/write-pipeline.ts +60 -8
  87. package/src/core/write-telemetry.ts +131 -0
  88. package/src/engine/activation.ts +1468 -1369
  89. package/src/engine/consolidation-scheduler.ts +1 -1
  90. package/src/engine/consolidation.ts +887 -869
  91. package/src/engine/eval.ts +6 -1
  92. package/src/index.ts +248 -227
  93. package/src/mcp.ts +1341 -1270
  94. package/src/recipes/index.ts +125 -0
  95. package/src/storage/pglite-schema.ts +27 -0
  96. package/src/storage/pglite.ts +1420 -1372
  97. package/src/storage/postgres.ts +1523 -1475
  98. package/src/storage/sqlite.ts +1936 -1861
  99. package/src/types/engram.ts +22 -0
package/dist/mcp.js CHANGED
@@ -6,13 +6,14 @@
6
6
  * Runs as a stdio-based MCP server that Claude Code connects to directly.
7
7
  * Uses the storage and engine layers in-process (no HTTP overhead).
8
8
  *
9
- * Tools exposed (16):
9
+ * Tools exposed (19):
10
10
  * memory_write — store a memory (salience filter decides disposition)
11
11
  * memory_recall — activate memories by context (cognitive retrieval)
12
12
  * memory_feedback — report whether a recalled memory was useful
13
13
  * memory_retract — invalidate a wrong memory with optional correction
14
14
  * memory_supersede — replace an outdated memory with a current one
15
15
  * memory_stats — get memory health metrics
16
+ * memory_whoami — identify this instance, mode, store, and sibling agent spaces
16
17
  * memory_checkpoint — save structured execution state (survives compaction)
17
18
  * memory_restore — restore state + targeted recall after compaction
18
19
  * memory_task_add — create a prioritized task
@@ -73,6 +74,9 @@ import { VERSION } from './version.js';
73
74
  import { buildPack, INTERVIEW_QUESTIONS } from './onboard/index.js';
74
75
  import { liteCompress, retrieveOriginal } from './core/lite-compress.js';
75
76
  import { queryPeerDecisions, formatPeerDecisions } from './coordination/peer-decisions.js';
77
+ import { startLoopLagMonitor } from './core/write-telemetry.js';
78
+ import { buildWhoami, formatWhoami } from './core/whoami.js';
79
+ import { renderTaskEndInvitation, validateRecipeWrite, recipeSlug, getRecipe } from './recipes/index.js';
76
80
  // --- Incognito Mode ---
77
81
  // When AWM_INCOGNITO=1, register zero tools. Claude won't see memory tools at all.
78
82
  // No DB, no engines, no sidecar — just a bare MCP server that exposes nothing.
@@ -124,6 +128,7 @@ else {
124
128
  const consolidationScheduler = new ConsolidationScheduler(store, consolidationEngine);
125
129
  stagingBuffer.start(DEFAULT_AGENT_CONFIG.stagingTtlMs);
126
130
  consolidationScheduler.start();
131
+ startLoopLagMonitor();
127
132
  // Coordination DB handle — set when AWM_COORDINATION=true, used by memory_write for decision propagation
128
133
  let coordDb = null;
129
134
  const server = new McpServer({
@@ -188,15 +193,15 @@ else {
188
193
  return 'unclassified';
189
194
  }
190
195
  // --- Tools ---
191
- 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.
192
-
193
- CALL THIS PROACTIVELY — do not wait to be asked. Write memories when you:
194
- - Discover something about the codebase, bugs, or architecture
195
- - Make a decision and want to remember why
196
- - Encounter and resolve an error
197
- - Learn a user preference or project pattern
198
- - Complete a significant piece of work
199
-
196
+ 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.
197
+
198
+ CALL THIS PROACTIVELY — do not wait to be asked. Write memories when you:
199
+ - Discover something about the codebase, bugs, or architecture
200
+ - Make a decision and want to remember why
201
+ - Encounter and resolve an error
202
+ - Learn a user preference or project pattern
203
+ - Complete a significant piece of work
204
+
200
205
  The concept should be a short label (3-8 words). The content should be the full detail.`, {
201
206
  concept: z.string().describe('Short label for this memory (3-8 words)'),
202
207
  content: z.string().describe('Full detail of what was learned'),
@@ -229,6 +234,14 @@ The concept should be a short label (3-8 words). The content should be the full
229
234
  .describe('Confidence: verified (tested), observed (read in code), assumed (reasoning).'),
230
235
  session_id: z.string().optional()
231
236
  .describe('Session/conversation grouping ID. Memories with same session_id are associated.'),
237
+ origin_class: z.enum(['user-stated', 'tool-output', 'inference', 'recipe']).optional()
238
+ .describe('Provenance (D5, log-only): where this knowledge came from. user-stated = the human said it; tool-output = read from a tool/system; inference = your reasoning; recipe = produced by a cognition recipe.'),
239
+ recipe_id: z.string().optional()
240
+ .describe('Cognition-recipe id+version when origin_class is recipe.'),
241
+ valid_from: z.string().optional()
242
+ .describe('ISO date when the FACT becomes valid (temporal validity, not ingestion time).'),
243
+ valid_to: z.string().optional()
244
+ .describe('ISO date when the FACT stops being valid (e.g., a deadline or a superseding change).'),
232
245
  intent: z.enum(['decision', 'question', 'todo', 'finding', 'context']).optional()
233
246
  .describe('What kind of memory this is.'),
234
247
  }, async (params) => {
@@ -247,6 +260,37 @@ The concept should be a short label (3-8 words). The content should be the full
247
260
  metaTags.push(`sid=${params.session_id}`);
248
261
  if (params.intent)
249
262
  metaTags.push(`intent=${params.intent}`);
263
+ // D14 (2026-07-30): recipe write-backs are contract-checked. Provenance
264
+ // must never claim a recipe that does not exist, and malformed
265
+ // derivations are rejected with the contract echoed back so the host
266
+ // can self-correct in one retry.
267
+ if (params.origin_class === 'recipe') {
268
+ if (!params.recipe_id) {
269
+ return { content: [{ type: 'text', text: "Recipe write rejected: origin_class 'recipe' requires recipe_id (e.g. 'skill-derivation@1')." }] };
270
+ }
271
+ const v = validateRecipeWrite(params.recipe_id, params.concept, params.content);
272
+ if (!v.ok) {
273
+ const contract = getRecipe(params.recipe_id)?.writeBack ?? 'unknown recipe';
274
+ return { content: [{ type: 'text', text: `Recipe write rejected (${params.recipe_id}): ${v.errors.join('; ')}\nContract: ${contract}` }] };
275
+ }
276
+ // Standardize recipe write-backs: canonical class, standard tags.
277
+ const slug = recipeSlug(params.concept);
278
+ const ensure = (tag) => { if (!userTags.includes(tag) && !metaTags.includes(tag))
279
+ metaTags.push(tag); };
280
+ if (params.recipe_id.startsWith('skill-derivation')) {
281
+ ensure('topic=skill');
282
+ ensure(`skill=${slug}`);
283
+ params.memory_type = params.memory_type ?? 'procedural';
284
+ }
285
+ else if (params.recipe_id.startsWith('friction-lesson')) {
286
+ ensure('topic=friction');
287
+ ensure(`about=${slug}`);
288
+ // zod defaults event_type to 'observation', so force the recipe's
289
+ // contract value rather than ??-guarding against undefined.
290
+ params.event_type = 'friction';
291
+ }
292
+ params.memory_class = 'canonical';
293
+ }
250
294
  const memoryType = params.memory_type ?? classifyMemoryType(params.content);
251
295
  const result = await performWrite({ store, connectionEngine }, {
252
296
  agentId: AGENT_ID,
@@ -261,6 +305,11 @@ The concept should be a short label (3-8 words). The content should be the full
261
305
  memoryClass: params.memory_class,
262
306
  memoryType,
263
307
  supersedes: params.supersedes,
308
+ originClass: params.origin_class,
309
+ writerSession: params.session_id,
310
+ recipeId: params.recipe_id,
311
+ validFrom: params.valid_from,
312
+ validTo: params.valid_to,
264
313
  });
265
314
  // Auto-checkpoint — covers create/reinforce/supersede uniformly
266
315
  try {
@@ -296,19 +345,22 @@ The concept should be a short label (3-8 words). The content should be the full
296
345
  return {
297
346
  content: [{
298
347
  type: 'text',
299
- text: `Stored (${salience.disposition}) "${params.concept}" [${salience.score.toFixed(2)}]\nID: ${engram.id}`,
348
+ text: `Stored (${salience.disposition}) "${params.concept}" [${salience.score.toFixed(2)}]\nID: ${engram.id}`
349
+ + (isLowSalience
350
+ ? `\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))})`
351
+ : ''),
300
352
  }],
301
353
  };
302
354
  });
303
- server.tool('memory_recall', `Recall memories relevant to a query. Uses cognitive activation — not keyword search.
304
-
305
- ALWAYS call this when:
306
- - Starting work on a project or topic (recall what you know)
307
- - Debugging (recall similar errors and solutions)
308
- - Making decisions (recall past decisions and outcomes)
309
- - The user mentions a topic you might have stored memories about
310
-
311
- Accepts either "query" or "context" parameter — both work identically.
355
+ server.tool('memory_recall', `Recall memories relevant to a query. Uses cognitive activation — not keyword search.
356
+
357
+ ALWAYS call this when:
358
+ - Starting work on a project or topic (recall what you know)
359
+ - Debugging (recall similar errors and solutions)
360
+ - Making decisions (recall past decisions and outcomes)
361
+ - The user mentions a topic you might have stored memories about
362
+
363
+ Accepts either "query" or "context" parameter — both work identically.
312
364
  Returns the most relevant memories ranked by text relevance, temporal recency, and associative strength.`, {
313
365
  query: z.string().optional().describe('What to search for — describe the situation, question, or topic'),
314
366
  context: z.string().optional().describe('Alias for query (either works)'),
@@ -370,7 +422,16 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
370
422
  // requested 'compact' or 'auto' granularity, surface the engine-computed
371
423
  // summary instead of the full content — same engram, less to read.
372
424
  const body = r.summary ?? r.engram.content;
373
- return `${i + 1}. **${r.engram.concept}** (${r.score.toFixed(3)}): ${body}`;
425
+ // D8 (2026-07-30): conflict surfacing — a superseded memory that still
426
+ // ranks is shown WITH its replacement pointer instead of silently
427
+ // down-ranked. The model should trust the successor.
428
+ const chain = r.engram.supersededBy
429
+ ? ` ⚠ SUPERSEDED by ${r.engram.supersededBy} — treat as historical; recall/fetch the successor before relying on this.`
430
+ : '';
431
+ const validity = r.engram.validTo
432
+ ? ` [valid until ${r.engram.validTo}]`
433
+ : '';
434
+ return `${i + 1}. **${r.engram.concept}** (${r.score.toFixed(3)})${validity}: ${body}${chain}`;
374
435
  });
375
436
  return {
376
437
  content: [{
@@ -379,8 +440,8 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
379
440
  }],
380
441
  };
381
442
  });
382
- 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.
383
-
443
+ 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.
444
+
384
445
  Always call this after using a recalled memory so the system learns what's valuable.`, {
385
446
  engram_id: z.string().describe('ID of the memory (from memory_recall results)'),
386
447
  useful: z.boolean().describe('Was this memory actually helpful?'),
@@ -403,8 +464,8 @@ Always call this after using a recalled memory so the system learns what's valua
403
464
  }],
404
465
  };
405
466
  });
406
- server.tool('memory_retract', `Retract a memory that turned out to be wrong. Creates a correction and reduces confidence of related memories.
407
-
467
+ server.tool('memory_retract', `Retract a memory that turned out to be wrong. Creates a correction and reduces confidence of related memories.
468
+
408
469
  Use this when you discover a memory contains incorrect information.`, {
409
470
  engram_id: z.string().describe('ID of the wrong memory'),
410
471
  reason: z.string().describe('Why is this memory wrong?'),
@@ -428,13 +489,13 @@ Use this when you discover a memory contains incorrect information.`, {
428
489
  }],
429
490
  };
430
491
  });
431
- 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.
432
-
433
- Use this when:
434
- - A status or count has changed (e.g., "5 reviews done" → "7 reviews done")
435
- - Architecture or infrastructure evolved (e.g., "two-repo model" → "three-repo model")
436
- - A schedule or plan was updated
437
-
492
+ 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.
493
+
494
+ Use this when:
495
+ - A status or count has changed (e.g., "5 reviews done" → "7 reviews done")
496
+ - Architecture or infrastructure evolved (e.g., "two-repo model" → "three-repo model")
497
+ - A schedule or plan was updated
498
+
438
499
  The old memory stays in the database (searchable for history) but is heavily down-ranked in recall so the current version dominates.`, {
439
500
  old_engram_id: z.string().describe('ID of the outdated memory'),
440
501
  new_engram_id: z.string().describe('ID of the replacement memory'),
@@ -461,7 +522,11 @@ The old memory stays in the database (searchable for history) but is heavily dow
461
522
  }],
462
523
  };
463
524
  });
464
- server.tool('memory_stats', `Get memory health statshow many memories, confidence levels, association count, and system performance.
525
+ server.tool('memory_whoami', `Identify THIS AWM instanceagent id, mode (standalone/hive), backend, store path, code provenance, ports, and the sibling agent spaces present in the same store. Call when unsure which AWM instance or memory space you are talking to.`, {}, async () => {
526
+ const info = await buildWhoami(store, AGENT_ID, 'mcp');
527
+ return { content: [{ type: 'text', text: formatWhoami(info) }] };
528
+ });
529
+ server.tool('memory_stats', `Get memory health stats — how many memories, confidence levels, association count, and system performance.
465
530
  Also shows the activity log path so the user can tail it to see what's happening.`, {}, async () => {
466
531
  const metrics = await evalEngine.computeMetrics(AGENT_ID);
467
532
  const checkpoint = await store.getCheckpoint(AGENT_ID);
@@ -492,13 +557,13 @@ Also shows the activity log path so the user can tail it to see what's happening
492
557
  };
493
558
  });
494
559
  // --- Checkpointing Tools ---
495
- server.tool('memory_checkpoint', `Save your current execution state so you can recover after context compaction.
496
-
497
- ALWAYS call this before:
498
- - Long operations (multi-file generation, large refactors, overnight work)
499
- - Anything that might fill the context window
500
- - Switching to a different task
501
-
560
+ server.tool('memory_checkpoint', `Save your current execution state so you can recover after context compaction.
561
+
562
+ ALWAYS call this before:
563
+ - Long operations (multi-file generation, large refactors, overnight work)
564
+ - Anything that might fill the context window
565
+ - Switching to a different task
566
+
502
567
  Also call periodically during long sessions to avoid losing state. The state is saved per-agent and overwrites any previous checkpoint.`, {
503
568
  current_task: z.string().describe('What you are currently working on'),
504
569
  decisions: z.array(z.string()).optional().default([])
@@ -532,14 +597,14 @@ Also call periodically during long sessions to avoid losing state. The state is
532
597
  }],
533
598
  };
534
599
  });
535
- server.tool('memory_restore', `Restore your previous execution state after context compaction or at session start.
536
-
537
- Returns:
538
- - Your saved execution state (task, decisions, next steps, files)
539
- - Recently recalled memories for context
540
- - Your last write for continuity
541
- - How long you were idle
542
-
600
+ server.tool('memory_restore', `Restore your previous execution state after context compaction or at session start.
601
+
602
+ Returns:
603
+ - Your saved execution state (task, decisions, next steps, files)
604
+ - Recently recalled memories for context
605
+ - Your last write for continuity
606
+ - How long you were idle
607
+
543
608
  Use this at the start of every session or after compaction to pick up where you left off.`, {}, async () => {
544
609
  const checkpoint = await store.getCheckpoint(AGENT_ID);
545
610
  // Cold-store nudge: an empty store means the agent has nothing to recall — offer to warm-start.
@@ -658,9 +723,9 @@ Use this at the start of every session or after compaction to pick up where you
658
723
  if (coordDb) {
659
724
  try {
660
725
  const myAgent = coordDb.prepare(`SELECT id FROM coord_agents WHERE name = ? AND status != 'dead' ORDER BY last_seen DESC LIMIT 1`).get(AGENT_ID);
661
- const peerDecisions = coordDb.prepare(`SELECT d.summary, a.name AS author_name, d.created_at
662
- FROM coord_decisions d JOIN coord_agents a ON d.author_id = a.id
663
- WHERE d.author_id != ? AND d.created_at > datetime('now', '-30 minutes')
726
+ const peerDecisions = coordDb.prepare(`SELECT d.summary, a.name AS author_name, d.created_at
727
+ FROM coord_decisions d JOIN coord_agents a ON d.author_id = a.id
728
+ WHERE d.author_id != ? AND d.created_at > datetime('now', '-30 minutes')
664
729
  ORDER BY d.created_at DESC LIMIT 10`).all(myAgent?.id ?? '');
665
730
  if (peerDecisions.length > 0) {
666
731
  parts.push(`\n**Peer decisions (last 30 min):**`);
@@ -679,11 +744,11 @@ Use this at the start of every session or after compaction to pick up where you
679
744
  };
680
745
  });
681
746
  // --- Onboarding Tools (warm-start a cold store) ---
682
- server.tool('onboard_scan', `Scan a project's documentation + repository and return CANDIDATE memories to seed a cold store.
683
-
684
- Use this when the store is empty / you're new to a project. The scan is deterministic
685
- (real file contents, not guesses) — YOUR job is to refine the candidates into atomic,
686
- recall-shaped memories, run the interview (onboard_questions), confirm with the user, then
747
+ server.tool('onboard_scan', `Scan a project's documentation + repository and return CANDIDATE memories to seed a cold store.
748
+
749
+ Use this when the store is empty / you're new to a project. The scan is deterministic
750
+ (real file contents, not guesses) — YOUR job is to refine the candidates into atomic,
751
+ recall-shaped memories, run the interview (onboard_questions), confirm with the user, then
687
752
  save the good ones with memory_write (memory_class="canonical"). Nothing is saved by this tool.`, {
688
753
  docs: z.array(z.string()).optional()
689
754
  .describe('Doc files/dirs to scan (Markdown/text). Defaults to the repo (or cwd).'),
@@ -708,18 +773,18 @@ save the good ones with memory_write (memory_class="canonical"). Nothing is save
708
773
  ].join('\n');
709
774
  return { content: [{ type: 'text', text }] };
710
775
  });
711
- server.tool('onboard_questions', `Return the onboarding interview questions. Ask the user ONE at a time, starting with the
776
+ server.tool('onboard_questions', `Return the onboarding interview questions. Ask the user ONE at a time, starting with the
712
777
  goal of the memory system, and ask follow-ups for clarity. Turn each answer into a canonical memory.`, {}, async () => ({
713
778
  content: [{ type: 'text', text: INTERVIEW_QUESTIONS.map((q, i) => `${i + 1}. ${q}`).join('\n') }],
714
779
  }));
715
780
  // --- Task Management Tools ---
716
- server.tool('memory_task_add', `Create a task that you need to come back to. Tasks are memories with status and priority tracking.
717
-
718
- Use this when:
719
- - You identify work that needs doing but can't do it right now
720
- - The user mentions something to do later
721
- - You want to park a sub-task while focusing on something more urgent
722
-
781
+ server.tool('memory_task_add', `Create a task that you need to come back to. Tasks are memories with status and priority tracking.
782
+
783
+ Use this when:
784
+ - You identify work that needs doing but can't do it right now
785
+ - The user mentions something to do later
786
+ - You want to park a sub-task while focusing on something more urgent
787
+
723
788
  Tasks automatically get high salience so they won't be discarded.`, {
724
789
  concept: z.string().describe('Short task title (3-10 words)'),
725
790
  content: z.string().describe('Full task description — what needs doing, context, acceptance criteria'),
@@ -759,11 +824,11 @@ Tasks automatically get high salience so they won't be discarded.`, {
759
824
  }],
760
825
  };
761
826
  });
762
- server.tool('memory_task_update', `Update a task's status or priority. Use this to:
763
- - Start working on a task (open → in_progress)
764
- - Mark a task done (→ done)
765
- - Block a task on another (→ blocked)
766
- - Reprioritize (change priority)
827
+ server.tool('memory_task_update', `Update a task's status or priority. Use this to:
828
+ - Start working on a task (open → in_progress)
829
+ - Mark a task done (→ done)
830
+ - Block a task on another (→ blocked)
831
+ - Reprioritize (change priority)
767
832
  - Unblock a task (clear blocked_by)`, {
768
833
  task_id: z.string().describe('ID of the task to update'),
769
834
  status: z.enum(['open', 'in_progress', 'blocked', 'done']).optional()
@@ -793,8 +858,8 @@ Tasks automatically get high salience so they won't be discarded.`, {
793
858
  }],
794
859
  };
795
860
  });
796
- server.tool('memory_task_list', `List tasks with optional status filter. Shows tasks ordered by priority (urgent first).
797
-
861
+ server.tool('memory_task_list', `List tasks with optional status filter. Shows tasks ordered by priority (urgent first).
862
+
798
863
  Use at the start of a session to see what's pending, or to check blocked/done tasks.`, {
799
864
  status: z.enum(['open', 'in_progress', 'blocked', 'done']).optional()
800
865
  .describe('Filter by status (omit to see all active tasks)'),
@@ -820,10 +885,10 @@ Use at the start of a session to see what's pending, or to check blocked/done ta
820
885
  }],
821
886
  };
822
887
  });
823
- server.tool('memory_task_next', `Get the single most important task to work on next.
824
-
825
- Prioritizes: in_progress tasks first (finish what you started), then by priority level, then oldest first. Skips blocked and done tasks.
826
-
888
+ server.tool('memory_task_next', `Get the single most important task to work on next.
889
+
890
+ Prioritizes: in_progress tasks first (finish what you started), then by priority level, then oldest first. Skips blocked and done tasks.
891
+
827
892
  Use this when you finish a task or need to decide what to do next.`, {}, async () => {
828
893
  const next = await store.getNextTask(AGENT_ID);
829
894
  if (!next) {
@@ -839,13 +904,13 @@ Use this when you finish a task or need to decide what to do next.`, {}, async (
839
904
  };
840
905
  });
841
906
  // --- Task Bracket Tools ---
842
- server.tool('memory_task_begin', `Signal that you're starting a significant task. Auto-checkpoints current state and recalls relevant memories.
843
-
844
- CALL THIS when starting:
845
- - A multi-step operation (doc generation, large refactor, migration)
846
- - Work on a new topic or project area
847
- - Anything that might fill the context window
848
-
907
+ server.tool('memory_task_begin', `Signal that you're starting a significant task. Auto-checkpoints current state and recalls relevant memories.
908
+
909
+ CALL THIS when starting:
910
+ - A multi-step operation (doc generation, large refactor, migration)
911
+ - Work on a new topic or project area
912
+ - Anything that might fill the context window
913
+
849
914
  This ensures your state is saved before you start, and primes recall with relevant context.`, {
850
915
  topic: z.string().describe('What task are you starting? (3-15 words)'),
851
916
  files: z.array(z.string()).optional().default([])
@@ -896,13 +961,13 @@ This ensures your state is saved before you start, and primes recall with releva
896
961
  }],
897
962
  };
898
963
  });
899
- server.tool('memory_task_end', `Signal that you've finished a significant task. Writes a summary memory and auto-checkpoints.
900
-
901
- CALL THIS when you finish:
902
- - A multi-step operation
903
- - Before switching to a different topic
904
- - At the end of a work session
905
-
964
+ server.tool('memory_task_end', `Signal that you've finished a significant task. Writes a summary memory and auto-checkpoints.
965
+
966
+ CALL THIS when you finish:
967
+ - A multi-step operation
968
+ - Before switching to a different topic
969
+ - At the end of a work session
970
+
906
971
  This captures what was accomplished so future sessions can recall it.`, {
907
972
  summary: z.string().describe('What was accomplished? Include key outcomes, decisions, and any issues.'),
908
973
  tags: z.array(z.string()).optional().default([])
@@ -971,18 +1036,21 @@ This captures what was accomplished so future sessions can recall it.`, {
971
1036
  return {
972
1037
  content: [{
973
1038
  type: 'text',
974
- text: `Completed: "${completedTask}" [${salience.score.toFixed(2)}]${supersededNote}`,
1039
+ // D14 (2026-07-30): task end is the recipe moment — invite the host
1040
+ // to distill a skill and/or a failure lesson in separate focused
1041
+ // passes. The host owns the gates; AWM validates the write-backs.
1042
+ text: `Completed: "${completedTask}" [${salience.score.toFixed(2)}]${supersededNote}\n${renderTaskEndInvitation()}`,
975
1043
  }],
976
1044
  };
977
1045
  });
978
- server.tool('compress_output', `Compress a STRUCTURED tool output (JSON object/array, query rows, log records) into TOON —
979
- a compact, schema-aware tabular encoding — before putting it in your context. Cuts ~50-65%
980
- of the tokens on uniform arrays at zero comprehension cost (validated: models read TOON as
981
- accurately as JSON). Use this on large tool results you need to keep in context.
982
-
983
- Output-only and safe: it never changes the data. Non-JSON / prose is returned unchanged.
984
- TOON is only emitted when it reproduces the input exactly (self-verified round-trip);
985
- otherwise you get plain JSON back. When compressed, you also get a 'ref' — call
1046
+ server.tool('compress_output', `Compress a STRUCTURED tool output (JSON object/array, query rows, log records) into TOON —
1047
+ a compact, schema-aware tabular encoding — before putting it in your context. Cuts ~50-65%
1048
+ of the tokens on uniform arrays at zero comprehension cost (validated: models read TOON as
1049
+ accurately as JSON). Use this on large tool results you need to keep in context.
1050
+
1051
+ Output-only and safe: it never changes the data. Non-JSON / prose is returned unchanged.
1052
+ TOON is only emitted when it reproduces the input exactly (self-verified round-trip);
1053
+ otherwise you get plain JSON back. When compressed, you also get a 'ref' — call
986
1054
  retrieve_original(ref) to get the verbatim source back if you ever need it.`, {
987
1055
  output: z.string().describe('The tool output to compress — JSON text (preferred) or any string. Non-JSON is returned unchanged.'),
988
1056
  min_saving_chars: z.number().optional().describe('Only emit TOON if it saves at least this many characters (default 40).'),
@@ -996,8 +1064,8 @@ retrieve_original(ref) to get the verbatim source back if you ever need it.`, {
996
1064
  content: [{ type: 'text', text: header + r.text }],
997
1065
  };
998
1066
  });
999
- server.tool('retrieve_original', `Retrieve the verbatim original text for a 'ref' returned by compress_output. Use this when
1000
- you need the exact, uncompressed source (e.g. to pass it to another tool unchanged). Returns
1067
+ server.tool('retrieve_original', `Retrieve the verbatim original text for a 'ref' returned by compress_output. Use this when
1068
+ you need the exact, uncompressed source (e.g. to pass it to another tool unchanged). Returns
1001
1069
  an error if the ref has expired (originals are kept for the most recent compressions only).`, {
1002
1070
  ref: z.string().describe('The ref handle returned by compress_output (e.g. "awm_orig_12").'),
1003
1071
  }, async (params) => {