agent-working-memory 0.8.6 → 0.8.8
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 +46 -2
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +13 -0
- package/dist/adapters/common.js.map +1 -1
- package/dist/api/routes.js +8 -8
- package/dist/cli/migrate.js +29 -29
- package/dist/cli.js +105 -105
- package/dist/coordination/circuit-breaker.js +23 -23
- package/dist/core/lite-compress.d.ts +26 -0
- package/dist/core/lite-compress.d.ts.map +1 -0
- package/dist/core/lite-compress.js +105 -0
- package/dist/core/lite-compress.js.map +1 -0
- package/dist/mcp.d.ts +5 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +138 -84
- package/dist/mcp.js.map +1 -1
- package/dist/storage/pglite-schema.js +143 -143
- package/dist/storage/pglite.js +138 -138
- package/package.json +4 -3
- package/src/adapters/common.ts +13 -0
- package/src/api/index.ts +3 -3
- package/src/api/routes.ts +1 -1
- package/src/cli/migrate.ts +307 -307
- package/src/cli.ts +1 -1
- package/src/coordination/circuit-breaker.ts +83 -83
- package/src/coordination/failure-modes.ts +50 -50
- package/src/core/decay.ts +63 -63
- package/src/core/embeddings.ts +110 -110
- package/src/core/index.ts +5 -5
- package/src/core/lite-compress.ts +129 -0
- package/src/core/logger.ts +36 -36
- package/src/core/ml-worker-entry.ts +194 -194
- package/src/core/ml-worker.ts +281 -281
- package/src/core/query-expander.ts +122 -122
- package/src/core/reranker.ts +119 -119
- package/src/engine/confidence.ts +120 -120
- package/src/engine/connections.ts +162 -162
- package/src/engine/consolidation-scheduler.ts +242 -242
- package/src/engine/eval.ts +102 -102
- package/src/engine/eviction.ts +101 -101
- package/src/engine/index.ts +8 -8
- package/src/engine/retraction.ts +366 -366
- package/src/engine/staging.ts +74 -74
- package/src/mcp.ts +70 -4
- package/src/storage/factory.ts +147 -147
- package/src/storage/index.ts +3 -3
- package/src/storage/pglite-schema.ts +166 -166
- package/src/storage/pglite.ts +1363 -1363
- package/src/storage/store.ts +80 -80
- package/src/types/agent.ts +67 -67
- package/src/types/checkpoint.ts +46 -46
- package/src/types/eval.ts +100 -100
- package/src/types/index.ts +6 -6
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface CompressResult {
|
|
2
|
+
/** The text to put in the model's context. */
|
|
3
|
+
text: string;
|
|
4
|
+
/** 'toon' when compressed, 'json'/'passthrough' when not. */
|
|
5
|
+
format: 'toon' | 'json' | 'passthrough';
|
|
6
|
+
/** Retrieval handle for the verbatim original, or null when unchanged. */
|
|
7
|
+
ref: string | null;
|
|
8
|
+
charsBefore: number;
|
|
9
|
+
charsAfter: number;
|
|
10
|
+
/** Approx fraction of characters saved (0..1); a rough proxy for token savings. */
|
|
11
|
+
ratio: number;
|
|
12
|
+
}
|
|
13
|
+
export interface CompressOptions {
|
|
14
|
+
/** Don't bother emitting TOON unless it saves at least this many chars. Default 40. */
|
|
15
|
+
minSavingChars?: number;
|
|
16
|
+
}
|
|
17
|
+
/** Retrieve the verbatim original for a CCR-lite ref, or undefined if evicted. */
|
|
18
|
+
export declare function retrieveOriginal(ref: string): string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Compress a structured tool output for model consumption.
|
|
21
|
+
*
|
|
22
|
+
* @param value Either a JS object/array, or a string (JSON is parsed; anything
|
|
23
|
+
* that isn't valid JSON is treated as prose and passed through).
|
|
24
|
+
*/
|
|
25
|
+
export declare function liteCompress(value: unknown, options?: CompressOptions): CompressResult;
|
|
26
|
+
//# sourceMappingURL=lite-compress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lite-compress.d.ts","sourceRoot":"","sources":["../../src/core/lite-compress.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC;IACxC,0EAA0E;IAC1E,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,uFAAuF;IACvF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAkBD,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEhE;AAWD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,eAAoB,GAAG,cAAc,CA8C1F"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Lite output compressor — token-efficient encoding of STRUCTURED tool output.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: agents burn tokens re-reading large structured tool results
|
|
7
|
+
* (JSON arrays, query rows, log dumps). Encoding them as TOON (Token-Oriented
|
|
8
|
+
* Object Notation — a compact, schema-aware tabular form of JSON) cuts ~50-65%
|
|
9
|
+
* of the tokens on uniform arrays at ZERO comprehension cost. An A/B test on
|
|
10
|
+
* claude-sonnet-4-6 and claude-haiku-4-5 found identical retrieval accuracy
|
|
11
|
+
* reading TOON vs JSON (95.8%/83.3% on both encodings, identical misses).
|
|
12
|
+
*
|
|
13
|
+
* This is OUTPUT-ONLY. It never touches stored memory content or the write
|
|
14
|
+
* path. It is intentionally narrow and safe:
|
|
15
|
+
* - Structured data (parseable JSON object/array) -> TOON, IF it round-trips
|
|
16
|
+
* - Prose / non-JSON -> passthrough untouched
|
|
17
|
+
* - TOON that would lose fidelity or barely save -> plain JSON fallback
|
|
18
|
+
*
|
|
19
|
+
* Safety: TOON (like CSV/YAML) can type-coerce ambiguous bare scalars
|
|
20
|
+
* (the string "123" can decode back as the number 123). So every encode is
|
|
21
|
+
* SELF-VERIFIED (encode -> decode -> deep-equal) and we only emit TOON when it
|
|
22
|
+
* reproduces the input exactly. Originals are stashed so the agent can retrieve
|
|
23
|
+
* the verbatim source via a CCR-lite handle if it ever needs it.
|
|
24
|
+
*
|
|
25
|
+
* No ML, no network, no I/O — a pure in-process structural transform.
|
|
26
|
+
*/
|
|
27
|
+
import { encode, decode } from '@toon-format/toon';
|
|
28
|
+
// ── CCR-lite: stash verbatim originals, hand back a retrieval id ────────────
|
|
29
|
+
// Bounded FIFO so a long-lived MCP process can't leak memory.
|
|
30
|
+
const MAX_STORE = 512;
|
|
31
|
+
const _store = new Map();
|
|
32
|
+
let _seq = 0;
|
|
33
|
+
function stash(original) {
|
|
34
|
+
const id = `awm_orig_${++_seq}`;
|
|
35
|
+
_store.set(id, original);
|
|
36
|
+
if (_store.size > MAX_STORE) {
|
|
37
|
+
const oldest = _store.keys().next().value;
|
|
38
|
+
if (oldest !== undefined)
|
|
39
|
+
_store.delete(oldest);
|
|
40
|
+
}
|
|
41
|
+
return id;
|
|
42
|
+
}
|
|
43
|
+
/** Retrieve the verbatim original for a CCR-lite ref, or undefined if evicted. */
|
|
44
|
+
export function retrieveOriginal(ref) {
|
|
45
|
+
return _store.get(ref);
|
|
46
|
+
}
|
|
47
|
+
/** True if TOON encode->decode reproduces `obj` exactly (no scalar coercion drift). */
|
|
48
|
+
function roundTrips(obj, toon) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.stringify(decode(toon)) === JSON.stringify(obj);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Compress a structured tool output for model consumption.
|
|
58
|
+
*
|
|
59
|
+
* @param value Either a JS object/array, or a string (JSON is parsed; anything
|
|
60
|
+
* that isn't valid JSON is treated as prose and passed through).
|
|
61
|
+
*/
|
|
62
|
+
export function liteCompress(value, options = {}) {
|
|
63
|
+
const minSaving = options.minSavingChars ?? 40;
|
|
64
|
+
let obj;
|
|
65
|
+
let jsonText;
|
|
66
|
+
if (typeof value === 'string') {
|
|
67
|
+
try {
|
|
68
|
+
obj = JSON.parse(value);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Not JSON — prose. Leave it alone; that's granularity:compact's job.
|
|
72
|
+
return { text: value, format: 'passthrough', ref: null,
|
|
73
|
+
charsBefore: value.length, charsAfter: value.length, ratio: 0 };
|
|
74
|
+
}
|
|
75
|
+
jsonText = value;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
obj = value;
|
|
79
|
+
jsonText = JSON.stringify(obj, null, 2);
|
|
80
|
+
}
|
|
81
|
+
// Only structured shapes benefit; a bare scalar gains nothing.
|
|
82
|
+
if (obj === null || typeof obj !== 'object') {
|
|
83
|
+
return { text: jsonText, format: 'json', ref: null,
|
|
84
|
+
charsBefore: jsonText.length, charsAfter: jsonText.length, ratio: 0 };
|
|
85
|
+
}
|
|
86
|
+
let toon;
|
|
87
|
+
try {
|
|
88
|
+
toon = encode(obj);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return { text: jsonText, format: 'json', ref: null,
|
|
92
|
+
charsBefore: jsonText.length, charsAfter: jsonText.length, ratio: 0 };
|
|
93
|
+
}
|
|
94
|
+
const before = jsonText.length;
|
|
95
|
+
const after = toon.length;
|
|
96
|
+
// Reject if it doesn't round-trip exactly, or the saving isn't worth it.
|
|
97
|
+
if (!roundTrips(obj, toon) || before - after < minSaving) {
|
|
98
|
+
return { text: jsonText, format: 'json', ref: null,
|
|
99
|
+
charsBefore: before, charsAfter: before, ratio: 0 };
|
|
100
|
+
}
|
|
101
|
+
const ref = stash(jsonText);
|
|
102
|
+
return { text: toon, format: 'toon', ref,
|
|
103
|
+
charsBefore: before, charsAfter: after, ratio: 1 - after / before };
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=lite-compress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lite-compress.js","sourceRoot":"","sources":["../../src/core/lite-compress.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Runs as a stdio-based MCP server that Claude Code connects to directly.
|
|
5
5
|
* Uses the storage and engine layers in-process (no HTTP overhead).
|
|
6
6
|
*
|
|
7
|
-
* Tools exposed (
|
|
7
|
+
* Tools exposed (16):
|
|
8
8
|
* memory_write — store a memory (salience filter decides disposition)
|
|
9
9
|
* memory_recall — activate memories by context (cognitive retrieval)
|
|
10
10
|
* memory_feedback — report whether a recalled memory was useful
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
* memory_task_update — change task status, priority, or blocking
|
|
18
18
|
* memory_task_list — list tasks filtered by status
|
|
19
19
|
* memory_task_next — get the highest-priority actionable task
|
|
20
|
+
* memory_task_begin — start a task (auto-checkpoint + recall)
|
|
21
|
+
* memory_task_end — end a task (write summary + checkpoint)
|
|
22
|
+
* compress_output — encode structured tool output as TOON (token-efficient, lossless)
|
|
23
|
+
* retrieve_original — get the verbatim source for a compress_output ref
|
|
20
24
|
*
|
|
21
25
|
* Run: npx tsx src/mcp.ts
|
|
22
26
|
* Config: add to ~/.claude.json or .mcp.json
|
package/dist/mcp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG"}
|
package/dist/mcp.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
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 (
|
|
9
|
+
* Tools exposed (16):
|
|
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
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
* memory_task_update — change task status, priority, or blocking
|
|
20
20
|
* memory_task_list — list tasks filtered by status
|
|
21
21
|
* memory_task_next — get the highest-priority actionable task
|
|
22
|
+
* memory_task_begin — start a task (auto-checkpoint + recall)
|
|
23
|
+
* memory_task_end — end a task (write summary + checkpoint)
|
|
24
|
+
* compress_output — encode structured tool output as TOON (token-efficient, lossless)
|
|
25
|
+
* retrieve_original — get the verbatim source for a compress_output ref
|
|
22
26
|
*
|
|
23
27
|
* Run: npx tsx src/mcp.ts
|
|
24
28
|
* Config: add to ~/.claude.json or .mcp.json
|
|
@@ -65,6 +69,7 @@ import { DEFAULT_AGENT_CONFIG } from './types/agent.js';
|
|
|
65
69
|
import { embed } from './core/embeddings.js';
|
|
66
70
|
import { startSidecar } from './hooks/sidecar.js';
|
|
67
71
|
import { initLogger, log, getLogPath } from './core/logger.js';
|
|
72
|
+
import { liteCompress, retrieveOriginal } from './core/lite-compress.js';
|
|
68
73
|
import { queryPeerDecisions, formatPeerDecisions } from './coordination/peer-decisions.js';
|
|
69
74
|
// --- Incognito Mode ---
|
|
70
75
|
// When AWM_INCOGNITO=1, register zero tools. Claude won't see memory tools at all.
|
|
@@ -72,7 +77,7 @@ import { queryPeerDecisions, formatPeerDecisions } from './coordination/peer-dec
|
|
|
72
77
|
const INCOGNITO = process.env.AWM_INCOGNITO === '1' || process.env.AWM_INCOGNITO === 'true';
|
|
73
78
|
if (INCOGNITO) {
|
|
74
79
|
console.error('AWM: incognito mode — all memory tools disabled, nothing will be recorded');
|
|
75
|
-
const server = new McpServer({ name: 'agent-working-memory', version: '0.8.
|
|
80
|
+
const server = new McpServer({ name: 'agent-working-memory', version: '0.8.8' });
|
|
76
81
|
const transport = new StdioServerTransport();
|
|
77
82
|
server.connect(transport).catch(err => {
|
|
78
83
|
console.error('MCP server failed:', err);
|
|
@@ -84,7 +89,20 @@ else {
|
|
|
84
89
|
// --- Setup ---
|
|
85
90
|
const BACKEND = getConfiguredBackend();
|
|
86
91
|
const DB_PATH = process.env.AWM_DB_PATH ?? (BACKEND === 'pglite' ? 'memory-pglite' : 'memory.db');
|
|
87
|
-
|
|
92
|
+
// Fallback agent selection when AWM_AGENT_ID/WORKER_NAME are unset: derive from
|
|
93
|
+
// the project directory so plain `claude` launches still bind to the right
|
|
94
|
+
// store. Personal-Projects -> 'personal'; everything else -> 'work' (the
|
|
95
|
+
// primary store). MUST stay in sync with the SessionStart hook
|
|
96
|
+
// (~/.claude/hooks/awm-session-start.ps1) so the hook's restore and the
|
|
97
|
+
// server's reads/writes never diverge. Guard the AWM package's own path
|
|
98
|
+
// (it lives under Personal-Projects) so a stray server cwd can't mis-bind.
|
|
99
|
+
function deriveAgentFromDir() {
|
|
100
|
+
const dir = (process.env.CLAUDE_PROJECT_DIR ?? process.cwd()).replace(/\\/g, '/');
|
|
101
|
+
if (/\/AgentSynapse\//i.test(dir))
|
|
102
|
+
return 'work';
|
|
103
|
+
return /\/Personal-Projects(\/|$)/i.test(dir) ? 'personal' : 'work';
|
|
104
|
+
}
|
|
105
|
+
const AGENT_ID = process.env.AWM_AGENT_ID ?? process.env.WORKER_NAME ?? deriveAgentFromDir();
|
|
88
106
|
const HOOK_PORT = parseInt(process.env.AWM_HOOK_PORT ?? '8401', 10);
|
|
89
107
|
const HOOK_SECRET = process.env.AWM_HOOK_SECRET ?? null;
|
|
90
108
|
initLogger(DB_PATH);
|
|
@@ -108,7 +126,7 @@ else {
|
|
|
108
126
|
let coordDb = null;
|
|
109
127
|
const server = new McpServer({
|
|
110
128
|
name: 'agent-working-memory',
|
|
111
|
-
version: '0.8.
|
|
129
|
+
version: '0.8.8',
|
|
112
130
|
});
|
|
113
131
|
server.registerResource('awm-overview', 'awm://server/overview', {
|
|
114
132
|
title: 'AWM Overview',
|
|
@@ -168,15 +186,15 @@ else {
|
|
|
168
186
|
return 'unclassified';
|
|
169
187
|
}
|
|
170
188
|
// --- Tools ---
|
|
171
|
-
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.
|
|
172
|
-
|
|
173
|
-
CALL THIS PROACTIVELY — do not wait to be asked. Write memories when you:
|
|
174
|
-
- Discover something about the codebase, bugs, or architecture
|
|
175
|
-
- Make a decision and want to remember why
|
|
176
|
-
- Encounter and resolve an error
|
|
177
|
-
- Learn a user preference or project pattern
|
|
178
|
-
- Complete a significant piece of work
|
|
179
|
-
|
|
189
|
+
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.
|
|
190
|
+
|
|
191
|
+
CALL THIS PROACTIVELY — do not wait to be asked. Write memories when you:
|
|
192
|
+
- Discover something about the codebase, bugs, or architecture
|
|
193
|
+
- Make a decision and want to remember why
|
|
194
|
+
- Encounter and resolve an error
|
|
195
|
+
- Learn a user preference or project pattern
|
|
196
|
+
- Complete a significant piece of work
|
|
197
|
+
|
|
180
198
|
The concept should be a short label (3-8 words). The content should be the full detail.`, {
|
|
181
199
|
concept: z.string().describe('Short label for this memory (3-8 words)'),
|
|
182
200
|
content: z.string().describe('Full detail of what was learned'),
|
|
@@ -280,15 +298,15 @@ The concept should be a short label (3-8 words). The content should be the full
|
|
|
280
298
|
}],
|
|
281
299
|
};
|
|
282
300
|
});
|
|
283
|
-
server.tool('memory_recall', `Recall memories relevant to a query. Uses cognitive activation — not keyword search.
|
|
284
|
-
|
|
285
|
-
ALWAYS call this when:
|
|
286
|
-
- Starting work on a project or topic (recall what you know)
|
|
287
|
-
- Debugging (recall similar errors and solutions)
|
|
288
|
-
- Making decisions (recall past decisions and outcomes)
|
|
289
|
-
- The user mentions a topic you might have stored memories about
|
|
290
|
-
|
|
291
|
-
Accepts either "query" or "context" parameter — both work identically.
|
|
301
|
+
server.tool('memory_recall', `Recall memories relevant to a query. Uses cognitive activation — not keyword search.
|
|
302
|
+
|
|
303
|
+
ALWAYS call this when:
|
|
304
|
+
- Starting work on a project or topic (recall what you know)
|
|
305
|
+
- Debugging (recall similar errors and solutions)
|
|
306
|
+
- Making decisions (recall past decisions and outcomes)
|
|
307
|
+
- The user mentions a topic you might have stored memories about
|
|
308
|
+
|
|
309
|
+
Accepts either "query" or "context" parameter — both work identically.
|
|
292
310
|
Returns the most relevant memories ranked by text relevance, temporal recency, and associative strength.`, {
|
|
293
311
|
query: z.string().optional().describe('What to search for — describe the situation, question, or topic'),
|
|
294
312
|
context: z.string().optional().describe('Alias for query (either works)'),
|
|
@@ -359,8 +377,8 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
|
|
|
359
377
|
}],
|
|
360
378
|
};
|
|
361
379
|
});
|
|
362
|
-
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.
|
|
363
|
-
|
|
380
|
+
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.
|
|
381
|
+
|
|
364
382
|
Always call this after using a recalled memory so the system learns what's valuable.`, {
|
|
365
383
|
engram_id: z.string().describe('ID of the memory (from memory_recall results)'),
|
|
366
384
|
useful: z.boolean().describe('Was this memory actually helpful?'),
|
|
@@ -383,8 +401,8 @@ Always call this after using a recalled memory so the system learns what's valua
|
|
|
383
401
|
}],
|
|
384
402
|
};
|
|
385
403
|
});
|
|
386
|
-
server.tool('memory_retract', `Retract a memory that turned out to be wrong. Creates a correction and reduces confidence of related memories.
|
|
387
|
-
|
|
404
|
+
server.tool('memory_retract', `Retract a memory that turned out to be wrong. Creates a correction and reduces confidence of related memories.
|
|
405
|
+
|
|
388
406
|
Use this when you discover a memory contains incorrect information.`, {
|
|
389
407
|
engram_id: z.string().describe('ID of the wrong memory'),
|
|
390
408
|
reason: z.string().describe('Why is this memory wrong?'),
|
|
@@ -408,13 +426,13 @@ Use this when you discover a memory contains incorrect information.`, {
|
|
|
408
426
|
}],
|
|
409
427
|
};
|
|
410
428
|
});
|
|
411
|
-
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.
|
|
412
|
-
|
|
413
|
-
Use this when:
|
|
414
|
-
- A status or count has changed (e.g., "5 reviews done" → "7 reviews done")
|
|
415
|
-
- Architecture or infrastructure evolved (e.g., "two-repo model" → "three-repo model")
|
|
416
|
-
- A schedule or plan was updated
|
|
417
|
-
|
|
429
|
+
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.
|
|
430
|
+
|
|
431
|
+
Use this when:
|
|
432
|
+
- A status or count has changed (e.g., "5 reviews done" → "7 reviews done")
|
|
433
|
+
- Architecture or infrastructure evolved (e.g., "two-repo model" → "three-repo model")
|
|
434
|
+
- A schedule or plan was updated
|
|
435
|
+
|
|
418
436
|
The old memory stays in the database (searchable for history) but is heavily down-ranked in recall so the current version dominates.`, {
|
|
419
437
|
old_engram_id: z.string().describe('ID of the outdated memory'),
|
|
420
438
|
new_engram_id: z.string().describe('ID of the replacement memory'),
|
|
@@ -441,7 +459,7 @@ The old memory stays in the database (searchable for history) but is heavily dow
|
|
|
441
459
|
}],
|
|
442
460
|
};
|
|
443
461
|
});
|
|
444
|
-
server.tool('memory_stats', `Get memory health stats — how many memories, confidence levels, association count, and system performance.
|
|
462
|
+
server.tool('memory_stats', `Get memory health stats — how many memories, confidence levels, association count, and system performance.
|
|
445
463
|
Also shows the activity log path so the user can tail it to see what's happening.`, {}, async () => {
|
|
446
464
|
const metrics = await evalEngine.computeMetrics(AGENT_ID);
|
|
447
465
|
const checkpoint = await store.getCheckpoint(AGENT_ID);
|
|
@@ -472,13 +490,13 @@ Also shows the activity log path so the user can tail it to see what's happening
|
|
|
472
490
|
};
|
|
473
491
|
});
|
|
474
492
|
// --- Checkpointing Tools ---
|
|
475
|
-
server.tool('memory_checkpoint', `Save your current execution state so you can recover after context compaction.
|
|
476
|
-
|
|
477
|
-
ALWAYS call this before:
|
|
478
|
-
- Long operations (multi-file generation, large refactors, overnight work)
|
|
479
|
-
- Anything that might fill the context window
|
|
480
|
-
- Switching to a different task
|
|
481
|
-
|
|
493
|
+
server.tool('memory_checkpoint', `Save your current execution state so you can recover after context compaction.
|
|
494
|
+
|
|
495
|
+
ALWAYS call this before:
|
|
496
|
+
- Long operations (multi-file generation, large refactors, overnight work)
|
|
497
|
+
- Anything that might fill the context window
|
|
498
|
+
- Switching to a different task
|
|
499
|
+
|
|
482
500
|
Also call periodically during long sessions to avoid losing state. The state is saved per-agent and overwrites any previous checkpoint.`, {
|
|
483
501
|
current_task: z.string().describe('What you are currently working on'),
|
|
484
502
|
decisions: z.array(z.string()).optional().default([])
|
|
@@ -512,14 +530,14 @@ Also call periodically during long sessions to avoid losing state. The state is
|
|
|
512
530
|
}],
|
|
513
531
|
};
|
|
514
532
|
});
|
|
515
|
-
server.tool('memory_restore', `Restore your previous execution state after context compaction or at session start.
|
|
516
|
-
|
|
517
|
-
Returns:
|
|
518
|
-
- Your saved execution state (task, decisions, next steps, files)
|
|
519
|
-
- Recently recalled memories for context
|
|
520
|
-
- Your last write for continuity
|
|
521
|
-
- How long you were idle
|
|
522
|
-
|
|
533
|
+
server.tool('memory_restore', `Restore your previous execution state after context compaction or at session start.
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
- Your saved execution state (task, decisions, next steps, files)
|
|
537
|
+
- Recently recalled memories for context
|
|
538
|
+
- Your last write for continuity
|
|
539
|
+
- How long you were idle
|
|
540
|
+
|
|
523
541
|
Use this at the start of every session or after compaction to pick up where you left off.`, {}, async () => {
|
|
524
542
|
const checkpoint = await store.getCheckpoint(AGENT_ID);
|
|
525
543
|
const now = Date.now();
|
|
@@ -627,9 +645,9 @@ Use this at the start of every session or after compaction to pick up where you
|
|
|
627
645
|
if (coordDb) {
|
|
628
646
|
try {
|
|
629
647
|
const myAgent = coordDb.prepare(`SELECT id FROM coord_agents WHERE name = ? AND status != 'dead' ORDER BY last_seen DESC LIMIT 1`).get(AGENT_ID);
|
|
630
|
-
const peerDecisions = coordDb.prepare(`SELECT d.summary, a.name AS author_name, d.created_at
|
|
631
|
-
FROM coord_decisions d JOIN coord_agents a ON d.author_id = a.id
|
|
632
|
-
WHERE d.author_id != ? AND d.created_at > datetime('now', '-30 minutes')
|
|
648
|
+
const peerDecisions = coordDb.prepare(`SELECT d.summary, a.name AS author_name, d.created_at
|
|
649
|
+
FROM coord_decisions d JOIN coord_agents a ON d.author_id = a.id
|
|
650
|
+
WHERE d.author_id != ? AND d.created_at > datetime('now', '-30 minutes')
|
|
633
651
|
ORDER BY d.created_at DESC LIMIT 10`).all(myAgent?.id ?? '');
|
|
634
652
|
if (peerDecisions.length > 0) {
|
|
635
653
|
parts.push(`\n**Peer decisions (last 30 min):**`);
|
|
@@ -648,13 +666,13 @@ Use this at the start of every session or after compaction to pick up where you
|
|
|
648
666
|
};
|
|
649
667
|
});
|
|
650
668
|
// --- Task Management Tools ---
|
|
651
|
-
server.tool('memory_task_add', `Create a task that you need to come back to. Tasks are memories with status and priority tracking.
|
|
652
|
-
|
|
653
|
-
Use this when:
|
|
654
|
-
- You identify work that needs doing but can't do it right now
|
|
655
|
-
- The user mentions something to do later
|
|
656
|
-
- You want to park a sub-task while focusing on something more urgent
|
|
657
|
-
|
|
669
|
+
server.tool('memory_task_add', `Create a task that you need to come back to. Tasks are memories with status and priority tracking.
|
|
670
|
+
|
|
671
|
+
Use this when:
|
|
672
|
+
- You identify work that needs doing but can't do it right now
|
|
673
|
+
- The user mentions something to do later
|
|
674
|
+
- You want to park a sub-task while focusing on something more urgent
|
|
675
|
+
|
|
658
676
|
Tasks automatically get high salience so they won't be discarded.`, {
|
|
659
677
|
concept: z.string().describe('Short task title (3-10 words)'),
|
|
660
678
|
content: z.string().describe('Full task description — what needs doing, context, acceptance criteria'),
|
|
@@ -694,11 +712,11 @@ Tasks automatically get high salience so they won't be discarded.`, {
|
|
|
694
712
|
}],
|
|
695
713
|
};
|
|
696
714
|
});
|
|
697
|
-
server.tool('memory_task_update', `Update a task's status or priority. Use this to:
|
|
698
|
-
- Start working on a task (open → in_progress)
|
|
699
|
-
- Mark a task done (→ done)
|
|
700
|
-
- Block a task on another (→ blocked)
|
|
701
|
-
- Reprioritize (change priority)
|
|
715
|
+
server.tool('memory_task_update', `Update a task's status or priority. Use this to:
|
|
716
|
+
- Start working on a task (open → in_progress)
|
|
717
|
+
- Mark a task done (→ done)
|
|
718
|
+
- Block a task on another (→ blocked)
|
|
719
|
+
- Reprioritize (change priority)
|
|
702
720
|
- Unblock a task (clear blocked_by)`, {
|
|
703
721
|
task_id: z.string().describe('ID of the task to update'),
|
|
704
722
|
status: z.enum(['open', 'in_progress', 'blocked', 'done']).optional()
|
|
@@ -728,8 +746,8 @@ Tasks automatically get high salience so they won't be discarded.`, {
|
|
|
728
746
|
}],
|
|
729
747
|
};
|
|
730
748
|
});
|
|
731
|
-
server.tool('memory_task_list', `List tasks with optional status filter. Shows tasks ordered by priority (urgent first).
|
|
732
|
-
|
|
749
|
+
server.tool('memory_task_list', `List tasks with optional status filter. Shows tasks ordered by priority (urgent first).
|
|
750
|
+
|
|
733
751
|
Use at the start of a session to see what's pending, or to check blocked/done tasks.`, {
|
|
734
752
|
status: z.enum(['open', 'in_progress', 'blocked', 'done']).optional()
|
|
735
753
|
.describe('Filter by status (omit to see all active tasks)'),
|
|
@@ -755,10 +773,10 @@ Use at the start of a session to see what's pending, or to check blocked/done ta
|
|
|
755
773
|
}],
|
|
756
774
|
};
|
|
757
775
|
});
|
|
758
|
-
server.tool('memory_task_next', `Get the single most important task to work on next.
|
|
759
|
-
|
|
760
|
-
Prioritizes: in_progress tasks first (finish what you started), then by priority level, then oldest first. Skips blocked and done tasks.
|
|
761
|
-
|
|
776
|
+
server.tool('memory_task_next', `Get the single most important task to work on next.
|
|
777
|
+
|
|
778
|
+
Prioritizes: in_progress tasks first (finish what you started), then by priority level, then oldest first. Skips blocked and done tasks.
|
|
779
|
+
|
|
762
780
|
Use this when you finish a task or need to decide what to do next.`, {}, async () => {
|
|
763
781
|
const next = await store.getNextTask(AGENT_ID);
|
|
764
782
|
if (!next) {
|
|
@@ -774,13 +792,13 @@ Use this when you finish a task or need to decide what to do next.`, {}, async (
|
|
|
774
792
|
};
|
|
775
793
|
});
|
|
776
794
|
// --- Task Bracket Tools ---
|
|
777
|
-
server.tool('memory_task_begin', `Signal that you're starting a significant task. Auto-checkpoints current state and recalls relevant memories.
|
|
778
|
-
|
|
779
|
-
CALL THIS when starting:
|
|
780
|
-
- A multi-step operation (doc generation, large refactor, migration)
|
|
781
|
-
- Work on a new topic or project area
|
|
782
|
-
- Anything that might fill the context window
|
|
783
|
-
|
|
795
|
+
server.tool('memory_task_begin', `Signal that you're starting a significant task. Auto-checkpoints current state and recalls relevant memories.
|
|
796
|
+
|
|
797
|
+
CALL THIS when starting:
|
|
798
|
+
- A multi-step operation (doc generation, large refactor, migration)
|
|
799
|
+
- Work on a new topic or project area
|
|
800
|
+
- Anything that might fill the context window
|
|
801
|
+
|
|
784
802
|
This ensures your state is saved before you start, and primes recall with relevant context.`, {
|
|
785
803
|
topic: z.string().describe('What task are you starting? (3-15 words)'),
|
|
786
804
|
files: z.array(z.string()).optional().default([])
|
|
@@ -831,13 +849,13 @@ This ensures your state is saved before you start, and primes recall with releva
|
|
|
831
849
|
}],
|
|
832
850
|
};
|
|
833
851
|
});
|
|
834
|
-
server.tool('memory_task_end', `Signal that you've finished a significant task. Writes a summary memory and auto-checkpoints.
|
|
835
|
-
|
|
836
|
-
CALL THIS when you finish:
|
|
837
|
-
- A multi-step operation
|
|
838
|
-
- Before switching to a different topic
|
|
839
|
-
- At the end of a work session
|
|
840
|
-
|
|
852
|
+
server.tool('memory_task_end', `Signal that you've finished a significant task. Writes a summary memory and auto-checkpoints.
|
|
853
|
+
|
|
854
|
+
CALL THIS when you finish:
|
|
855
|
+
- A multi-step operation
|
|
856
|
+
- Before switching to a different topic
|
|
857
|
+
- At the end of a work session
|
|
858
|
+
|
|
841
859
|
This captures what was accomplished so future sessions can recall it.`, {
|
|
842
860
|
summary: z.string().describe('What was accomplished? Include key outcomes, decisions, and any issues.'),
|
|
843
861
|
tags: z.array(z.string()).optional().default([])
|
|
@@ -910,6 +928,42 @@ This captures what was accomplished so future sessions can recall it.`, {
|
|
|
910
928
|
}],
|
|
911
929
|
};
|
|
912
930
|
});
|
|
931
|
+
server.tool('compress_output', `Compress a STRUCTURED tool output (JSON object/array, query rows, log records) into TOON —
|
|
932
|
+
a compact, schema-aware tabular encoding — before putting it in your context. Cuts ~50-65%
|
|
933
|
+
of the tokens on uniform arrays at zero comprehension cost (validated: models read TOON as
|
|
934
|
+
accurately as JSON). Use this on large tool results you need to keep in context.
|
|
935
|
+
|
|
936
|
+
Output-only and safe: it never changes the data. Non-JSON / prose is returned unchanged.
|
|
937
|
+
TOON is only emitted when it reproduces the input exactly (self-verified round-trip);
|
|
938
|
+
otherwise you get plain JSON back. When compressed, you also get a 'ref' — call
|
|
939
|
+
retrieve_original(ref) to get the verbatim source back if you ever need it.`, {
|
|
940
|
+
output: z.string().describe('The tool output to compress — JSON text (preferred) or any string. Non-JSON is returned unchanged.'),
|
|
941
|
+
min_saving_chars: z.number().optional().describe('Only emit TOON if it saves at least this many characters (default 40).'),
|
|
942
|
+
}, async (params) => {
|
|
943
|
+
const r = liteCompress(params.output, { minSavingChars: params.min_saving_chars });
|
|
944
|
+
log(AGENT_ID, 'compress', `${r.format} ${r.charsBefore}->${r.charsAfter} chars (${(r.ratio * 100).toFixed(0)}%)${r.ref ? ` ref=${r.ref}` : ''}`);
|
|
945
|
+
const header = r.format === 'toon'
|
|
946
|
+
? `[TOON, ${(r.ratio * 100).toFixed(0)}% smaller — compact lossless JSON; read as data, ref=${r.ref}]\n`
|
|
947
|
+
: '';
|
|
948
|
+
return {
|
|
949
|
+
content: [{ type: 'text', text: header + r.text }],
|
|
950
|
+
};
|
|
951
|
+
});
|
|
952
|
+
server.tool('retrieve_original', `Retrieve the verbatim original text for a 'ref' returned by compress_output. Use this when
|
|
953
|
+
you need the exact, uncompressed source (e.g. to pass it to another tool unchanged). Returns
|
|
954
|
+
an error if the ref has expired (originals are kept for the most recent compressions only).`, {
|
|
955
|
+
ref: z.string().describe('The ref handle returned by compress_output (e.g. "awm_orig_12").'),
|
|
956
|
+
}, async (params) => {
|
|
957
|
+
const original = retrieveOriginal(params.ref);
|
|
958
|
+
if (original === undefined) {
|
|
959
|
+
return {
|
|
960
|
+
content: [{ type: 'text', text: `Error: ref "${params.ref}" not found or expired.` }],
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
return {
|
|
964
|
+
content: [{ type: 'text', text: original }],
|
|
965
|
+
};
|
|
966
|
+
});
|
|
913
967
|
// --- Start ---
|
|
914
968
|
async function main() {
|
|
915
969
|
const transport = new StdioServerTransport();
|