agent-working-memory 0.13.1 → 0.14.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.
- package/README.md +200 -238
- package/dist/adapters/common.d.ts +6 -0
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +457 -362
- package/dist/adapters/common.js.map +1 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +24 -8
- package/dist/api/routes.js.map +1 -1
- package/dist/core/alias-map.d.ts +16 -0
- package/dist/core/alias-map.d.ts.map +1 -0
- package/dist/core/alias-map.js +102 -0
- package/dist/core/alias-map.js.map +1 -0
- package/dist/core/embeddings.d.ts +17 -0
- package/dist/core/embeddings.d.ts.map +1 -1
- package/dist/core/embeddings.js +50 -1
- package/dist/core/embeddings.js.map +1 -1
- package/dist/core/recall-config.d.ts +52 -0
- package/dist/core/recall-config.d.ts.map +1 -0
- package/dist/core/recall-config.js +110 -0
- package/dist/core/recall-config.js.map +1 -0
- package/dist/core/rerank-window.d.ts +61 -0
- package/dist/core/rerank-window.d.ts.map +1 -0
- package/dist/core/rerank-window.js +153 -0
- package/dist/core/rerank-window.js.map +1 -0
- package/dist/core/rerank2.d.ts +62 -0
- package/dist/core/rerank2.d.ts.map +1 -0
- package/dist/core/rerank2.js +75 -0
- package/dist/core/rerank2.js.map +1 -0
- package/dist/core/retrieval-text.d.ts +55 -0
- package/dist/core/retrieval-text.d.ts.map +1 -0
- package/dist/core/retrieval-text.js +87 -0
- package/dist/core/retrieval-text.js.map +1 -0
- package/dist/core/temporal-query.d.ts +61 -0
- package/dist/core/temporal-query.d.ts.map +1 -0
- package/dist/core/temporal-query.js +168 -0
- package/dist/core/temporal-query.js.map +1 -0
- package/dist/core/token-budget.d.ts +75 -0
- package/dist/core/token-budget.d.ts.map +1 -0
- package/dist/core/token-budget.js +136 -0
- package/dist/core/token-budget.js.map +1 -0
- package/dist/core/whoami.d.ts +11 -0
- package/dist/core/whoami.d.ts.map +1 -1
- package/dist/core/whoami.js +10 -0
- package/dist/core/whoami.js.map +1 -1
- package/dist/core/write-pipeline.d.ts.map +1 -1
- package/dist/core/write-pipeline.js +6 -3
- package/dist/core/write-pipeline.js.map +1 -1
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +146 -32
- package/dist/engine/activation.js.map +1 -1
- package/dist/hooks/prime.d.ts +77 -0
- package/dist/hooks/prime.d.ts.map +1 -0
- package/dist/hooks/prime.js +92 -0
- package/dist/hooks/prime.js.map +1 -0
- package/dist/hooks/sidecar.d.ts.map +1 -1
- package/dist/hooks/sidecar.js +39 -0
- package/dist/hooks/sidecar.js.map +1 -1
- package/dist/mcp.js +160 -107
- package/dist/mcp.js.map +1 -1
- package/dist/storage/pglite.d.ts.map +1 -1
- package/dist/storage/pglite.js +10 -2
- package/dist/storage/pglite.js.map +1 -1
- package/dist/storage/postgres.d.ts.map +1 -1
- package/dist/storage/postgres.js +10 -2
- package/dist/storage/postgres.js.map +1 -1
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +12 -2
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/types/engram.d.ts +31 -0
- package/dist/types/engram.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/adapters/common.ts +666 -567
- package/src/api/routes.ts +1015 -999
- package/src/core/alias-map.ts +97 -0
- package/src/core/embeddings.ts +172 -115
- package/src/core/recall-config.ts +115 -0
- package/src/core/rerank-window.ts +158 -0
- package/src/core/rerank2.ts +82 -0
- package/src/core/retrieval-text.ts +82 -0
- package/src/core/temporal-query.ts +193 -0
- package/src/core/token-budget.ts +160 -0
- package/src/core/whoami.ts +110 -92
- package/src/core/write-pipeline.ts +6 -3
- package/src/engine/activation.ts +1579 -1468
- package/src/hooks/prime.ts +136 -0
- package/src/hooks/sidecar.ts +43 -0
- package/src/mcp.ts +1443 -1387
- package/src/storage/pglite.ts +10 -2
- package/src/storage/postgres.ts +10 -2
- package/src/storage/sqlite.ts +12 -2
- package/src/types/engram.ts +32 -0
package/dist/mcp.js
CHANGED
|
@@ -80,6 +80,7 @@ import { startLoopLagMonitor } from './core/write-telemetry.js';
|
|
|
80
80
|
import { buildWhoami, formatWhoami } from './core/whoami.js';
|
|
81
81
|
import { renderTaskEndInvitation, validateRecipeWrite, recipeSlug, getRecipe } from './recipes/index.js';
|
|
82
82
|
import { formatRecallResultLine } from './core/format-recall.js';
|
|
83
|
+
import { packRecallByBudget, formatTokenFooter, estimateTokens } from './core/token-budget.js';
|
|
83
84
|
// --- Incognito Mode ---
|
|
84
85
|
// When AWM_INCOGNITO=1, register zero tools. Claude won't see memory tools at all.
|
|
85
86
|
// No DB, no engines, no sidecar — just a bare MCP server that exposes nothing.
|
|
@@ -196,15 +197,15 @@ else {
|
|
|
196
197
|
return 'unclassified';
|
|
197
198
|
}
|
|
198
199
|
// --- Tools ---
|
|
199
|
-
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.
|
|
200
|
-
|
|
201
|
-
CALL THIS PROACTIVELY — do not wait to be asked. Write memories when you:
|
|
202
|
-
- Discover something about the codebase, bugs, or architecture
|
|
203
|
-
- Make a decision and want to remember why
|
|
204
|
-
- Encounter and resolve an error
|
|
205
|
-
- Learn a user preference or project pattern
|
|
206
|
-
- Complete a significant piece of work
|
|
207
|
-
|
|
200
|
+
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.
|
|
201
|
+
|
|
202
|
+
CALL THIS PROACTIVELY — do not wait to be asked. Write memories when you:
|
|
203
|
+
- Discover something about the codebase, bugs, or architecture
|
|
204
|
+
- Make a decision and want to remember why
|
|
205
|
+
- Encounter and resolve an error
|
|
206
|
+
- Learn a user preference or project pattern
|
|
207
|
+
- Complete a significant piece of work
|
|
208
|
+
|
|
208
209
|
The concept should be a short label (3-8 words). The content should be the full detail.`, {
|
|
209
210
|
concept: z.string().describe('Short label for this memory (3-8 words)'),
|
|
210
211
|
content: z.string().describe('Full detail of what was learned'),
|
|
@@ -355,27 +356,28 @@ The concept should be a short label (3-8 words). The content should be the full
|
|
|
355
356
|
}],
|
|
356
357
|
};
|
|
357
358
|
});
|
|
358
|
-
server.tool('memory_recall', `Recall memories relevant to a query. Uses cognitive activation — not keyword search.
|
|
359
|
-
|
|
360
|
-
ALWAYS call this when:
|
|
361
|
-
- Starting work on a project or topic (recall what you know)
|
|
362
|
-
- Debugging (recall similar errors and solutions)
|
|
363
|
-
- Making decisions (recall past decisions and outcomes)
|
|
364
|
-
- The user mentions a topic you might have stored memories about
|
|
365
|
-
|
|
366
|
-
Accepts either "query" or "context" parameter — both work identically.
|
|
359
|
+
server.tool('memory_recall', `Recall memories relevant to a query. Uses cognitive activation — not keyword search.
|
|
360
|
+
|
|
361
|
+
ALWAYS call this when:
|
|
362
|
+
- Starting work on a project or topic (recall what you know)
|
|
363
|
+
- Debugging (recall similar errors and solutions)
|
|
364
|
+
- Making decisions (recall past decisions and outcomes)
|
|
365
|
+
- The user mentions a topic you might have stored memories about
|
|
366
|
+
|
|
367
|
+
Accepts either "query" or "context" parameter — both work identically.
|
|
367
368
|
Returns the most relevant memories ranked by text relevance, temporal recency, and associative strength.`, {
|
|
368
369
|
query: z.string().optional().describe('What to search for — describe the situation, question, or topic'),
|
|
369
370
|
context: z.string().optional().describe('Alias for query (either works)'),
|
|
370
|
-
limit: z.number().optional().default(
|
|
371
|
+
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 and rerank is ~90% 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 optimise for.'),
|
|
371
372
|
min_score: z.number().optional().default(0.05).describe('Minimum relevance score (default 0.05)'),
|
|
372
373
|
include_staging: z.boolean().optional().default(false).describe('Include weak/unconfirmed memories?'),
|
|
373
374
|
use_reranker: z.boolean().optional().default(true).describe('Use cross-encoder re-ranking for better relevance (default true)'),
|
|
374
375
|
use_expansion: z.boolean().optional().default(true).describe('Expand query with synonyms for better recall (default true)'),
|
|
375
376
|
memory_type: z.enum(['episodic', 'semantic', 'procedural']).optional().describe('Filter by memory type (omit to search all types)'),
|
|
376
377
|
workspace: z.string().optional().describe('Search across all agents in this workspace (hive mode). Omit for agent-scoped recall only.'),
|
|
377
|
-
require_confidence: z.number().optional().describe('
|
|
378
|
+
require_confidence: z.number().optional().default(0.05).describe('Abstain (return nothing) when recall confidence is below this threshold. Defaults to 0.05 — a LIGHT filter, chosen from measurement: it halves the rate of answering off-topic queries at zero cost to hit rate. Raise it only for push-style use where nobody asked (0.25 is what the prime hook uses). Do NOT raise it for ordinary recall: a miss is expensive, because the agent then reads the codebase instead (~2,106 tokens), so aggressive thresholds measurably destroy value — 0.20+ cut net tokens saved by 25% in tests/abstention-eval. Pass 0 to disable. NOTE: this is NOT min_score — it gates on the SHAPE of the score distribution across the whole result set, not per-result relevance, so memories that pass min_score can still be withheld. When that happens the reply says RECALL ABSTAINED and reports how many were withheld; an empty result is only absence when it does not.'),
|
|
378
379
|
granularity: z.enum(['full', 'compact', 'auto']).optional().describe('Output granularity (Paper 3: cognitive teaming). "full" (default): no change. "compact": every result carries a short summary field. "auto": confidence-adaptive — top result gets a longer summary when there is a clear winner, otherwise everything is compact for scanning.'),
|
|
380
|
+
max_tokens: z.number().optional().describe('Token budget for the response. `limit` is a COUNT and is token-blind — 5 results may cost 400 tokens or 4,000. Use this when context is tight: results are packed by value-per-token until the budget is reached, the top-scored match always gets first refusal, and the reply reports what it cost and what was withheld. Omit for no budget (everything is returned, still with accounting).'),
|
|
379
381
|
}, async (params) => {
|
|
380
382
|
const queryText = params.query ?? params.context;
|
|
381
383
|
if (!queryText) {
|
|
@@ -388,6 +390,9 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
|
|
|
388
390
|
}
|
|
389
391
|
// Use workspace from param, env var, or omit for agent-scoped
|
|
390
392
|
const workspace = params.workspace ?? process.env.AWM_WORKSPACE ?? undefined;
|
|
393
|
+
// Set only when a gate WITHHELD results. Distinguishes abstention from absence,
|
|
394
|
+
// which an empty array cannot.
|
|
395
|
+
let abstained;
|
|
391
396
|
const results = await activationEngine.activate({
|
|
392
397
|
agentId: AGENT_ID,
|
|
393
398
|
context: queryText,
|
|
@@ -400,6 +405,7 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
|
|
|
400
405
|
workspace,
|
|
401
406
|
requireConfidence: params.require_confidence,
|
|
402
407
|
granularity: params.granularity,
|
|
408
|
+
onAbstain: (info) => { abstained = info; },
|
|
403
409
|
});
|
|
404
410
|
// Auto-checkpoint: track recall
|
|
405
411
|
try {
|
|
@@ -413,27 +419,69 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
|
|
|
413
419
|
? formatPeerDecisions(queryPeerDecisions(coordDb, AGENT_ID, queryText))
|
|
414
420
|
: '';
|
|
415
421
|
if (results.length === 0) {
|
|
422
|
+
// Two very different conditions used to render identically as
|
|
423
|
+
// "No relevant memories found." — a claim of absence the system cannot make
|
|
424
|
+
// when a gate withheld matches. Callers read it as absence and stopped
|
|
425
|
+
// looking, for memories scoring well above minScore.
|
|
426
|
+
const text = abstained
|
|
427
|
+
? [
|
|
428
|
+
`RECALL ABSTAINED — this is NOT "no memories exist".`,
|
|
429
|
+
``,
|
|
430
|
+
`${abstained.candidates} candidate${abstained.candidates === 1 ? '' : 's'} matched ` +
|
|
431
|
+
`(best score ${abstained.topScore.toFixed(3)}) and ${abstained.candidates === 1 ? 'was' : 'were'} ` +
|
|
432
|
+
`withheld because recall confidence ` +
|
|
433
|
+
`${abstained.confidence !== undefined ? abstained.confidence.toFixed(3) + ' ' : ''}` +
|
|
434
|
+
`fell below your require_confidence of ${abstained.threshold ?? '?'}.`,
|
|
435
|
+
``,
|
|
436
|
+
`require_confidence is NOT min_score. It gates on the SHAPE of the score` +
|
|
437
|
+
` distribution across the whole result set, not on how relevant any single` +
|
|
438
|
+
` memory is — so results that comfortably pass min_score can still be withheld here.`,
|
|
439
|
+
``,
|
|
440
|
+
`To see them: re-run this query with require_confidence: 0.`,
|
|
441
|
+
`Do not conclude the memories are absent without doing that.`,
|
|
442
|
+
].join('\n') + peerSuffix
|
|
443
|
+
: 'No relevant memories found.' + peerSuffix;
|
|
444
|
+
return { content: [{ type: 'text', text }] };
|
|
445
|
+
}
|
|
446
|
+
// Confidence-adaptive output (Paper 3: cognitive teaming) and D8
|
|
447
|
+
// (2026-07-30) conflict surfacing both live in the shared formatter now —
|
|
448
|
+
// see core/format-recall.ts for why it's extracted (0.12.1: unit-testable
|
|
449
|
+
// without booting the server) and why the id sits after the score.
|
|
450
|
+
// 0.13.3: pack to a token budget (if given) and always report the spend.
|
|
451
|
+
// `limit` is a count and is token-blind; max_tokens bounds what this call
|
|
452
|
+
// actually costs the caller's context. See core/token-budget.ts for why the
|
|
453
|
+
// top-scored result gets first refusal rather than pure density packing.
|
|
454
|
+
// Reserve what the rest of the reply will cost, so max_tokens bounds the
|
|
455
|
+
// WHOLE response and not just the result lines. FOOTER_ALLOWANCE is a
|
|
456
|
+
// fixed upper bound on the accounting line (measured at ~15-25 tokens; the
|
|
457
|
+
// unit test asserts it stays under 40).
|
|
458
|
+
const FOOTER_ALLOWANCE = 40;
|
|
459
|
+
const reserved = estimateTokens(peerSuffix) + FOOTER_ALLOWANCE;
|
|
460
|
+
const packed = packRecallByBudget(results, formatRecallResultLine, params.max_tokens, reserved);
|
|
461
|
+
// A budget too small to admit even the best match must not look like "no
|
|
462
|
+
// memories found" — that is a different fact and would send the caller off
|
|
463
|
+
// to read code that AWM could have answered. Say what actually happened so
|
|
464
|
+
// the fix (raise the budget) is obvious.
|
|
465
|
+
if (packed.kept === 0 && packed.total > 0) {
|
|
416
466
|
return {
|
|
417
467
|
content: [{
|
|
418
468
|
type: 'text',
|
|
419
|
-
text:
|
|
469
|
+
text: `${packed.total} memories matched, but none fit a ${params.max_tokens}-token budget `
|
|
470
|
+
+ `(smallest result is ~${packed.withheldTokens} tok across ${packed.total}). `
|
|
471
|
+
+ `Raise max_tokens, or use granularity: 'compact' to shrink each result.`
|
|
472
|
+
+ peerSuffix,
|
|
420
473
|
}],
|
|
421
474
|
};
|
|
422
475
|
}
|
|
423
|
-
// Confidence-adaptive output (Paper 3: cognitive teaming) and D8
|
|
424
|
-
// (2026-07-30) conflict surfacing both live in the shared formatter now —
|
|
425
|
-
// see core/format-recall.ts for why it's extracted (0.12.1: unit-testable
|
|
426
|
-
// without booting the server) and why the id sits after the score.
|
|
427
|
-
const lines = results.map(formatRecallResultLine);
|
|
428
476
|
return {
|
|
429
477
|
content: [{
|
|
430
478
|
type: 'text',
|
|
431
|
-
text: lines.join('\n') + peerSuffix,
|
|
479
|
+
text: packed.lines.join('\n') + peerSuffix + formatTokenFooter(packed, params.max_tokens),
|
|
432
480
|
}],
|
|
433
481
|
};
|
|
434
482
|
});
|
|
435
|
-
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.
|
|
436
|
-
|
|
483
|
+
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.
|
|
484
|
+
|
|
437
485
|
Always call this after using a recalled memory so the system learns what's valuable.`, {
|
|
438
486
|
engram_id: z.string().describe('ID of the memory (from memory_recall results)'),
|
|
439
487
|
useful: z.boolean().describe('Was this memory actually helpful?'),
|
|
@@ -456,8 +504,8 @@ Always call this after using a recalled memory so the system learns what's valua
|
|
|
456
504
|
}],
|
|
457
505
|
};
|
|
458
506
|
});
|
|
459
|
-
server.tool('memory_retract', `Retract a memory that turned out to be wrong. Creates a correction and reduces confidence of related memories.
|
|
460
|
-
|
|
507
|
+
server.tool('memory_retract', `Retract a memory that turned out to be wrong. Creates a correction and reduces confidence of related memories.
|
|
508
|
+
|
|
461
509
|
Use this when you discover a memory contains incorrect information.`, {
|
|
462
510
|
engram_id: z.string().describe('ID of the wrong memory'),
|
|
463
511
|
reason: z.string().describe('Why is this memory wrong?'),
|
|
@@ -481,13 +529,13 @@ Use this when you discover a memory contains incorrect information.`, {
|
|
|
481
529
|
}],
|
|
482
530
|
};
|
|
483
531
|
});
|
|
484
|
-
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.
|
|
485
|
-
|
|
486
|
-
Use this when:
|
|
487
|
-
- A status or count has changed (e.g., "5 reviews done" → "7 reviews done")
|
|
488
|
-
- Architecture or infrastructure evolved (e.g., "two-repo model" → "three-repo model")
|
|
489
|
-
- A schedule or plan was updated
|
|
490
|
-
|
|
532
|
+
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.
|
|
533
|
+
|
|
534
|
+
Use this when:
|
|
535
|
+
- A status or count has changed (e.g., "5 reviews done" → "7 reviews done")
|
|
536
|
+
- Architecture or infrastructure evolved (e.g., "two-repo model" → "three-repo model")
|
|
537
|
+
- A schedule or plan was updated
|
|
538
|
+
|
|
491
539
|
The old memory stays in the database (searchable for history) but is heavily down-ranked in recall so the current version dominates.`, {
|
|
492
540
|
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)'),
|
|
493
541
|
new_engram_id: z.string().describe('ID of the replacement memory'),
|
|
@@ -518,7 +566,7 @@ The old memory stays in the database (searchable for history) but is heavily dow
|
|
|
518
566
|
const info = await buildWhoami(store, AGENT_ID, 'mcp');
|
|
519
567
|
return { content: [{ type: 'text', text: formatWhoami(info) }] };
|
|
520
568
|
});
|
|
521
|
-
server.tool('memory_stats', `Get memory health stats — how many memories, confidence levels, association count, and system performance.
|
|
569
|
+
server.tool('memory_stats', `Get memory health stats — how many memories, confidence levels, association count, and system performance.
|
|
522
570
|
Also shows the activity log path so the user can tail it to see what's happening.`, {}, async () => {
|
|
523
571
|
const metrics = await evalEngine.computeMetrics(AGENT_ID);
|
|
524
572
|
const checkpoint = await store.getCheckpoint(AGENT_ID);
|
|
@@ -549,13 +597,13 @@ Also shows the activity log path so the user can tail it to see what's happening
|
|
|
549
597
|
};
|
|
550
598
|
});
|
|
551
599
|
// --- Checkpointing Tools ---
|
|
552
|
-
server.tool('memory_checkpoint', `Save your current execution state so you can recover after context compaction.
|
|
553
|
-
|
|
554
|
-
ALWAYS call this before:
|
|
555
|
-
- Long operations (multi-file generation, large refactors, overnight work)
|
|
556
|
-
- Anything that might fill the context window
|
|
557
|
-
- Switching to a different task
|
|
558
|
-
|
|
600
|
+
server.tool('memory_checkpoint', `Save your current execution state so you can recover after context compaction.
|
|
601
|
+
|
|
602
|
+
ALWAYS call this before:
|
|
603
|
+
- Long operations (multi-file generation, large refactors, overnight work)
|
|
604
|
+
- Anything that might fill the context window
|
|
605
|
+
- Switching to a different task
|
|
606
|
+
|
|
559
607
|
Also call periodically during long sessions to avoid losing state. The state is saved per-agent and overwrites any previous checkpoint.`, {
|
|
560
608
|
current_task: z.string().describe('What you are currently working on'),
|
|
561
609
|
decisions: z.array(z.string()).optional().default([])
|
|
@@ -589,14 +637,14 @@ Also call periodically during long sessions to avoid losing state. The state is
|
|
|
589
637
|
}],
|
|
590
638
|
};
|
|
591
639
|
});
|
|
592
|
-
server.tool('memory_restore', `Restore your previous execution state after context compaction or at session start.
|
|
593
|
-
|
|
594
|
-
Returns:
|
|
595
|
-
- Your saved execution state (task, decisions, next steps, files)
|
|
596
|
-
- Recently recalled memories for context
|
|
597
|
-
- Your last write for continuity
|
|
598
|
-
- How long you were idle
|
|
599
|
-
|
|
640
|
+
server.tool('memory_restore', `Restore your previous execution state after context compaction or at session start.
|
|
641
|
+
|
|
642
|
+
Returns:
|
|
643
|
+
- Your saved execution state (task, decisions, next steps, files)
|
|
644
|
+
- Recently recalled memories for context
|
|
645
|
+
- Your last write for continuity
|
|
646
|
+
- How long you were idle
|
|
647
|
+
|
|
600
648
|
Use this at the start of every session or after compaction to pick up where you left off.`, {}, async () => {
|
|
601
649
|
const checkpoint = await store.getCheckpoint(AGENT_ID);
|
|
602
650
|
// Cold-store nudge: an empty store means the agent has nothing to recall — offer to warm-start.
|
|
@@ -630,7 +678,11 @@ Use this at the start of every session or after compaction to pick up where you
|
|
|
630
678
|
const results = await activationEngine.activate({
|
|
631
679
|
agentId: AGENT_ID,
|
|
632
680
|
context: recallContext,
|
|
633
|
-
|
|
681
|
+
// 3, not 5 — measured token-positive (+115/recall vs -680) and 41% faster.
|
|
682
|
+
// The case is stronger here than for explicit recall: this is push-style, so
|
|
683
|
+
// nobody asked for these results and every one of them costs context the user
|
|
684
|
+
// did not request.
|
|
685
|
+
limit: 3,
|
|
634
686
|
minScore: 0.05,
|
|
635
687
|
useReranker: true,
|
|
636
688
|
useExpansion: true,
|
|
@@ -715,9 +767,9 @@ Use this at the start of every session or after compaction to pick up where you
|
|
|
715
767
|
if (coordDb) {
|
|
716
768
|
try {
|
|
717
769
|
const myAgent = coordDb.prepare(`SELECT id FROM coord_agents WHERE name = ? AND status != 'dead' ORDER BY last_seen DESC LIMIT 1`).get(AGENT_ID);
|
|
718
|
-
const peerDecisions = coordDb.prepare(`SELECT d.summary, a.name AS author_name, d.created_at
|
|
719
|
-
FROM coord_decisions d JOIN coord_agents a ON d.author_id = a.id
|
|
720
|
-
WHERE d.author_id != ? AND d.created_at > datetime('now', '-30 minutes')
|
|
770
|
+
const peerDecisions = coordDb.prepare(`SELECT d.summary, a.name AS author_name, d.created_at
|
|
771
|
+
FROM coord_decisions d JOIN coord_agents a ON d.author_id = a.id
|
|
772
|
+
WHERE d.author_id != ? AND d.created_at > datetime('now', '-30 minutes')
|
|
721
773
|
ORDER BY d.created_at DESC LIMIT 10`).all(myAgent?.id ?? '');
|
|
722
774
|
if (peerDecisions.length > 0) {
|
|
723
775
|
parts.push(`\n**Peer decisions (last 30 min):**`);
|
|
@@ -736,11 +788,11 @@ Use this at the start of every session or after compaction to pick up where you
|
|
|
736
788
|
};
|
|
737
789
|
});
|
|
738
790
|
// --- Onboarding Tools (warm-start a cold store) ---
|
|
739
|
-
server.tool('onboard_scan', `Scan a project's documentation + repository and return CANDIDATE memories to seed a cold store.
|
|
740
|
-
|
|
741
|
-
Use this when the store is empty / you're new to a project. The scan is deterministic
|
|
742
|
-
(real file contents, not guesses) — YOUR job is to refine the candidates into atomic,
|
|
743
|
-
recall-shaped memories, run the interview (onboard_questions), confirm with the user, then
|
|
791
|
+
server.tool('onboard_scan', `Scan a project's documentation + repository and return CANDIDATE memories to seed a cold store.
|
|
792
|
+
|
|
793
|
+
Use this when the store is empty / you're new to a project. The scan is deterministic
|
|
794
|
+
(real file contents, not guesses) — YOUR job is to refine the candidates into atomic,
|
|
795
|
+
recall-shaped memories, run the interview (onboard_questions), confirm with the user, then
|
|
744
796
|
save the good ones with memory_write (memory_class="canonical"). Nothing is saved by this tool.`, {
|
|
745
797
|
docs: z.array(z.string()).optional()
|
|
746
798
|
.describe('Doc files/dirs to scan (Markdown/text). Defaults to the repo (or cwd).'),
|
|
@@ -765,18 +817,18 @@ save the good ones with memory_write (memory_class="canonical"). Nothing is save
|
|
|
765
817
|
].join('\n');
|
|
766
818
|
return { content: [{ type: 'text', text }] };
|
|
767
819
|
});
|
|
768
|
-
server.tool('onboard_questions', `Return the onboarding interview questions. Ask the user ONE at a time, starting with the
|
|
820
|
+
server.tool('onboard_questions', `Return the onboarding interview questions. Ask the user ONE at a time, starting with the
|
|
769
821
|
goal of the memory system, and ask follow-ups for clarity. Turn each answer into a canonical memory.`, {}, async () => ({
|
|
770
822
|
content: [{ type: 'text', text: INTERVIEW_QUESTIONS.map((q, i) => `${i + 1}. ${q}`).join('\n') }],
|
|
771
823
|
}));
|
|
772
824
|
// --- Task Management Tools ---
|
|
773
|
-
server.tool('memory_task_add', `Create a task that you need to come back to. Tasks are memories with status and priority tracking.
|
|
774
|
-
|
|
775
|
-
Use this when:
|
|
776
|
-
- You identify work that needs doing but can't do it right now
|
|
777
|
-
- The user mentions something to do later
|
|
778
|
-
- You want to park a sub-task while focusing on something more urgent
|
|
779
|
-
|
|
825
|
+
server.tool('memory_task_add', `Create a task that you need to come back to. Tasks are memories with status and priority tracking.
|
|
826
|
+
|
|
827
|
+
Use this when:
|
|
828
|
+
- You identify work that needs doing but can't do it right now
|
|
829
|
+
- The user mentions something to do later
|
|
830
|
+
- You want to park a sub-task while focusing on something more urgent
|
|
831
|
+
|
|
780
832
|
Tasks automatically get high salience so they won't be discarded.`, {
|
|
781
833
|
concept: z.string().describe('Short task title (3-10 words)'),
|
|
782
834
|
content: z.string().describe('Full task description — what needs doing, context, acceptance criteria'),
|
|
@@ -816,11 +868,11 @@ Tasks automatically get high salience so they won't be discarded.`, {
|
|
|
816
868
|
}],
|
|
817
869
|
};
|
|
818
870
|
});
|
|
819
|
-
server.tool('memory_task_update', `Update a task's status or priority. Use this to:
|
|
820
|
-
- Start working on a task (open → in_progress)
|
|
821
|
-
- Mark a task done (→ done)
|
|
822
|
-
- Block a task on another (→ blocked)
|
|
823
|
-
- Reprioritize (change priority)
|
|
871
|
+
server.tool('memory_task_update', `Update a task's status or priority. Use this to:
|
|
872
|
+
- Start working on a task (open → in_progress)
|
|
873
|
+
- Mark a task done (→ done)
|
|
874
|
+
- Block a task on another (→ blocked)
|
|
875
|
+
- Reprioritize (change priority)
|
|
824
876
|
- Unblock a task (clear blocked_by)`, {
|
|
825
877
|
task_id: z.string().describe('ID of the task to update'),
|
|
826
878
|
status: z.enum(['open', 'in_progress', 'blocked', 'done']).optional()
|
|
@@ -850,8 +902,8 @@ Tasks automatically get high salience so they won't be discarded.`, {
|
|
|
850
902
|
}],
|
|
851
903
|
};
|
|
852
904
|
});
|
|
853
|
-
server.tool('memory_task_list', `List tasks with optional status filter. Shows tasks ordered by priority (urgent first).
|
|
854
|
-
|
|
905
|
+
server.tool('memory_task_list', `List tasks with optional status filter. Shows tasks ordered by priority (urgent first).
|
|
906
|
+
|
|
855
907
|
Use at the start of a session to see what's pending, or to check blocked/done tasks.`, {
|
|
856
908
|
status: z.enum(['open', 'in_progress', 'blocked', 'done']).optional()
|
|
857
909
|
.describe('Filter by status (omit to see all active tasks)'),
|
|
@@ -877,10 +929,10 @@ Use at the start of a session to see what's pending, or to check blocked/done ta
|
|
|
877
929
|
}],
|
|
878
930
|
};
|
|
879
931
|
});
|
|
880
|
-
server.tool('memory_task_next', `Get the single most important task to work on next.
|
|
881
|
-
|
|
882
|
-
Prioritizes: in_progress tasks first (finish what you started), then by priority level, then oldest first. Skips blocked and done tasks.
|
|
883
|
-
|
|
932
|
+
server.tool('memory_task_next', `Get the single most important task to work on next.
|
|
933
|
+
|
|
934
|
+
Prioritizes: in_progress tasks first (finish what you started), then by priority level, then oldest first. Skips blocked and done tasks.
|
|
935
|
+
|
|
884
936
|
Use this when you finish a task or need to decide what to do next.`, {}, async () => {
|
|
885
937
|
const next = await store.getNextTask(AGENT_ID);
|
|
886
938
|
if (!next) {
|
|
@@ -896,13 +948,13 @@ Use this when you finish a task or need to decide what to do next.`, {}, async (
|
|
|
896
948
|
};
|
|
897
949
|
});
|
|
898
950
|
// --- Task Bracket Tools ---
|
|
899
|
-
server.tool('memory_task_begin', `Signal that you're starting a significant task. Auto-checkpoints current state and recalls relevant memories.
|
|
900
|
-
|
|
901
|
-
CALL THIS when starting:
|
|
902
|
-
- A multi-step operation (doc generation, large refactor, migration)
|
|
903
|
-
- Work on a new topic or project area
|
|
904
|
-
- Anything that might fill the context window
|
|
905
|
-
|
|
951
|
+
server.tool('memory_task_begin', `Signal that you're starting a significant task. Auto-checkpoints current state and recalls relevant memories.
|
|
952
|
+
|
|
953
|
+
CALL THIS when starting:
|
|
954
|
+
- A multi-step operation (doc generation, large refactor, migration)
|
|
955
|
+
- Work on a new topic or project area
|
|
956
|
+
- Anything that might fill the context window
|
|
957
|
+
|
|
906
958
|
This ensures your state is saved before you start, and primes recall with relevant context.`, {
|
|
907
959
|
topic: z.string().describe('What task are you starting? (3-15 words)'),
|
|
908
960
|
files: z.array(z.string()).optional().default([])
|
|
@@ -928,7 +980,8 @@ This ensures your state is saved before you start, and primes recall with releva
|
|
|
928
980
|
const results = await activationEngine.activate({
|
|
929
981
|
agentId: AGENT_ID,
|
|
930
982
|
context: params.topic,
|
|
931
|
-
|
|
983
|
+
// 3, not 5 — see memory_recall. Push-style auto-recall on task start.
|
|
984
|
+
limit: 3,
|
|
932
985
|
minScore: 0.05,
|
|
933
986
|
useReranker: true,
|
|
934
987
|
useExpansion: true,
|
|
@@ -953,13 +1006,13 @@ This ensures your state is saved before you start, and primes recall with releva
|
|
|
953
1006
|
}],
|
|
954
1007
|
};
|
|
955
1008
|
});
|
|
956
|
-
server.tool('memory_task_end', `Signal that you've finished a significant task. Writes a summary memory and auto-checkpoints.
|
|
957
|
-
|
|
958
|
-
CALL THIS when you finish:
|
|
959
|
-
- A multi-step operation
|
|
960
|
-
- Before switching to a different topic
|
|
961
|
-
- At the end of a work session
|
|
962
|
-
|
|
1009
|
+
server.tool('memory_task_end', `Signal that you've finished a significant task. Writes a summary memory and auto-checkpoints.
|
|
1010
|
+
|
|
1011
|
+
CALL THIS when you finish:
|
|
1012
|
+
- A multi-step operation
|
|
1013
|
+
- Before switching to a different topic
|
|
1014
|
+
- At the end of a work session
|
|
1015
|
+
|
|
963
1016
|
This captures what was accomplished so future sessions can recall it.`, {
|
|
964
1017
|
summary: z.string().describe('What was accomplished? Include key outcomes, decisions, and any issues.'),
|
|
965
1018
|
tags: z.array(z.string()).optional().default([])
|
|
@@ -1035,14 +1088,14 @@ This captures what was accomplished so future sessions can recall it.`, {
|
|
|
1035
1088
|
}],
|
|
1036
1089
|
};
|
|
1037
1090
|
});
|
|
1038
|
-
server.tool('compress_output', `Compress a STRUCTURED tool output (JSON object/array, query rows, log records) into TOON —
|
|
1039
|
-
a compact, schema-aware tabular encoding — before putting it in your context. Cuts ~50-65%
|
|
1040
|
-
of the tokens on uniform arrays at zero comprehension cost (validated: models read TOON as
|
|
1041
|
-
accurately as JSON). Use this on large tool results you need to keep in context.
|
|
1042
|
-
|
|
1043
|
-
Output-only and safe: it never changes the data. Non-JSON / prose is returned unchanged.
|
|
1044
|
-
TOON is only emitted when it reproduces the input exactly (self-verified round-trip);
|
|
1045
|
-
otherwise you get plain JSON back. When compressed, you also get a 'ref' — call
|
|
1091
|
+
server.tool('compress_output', `Compress a STRUCTURED tool output (JSON object/array, query rows, log records) into TOON —
|
|
1092
|
+
a compact, schema-aware tabular encoding — before putting it in your context. Cuts ~50-65%
|
|
1093
|
+
of the tokens on uniform arrays at zero comprehension cost (validated: models read TOON as
|
|
1094
|
+
accurately as JSON). Use this on large tool results you need to keep in context.
|
|
1095
|
+
|
|
1096
|
+
Output-only and safe: it never changes the data. Non-JSON / prose is returned unchanged.
|
|
1097
|
+
TOON is only emitted when it reproduces the input exactly (self-verified round-trip);
|
|
1098
|
+
otherwise you get plain JSON back. When compressed, you also get a 'ref' — call
|
|
1046
1099
|
retrieve_original(ref) to get the verbatim source back if you ever need it.`, {
|
|
1047
1100
|
output: z.string().describe('The tool output to compress — JSON text (preferred) or any string. Non-JSON is returned unchanged.'),
|
|
1048
1101
|
min_saving_chars: z.number().optional().describe('Only emit TOON if it saves at least this many characters (default 40).'),
|
|
@@ -1056,8 +1109,8 @@ retrieve_original(ref) to get the verbatim source back if you ever need it.`, {
|
|
|
1056
1109
|
content: [{ type: 'text', text: header + r.text }],
|
|
1057
1110
|
};
|
|
1058
1111
|
});
|
|
1059
|
-
server.tool('retrieve_original', `Retrieve the verbatim original text for a 'ref' returned by compress_output. Use this when
|
|
1060
|
-
you need the exact, uncompressed source (e.g. to pass it to another tool unchanged). Returns
|
|
1112
|
+
server.tool('retrieve_original', `Retrieve the verbatim original text for a 'ref' returned by compress_output. Use this when
|
|
1113
|
+
you need the exact, uncompressed source (e.g. to pass it to another tool unchanged). Returns
|
|
1061
1114
|
an error if the ref has expired (originals are kept for the most recent compressions only).`, {
|
|
1062
1115
|
ref: z.string().describe('The ref handle returned by compress_output (e.g. "awm_orig_12").'),
|
|
1063
1116
|
}, async (params) => {
|