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.
- package/README.md +8 -5
- package/dist/adapters/common.d.ts +34 -0
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +528 -446
- package/dist/adapters/common.js.map +1 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +19 -0
- package/dist/api/routes.js.map +1 -1
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/migrate.js +31 -29
- package/dist/cli/migrate.js.map +1 -1
- package/dist/core/asked-value.d.ts +37 -0
- package/dist/core/asked-value.d.ts.map +1 -0
- package/dist/core/asked-value.js +77 -0
- package/dist/core/asked-value.js.map +1 -0
- package/dist/core/conflicts.d.ts +89 -0
- package/dist/core/conflicts.d.ts.map +1 -0
- package/dist/core/conflicts.js +267 -0
- package/dist/core/conflicts.js.map +1 -0
- package/dist/core/embeddings.d.ts +14 -2
- package/dist/core/embeddings.d.ts.map +1 -1
- package/dist/core/embeddings.js +32 -10
- package/dist/core/embeddings.js.map +1 -1
- package/dist/core/format-recall.d.ts +21 -1
- package/dist/core/format-recall.d.ts.map +1 -1
- package/dist/core/format-recall.js +60 -5
- package/dist/core/format-recall.js.map +1 -1
- package/dist/core/ml-worker.d.ts +24 -1
- package/dist/core/ml-worker.d.ts.map +1 -1
- package/dist/core/ml-worker.js +165 -4
- package/dist/core/ml-worker.js.map +1 -1
- package/dist/core/model-daemon-client.d.ts +77 -0
- package/dist/core/model-daemon-client.d.ts.map +1 -0
- package/dist/core/model-daemon-client.js +383 -0
- package/dist/core/model-daemon-client.js.map +1 -0
- package/dist/core/model-daemon-server.d.ts +71 -0
- package/dist/core/model-daemon-server.d.ts.map +1 -0
- package/dist/core/model-daemon-server.js +363 -0
- package/dist/core/model-daemon-server.js.map +1 -0
- package/dist/core/model-daemon.d.ts +137 -0
- package/dist/core/model-daemon.d.ts.map +1 -0
- package/dist/core/model-daemon.js +231 -0
- package/dist/core/model-daemon.js.map +1 -0
- package/dist/core/query-expander.d.ts +5 -0
- package/dist/core/query-expander.d.ts.map +1 -1
- package/dist/core/query-expander.js +3 -2
- package/dist/core/query-expander.js.map +1 -1
- package/dist/core/recall-config.d.ts +1 -1
- package/dist/core/recall-config.d.ts.map +1 -1
- package/dist/core/recall-config.js +5 -0
- package/dist/core/recall-config.js.map +1 -1
- package/dist/core/rerank-window.d.ts +7 -2
- package/dist/core/rerank-window.d.ts.map +1 -1
- package/dist/core/rerank-window.js +9 -4
- package/dist/core/rerank-window.js.map +1 -1
- package/dist/core/rerank2.d.ts +52 -2
- package/dist/core/rerank2.d.ts.map +1 -1
- package/dist/core/rerank2.js +60 -3
- package/dist/core/rerank2.js.map +1 -1
- package/dist/core/reranker.d.ts +19 -0
- package/dist/core/reranker.d.ts.map +1 -1
- package/dist/core/reranker.js +90 -13
- package/dist/core/reranker.js.map +1 -1
- package/dist/core/trust.d.ts +32 -0
- package/dist/core/trust.d.ts.map +1 -0
- package/dist/core/trust.js +66 -0
- package/dist/core/trust.js.map +1 -0
- package/dist/core/validity.d.ts +21 -0
- package/dist/core/validity.d.ts.map +1 -0
- package/dist/core/validity.js +41 -0
- package/dist/core/validity.js.map +1 -0
- package/dist/core/whoami.d.ts +3 -0
- package/dist/core/whoami.d.ts.map +1 -1
- package/dist/core/whoami.js +14 -0
- package/dist/core/whoami.js.map +1 -1
- package/dist/core/write-pipeline.d.ts +48 -0
- package/dist/core/write-pipeline.d.ts.map +1 -1
- package/dist/core/write-pipeline.js +80 -3
- package/dist/core/write-pipeline.js.map +1 -1
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +27 -4
- package/dist/engine/activation.js.map +1 -1
- package/dist/engine/consolidation.d.ts.map +1 -1
- package/dist/engine/consolidation.js +53 -4
- package/dist/engine/consolidation.js.map +1 -1
- package/dist/hooks/prime-query.d.ts +57 -0
- package/dist/hooks/prime-query.d.ts.map +1 -0
- package/dist/hooks/prime-query.js +388 -0
- package/dist/hooks/prime-query.js.map +1 -0
- package/dist/hooks/prime.d.ts +8 -0
- package/dist/hooks/prime.d.ts.map +1 -1
- package/dist/hooks/prime.js +21 -2
- package/dist/hooks/prime.js.map +1 -1
- package/dist/hooks/sidecar.d.ts +18 -0
- package/dist/hooks/sidecar.d.ts.map +1 -1
- package/dist/hooks/sidecar.js +58 -2
- package/dist/hooks/sidecar.js.map +1 -1
- package/dist/index.js +15 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +157 -104
- package/dist/mcp.js.map +1 -1
- package/dist/model-daemon.d.ts +8 -0
- package/dist/model-daemon.d.ts.map +1 -0
- package/dist/model-daemon.js +45 -0
- package/dist/model-daemon.js.map +1 -0
- package/dist/storage/factory.d.ts.map +1 -1
- package/dist/storage/factory.js +5 -0
- package/dist/storage/factory.js.map +1 -1
- package/dist/storage/pglite-schema.d.ts.map +1 -1
- package/dist/storage/pglite-schema.js +176 -170
- package/dist/storage/pglite-schema.js.map +1 -1
- package/dist/storage/pglite.d.ts +5 -0
- package/dist/storage/pglite.d.ts.map +1 -1
- package/dist/storage/pglite.js +165 -153
- package/dist/storage/pglite.js.map +1 -1
- package/dist/storage/postgres.d.ts +5 -0
- package/dist/storage/postgres.d.ts.map +1 -1
- package/dist/storage/postgres.js +165 -153
- package/dist/storage/postgres.js.map +1 -1
- package/dist/storage/sqlite.d.ts +22 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +415 -363
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/types/engram.d.ts +33 -0
- package/dist/types/engram.d.ts.map +1 -1
- package/dist/types/engram.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/README.md +1 -1
- package/plugin/bin/awm-mcp-launcher.cjs +2 -2
- package/plugin/skills/awm-memory/SKILL.md +11 -2
- package/src/adapters/common.ts +881 -802
- package/src/api/routes.ts +20 -0
- package/src/cli/migrate.ts +309 -307
- package/src/core/asked-value.ts +74 -0
- package/src/core/conflicts.ts +280 -0
- package/src/core/embeddings.ts +35 -10
- package/src/core/format-recall.ts +83 -25
- package/src/core/ml-worker.ts +458 -281
- package/src/core/model-daemon-client.ts +374 -0
- package/src/core/model-daemon-server.ts +344 -0
- package/src/core/model-daemon.ts +243 -0
- package/src/core/query-expander.ts +127 -126
- package/src/core/recall-config.ts +5 -0
- package/src/core/rerank-window.ts +164 -159
- package/src/core/rerank2.ts +143 -82
- package/src/core/reranker.ts +92 -12
- package/src/core/trust.ts +65 -0
- package/src/core/validity.ts +47 -0
- package/src/core/whoami.ts +136 -120
- package/src/core/write-pipeline.ts +119 -3
- package/src/engine/activation.ts +27 -4
- package/src/engine/consolidation.ts +950 -899
- package/src/hooks/prime-query.ts +375 -0
- package/src/hooks/prime.ts +25 -2
- package/src/hooks/sidecar.ts +57 -3
- package/src/index.ts +258 -248
- package/src/mcp.ts +1529 -1475
- package/src/model-daemon.ts +48 -0
- package/src/storage/factory.ts +25 -19
- package/src/storage/pglite-schema.ts +199 -193
- package/src/storage/pglite.ts +1513 -1498
- package/src/storage/postgres.ts +1613 -1598
- package/src/storage/sqlite.ts +2069 -2019
- 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 (
|
|
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
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
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 {
|