agent-working-memory 0.3.2 → 0.4.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 +56 -7
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +3 -1
- package/dist/api/routes.js.map +1 -1
- package/dist/cli.js +94 -23
- package/dist/cli.js.map +1 -1
- package/dist/core/logger.d.ts +12 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +32 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/salience.d.ts +14 -0
- package/dist/core/salience.d.ts.map +1 -1
- package/dist/core/salience.js +51 -5
- package/dist/core/salience.js.map +1 -1
- package/dist/hooks/sidecar.d.ts +27 -0
- package/dist/hooks/sidecar.d.ts.map +1 -0
- package/dist/hooks/sidecar.js +220 -0
- package/dist/hooks/sidecar.js.map +1 -0
- package/dist/mcp.js +631 -417
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
- package/src/api/routes.ts +4 -1
- package/src/cli.ts +98 -23
- package/src/core/logger.ts +34 -0
- package/src/core/salience.ts +51 -5
- package/src/hooks/sidecar.ts +258 -0
- package/src/mcp.ts +282 -33
package/src/mcp.ts
CHANGED
|
@@ -51,17 +51,41 @@ import { RetractionEngine } from './engine/retraction.js';
|
|
|
51
51
|
import { EvalEngine } from './engine/eval.js';
|
|
52
52
|
import { ConsolidationEngine } from './engine/consolidation.js';
|
|
53
53
|
import { ConsolidationScheduler } from './engine/consolidation-scheduler.js';
|
|
54
|
-
import { evaluateSalience } from './core/salience.js';
|
|
54
|
+
import { evaluateSalience, computeNovelty } from './core/salience.js';
|
|
55
55
|
import type { ConsciousState } from './types/checkpoint.js';
|
|
56
56
|
import type { SalienceEventType } from './core/salience.js';
|
|
57
57
|
import type { TaskStatus, TaskPriority } from './types/engram.js';
|
|
58
58
|
import { DEFAULT_AGENT_CONFIG } from './types/agent.js';
|
|
59
59
|
import { embed } from './core/embeddings.js';
|
|
60
|
+
import { startSidecar } from './hooks/sidecar.js';
|
|
61
|
+
import { initLogger, log, getLogPath } from './core/logger.js';
|
|
62
|
+
|
|
63
|
+
// --- Incognito Mode ---
|
|
64
|
+
// When AWM_INCOGNITO=1, register zero tools. Claude won't see memory tools at all.
|
|
65
|
+
// No DB, no engines, no sidecar — just a bare MCP server that exposes nothing.
|
|
66
|
+
|
|
67
|
+
const INCOGNITO = process.env.AWM_INCOGNITO === '1' || process.env.AWM_INCOGNITO === 'true';
|
|
68
|
+
|
|
69
|
+
if (INCOGNITO) {
|
|
70
|
+
console.error('AWM: incognito mode — all memory tools disabled, nothing will be recorded');
|
|
71
|
+
const server = new McpServer({ name: 'agent-working-memory', version: '0.4.0' });
|
|
72
|
+
const transport = new StdioServerTransport();
|
|
73
|
+
server.connect(transport).catch(err => {
|
|
74
|
+
console.error('MCP server failed:', err);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
});
|
|
77
|
+
// No tools registered — Claude won't see any memory_* tools
|
|
78
|
+
} else {
|
|
60
79
|
|
|
61
80
|
// --- Setup ---
|
|
62
81
|
|
|
63
82
|
const DB_PATH = process.env.AWM_DB_PATH ?? 'memory.db';
|
|
64
83
|
const AGENT_ID = process.env.AWM_AGENT_ID ?? 'claude-code';
|
|
84
|
+
const HOOK_PORT = parseInt(process.env.AWM_HOOK_PORT ?? '8401', 10);
|
|
85
|
+
const HOOK_SECRET = process.env.AWM_HOOK_SECRET ?? null;
|
|
86
|
+
|
|
87
|
+
initLogger(DB_PATH);
|
|
88
|
+
log(AGENT_ID, 'startup', `MCP server starting (db: ${DB_PATH}, hooks: ${HOOK_PORT})`);
|
|
65
89
|
|
|
66
90
|
const store = new EngramStore(DB_PATH);
|
|
67
91
|
const activationEngine = new ActivationEngine(store);
|
|
@@ -78,7 +102,7 @@ consolidationScheduler.start();
|
|
|
78
102
|
|
|
79
103
|
const server = new McpServer({
|
|
80
104
|
name: 'agent-working-memory',
|
|
81
|
-
version: '0.
|
|
105
|
+
version: '0.4.0',
|
|
82
106
|
});
|
|
83
107
|
|
|
84
108
|
// --- Tools ---
|
|
@@ -87,11 +111,12 @@ server.tool(
|
|
|
87
111
|
'memory_write',
|
|
88
112
|
`Store a memory. The salience filter decides whether it's worth keeping (active), needs more evidence (staging), or should be discarded.
|
|
89
113
|
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
114
|
+
CALL THIS PROACTIVELY — do not wait to be asked. Write memories when you:
|
|
115
|
+
- Discover something about the codebase, bugs, or architecture
|
|
116
|
+
- Make a decision and want to remember why
|
|
117
|
+
- Encounter and resolve an error
|
|
118
|
+
- Learn a user preference or project pattern
|
|
119
|
+
- Complete a significant piece of work
|
|
95
120
|
|
|
96
121
|
The concept should be a short label (3-8 words). The content should be the full detail.`,
|
|
97
122
|
{
|
|
@@ -111,6 +136,9 @@ The concept should be a short label (3-8 words). The content should be the full
|
|
|
111
136
|
.describe('How much effort to resolve? 0=trivial, 1=significant debugging'),
|
|
112
137
|
},
|
|
113
138
|
async (params) => {
|
|
139
|
+
// Check novelty — is this new information or a duplicate?
|
|
140
|
+
const novelty = computeNovelty(store, AGENT_ID, params.concept, params.content);
|
|
141
|
+
|
|
114
142
|
const salience = evaluateSalience({
|
|
115
143
|
content: params.content,
|
|
116
144
|
eventType: params.event_type as SalienceEventType,
|
|
@@ -118,13 +146,15 @@ The concept should be a short label (3-8 words). The content should be the full
|
|
|
118
146
|
decisionMade: params.decision_made,
|
|
119
147
|
causalDepth: params.causal_depth,
|
|
120
148
|
resolutionEffort: params.resolution_effort,
|
|
149
|
+
novelty,
|
|
121
150
|
});
|
|
122
151
|
|
|
123
152
|
if (salience.disposition === 'discard') {
|
|
153
|
+
log(AGENT_ID, 'write:discard', `"${params.concept}" salience=${salience.score.toFixed(2)} novelty=${novelty.toFixed(1)}`);
|
|
124
154
|
return {
|
|
125
155
|
content: [{
|
|
126
156
|
type: 'text' as const,
|
|
127
|
-
text: `
|
|
157
|
+
text: `Discarded (salience ${salience.score.toFixed(2)}, novelty ${novelty.toFixed(1)})`,
|
|
128
158
|
}],
|
|
129
159
|
};
|
|
130
160
|
}
|
|
@@ -154,10 +184,12 @@ The concept should be a short label (3-8 words). The content should be the full
|
|
|
154
184
|
// Auto-checkpoint: track write
|
|
155
185
|
try { store.updateAutoCheckpointWrite(AGENT_ID, engram.id); } catch { /* non-fatal */ }
|
|
156
186
|
|
|
187
|
+
log(AGENT_ID, `write:${salience.disposition}`, `"${params.concept}" salience=${salience.score.toFixed(2)} novelty=${novelty.toFixed(1)} id=${engram.id}`);
|
|
188
|
+
|
|
157
189
|
return {
|
|
158
190
|
content: [{
|
|
159
191
|
type: 'text' as const,
|
|
160
|
-
text: `
|
|
192
|
+
text: `Stored (${salience.disposition}) "${params.concept}" [${salience.score.toFixed(2)}]`,
|
|
161
193
|
}],
|
|
162
194
|
};
|
|
163
195
|
}
|
|
@@ -165,16 +197,19 @@ The concept should be a short label (3-8 words). The content should be the full
|
|
|
165
197
|
|
|
166
198
|
server.tool(
|
|
167
199
|
'memory_recall',
|
|
168
|
-
`Recall memories relevant to a
|
|
200
|
+
`Recall memories relevant to a query. Uses cognitive activation — not keyword search.
|
|
169
201
|
|
|
170
|
-
|
|
171
|
-
- Starting a
|
|
202
|
+
ALWAYS call this when:
|
|
203
|
+
- Starting work on a project or topic (recall what you know)
|
|
172
204
|
- Debugging (recall similar errors and solutions)
|
|
173
205
|
- Making decisions (recall past decisions and outcomes)
|
|
206
|
+
- The user mentions a topic you might have stored memories about
|
|
174
207
|
|
|
175
|
-
|
|
208
|
+
Accepts either "query" or "context" parameter — both work identically.
|
|
209
|
+
Returns the most relevant memories ranked by text relevance, temporal recency, and associative strength.`,
|
|
176
210
|
{
|
|
177
|
-
|
|
211
|
+
query: z.string().optional().describe('What to search for — describe the situation, question, or topic'),
|
|
212
|
+
context: z.string().optional().describe('Alias for query (either works)'),
|
|
178
213
|
limit: z.number().optional().default(5).describe('Max memories to return (default 5)'),
|
|
179
214
|
min_score: z.number().optional().default(0.05).describe('Minimum relevance score (default 0.05)'),
|
|
180
215
|
include_staging: z.boolean().optional().default(false).describe('Include weak/unconfirmed memories?'),
|
|
@@ -182,9 +217,18 @@ Returns the most relevant memories ranked by a composite score of text relevance
|
|
|
182
217
|
use_expansion: z.boolean().optional().default(true).describe('Expand query with synonyms for better recall (default true)'),
|
|
183
218
|
},
|
|
184
219
|
async (params) => {
|
|
220
|
+
const queryText = params.query ?? params.context;
|
|
221
|
+
if (!queryText) {
|
|
222
|
+
return {
|
|
223
|
+
content: [{
|
|
224
|
+
type: 'text' as const,
|
|
225
|
+
text: 'Error: provide either "query" or "context" parameter with your search text.',
|
|
226
|
+
}],
|
|
227
|
+
};
|
|
228
|
+
}
|
|
185
229
|
const results = await activationEngine.activate({
|
|
186
230
|
agentId: AGENT_ID,
|
|
187
|
-
context:
|
|
231
|
+
context: queryText,
|
|
188
232
|
limit: params.limit,
|
|
189
233
|
minScore: params.min_score,
|
|
190
234
|
includeStaging: params.include_staging,
|
|
@@ -195,9 +239,11 @@ Returns the most relevant memories ranked by a composite score of text relevance
|
|
|
195
239
|
// Auto-checkpoint: track recall
|
|
196
240
|
try {
|
|
197
241
|
const ids = results.map(r => r.engram.id);
|
|
198
|
-
store.updateAutoCheckpointRecall(AGENT_ID,
|
|
242
|
+
store.updateAutoCheckpointRecall(AGENT_ID, queryText, ids);
|
|
199
243
|
} catch { /* non-fatal */ }
|
|
200
244
|
|
|
245
|
+
log(AGENT_ID, 'recall', `"${queryText.slice(0, 80)}" → ${results.length} results`);
|
|
246
|
+
|
|
201
247
|
if (results.length === 0) {
|
|
202
248
|
return {
|
|
203
249
|
content: [{
|
|
@@ -208,14 +254,13 @@ Returns the most relevant memories ranked by a composite score of text relevance
|
|
|
208
254
|
}
|
|
209
255
|
|
|
210
256
|
const lines = results.map((r, i) => {
|
|
211
|
-
|
|
212
|
-
return `${i + 1}. **${r.engram.concept}** (score: ${r.score.toFixed(3)})${tags}\n ${r.engram.content}\n _${r.why}_\n ID: ${r.engram.id}`;
|
|
257
|
+
return `${i + 1}. **${r.engram.concept}** (${r.score.toFixed(3)}): ${r.engram.content}`;
|
|
213
258
|
});
|
|
214
259
|
|
|
215
260
|
return {
|
|
216
261
|
content: [{
|
|
217
262
|
type: 'text' as const,
|
|
218
|
-
text:
|
|
263
|
+
text: lines.join('\n'),
|
|
219
264
|
}],
|
|
220
265
|
};
|
|
221
266
|
}
|
|
@@ -245,7 +290,7 @@ Always call this after using a recalled memory so the system learns what's valua
|
|
|
245
290
|
return {
|
|
246
291
|
content: [{
|
|
247
292
|
type: 'text' as const,
|
|
248
|
-
text: `Feedback
|
|
293
|
+
text: `Feedback: ${params.useful ? '+useful' : '-not useful'}`,
|
|
249
294
|
}],
|
|
250
295
|
};
|
|
251
296
|
}
|
|
@@ -286,10 +331,12 @@ Use this when you discover a memory contains incorrect information.`,
|
|
|
286
331
|
|
|
287
332
|
server.tool(
|
|
288
333
|
'memory_stats',
|
|
289
|
-
`Get memory health stats — how many memories, confidence levels, association count, and system performance
|
|
334
|
+
`Get memory health stats — how many memories, confidence levels, association count, and system performance.
|
|
335
|
+
Also shows the activity log path so the user can tail it to see what's happening.`,
|
|
290
336
|
{},
|
|
291
337
|
async () => {
|
|
292
338
|
const metrics = evalEngine.computeMetrics(AGENT_ID);
|
|
339
|
+
const checkpoint = store.getCheckpoint(AGENT_ID);
|
|
293
340
|
const lines = [
|
|
294
341
|
`Agent: ${AGENT_ID}`,
|
|
295
342
|
`Active memories: ${metrics.activeEngramCount}`,
|
|
@@ -300,6 +347,14 @@ server.tool(
|
|
|
300
347
|
`Edge utility: ${(metrics.edgeUtilityRate * 100).toFixed(1)}%`,
|
|
301
348
|
`Activations (24h): ${metrics.activationCount}`,
|
|
302
349
|
`Avg latency: ${metrics.avgLatencyMs.toFixed(1)}ms`,
|
|
350
|
+
``,
|
|
351
|
+
`Session writes: ${checkpoint?.auto.writeCountSinceConsolidation ?? 0}`,
|
|
352
|
+
`Session recalls: ${checkpoint?.auto.recallCountSinceConsolidation ?? 0}`,
|
|
353
|
+
`Last activity: ${checkpoint?.auto.lastActivityAt?.toISOString() ?? 'never'}`,
|
|
354
|
+
`Checkpoint: ${checkpoint?.executionState ? checkpoint.executionState.currentTask : 'none'}`,
|
|
355
|
+
``,
|
|
356
|
+
`Activity log: ${getLogPath() ?? 'not configured'}`,
|
|
357
|
+
`Hook sidecar: 127.0.0.1:${HOOK_PORT}`,
|
|
303
358
|
];
|
|
304
359
|
|
|
305
360
|
return {
|
|
@@ -317,12 +372,12 @@ server.tool(
|
|
|
317
372
|
'memory_checkpoint',
|
|
318
373
|
`Save your current execution state so you can recover after context compaction.
|
|
319
374
|
|
|
320
|
-
|
|
321
|
-
-
|
|
322
|
-
-
|
|
323
|
-
-
|
|
375
|
+
ALWAYS call this before:
|
|
376
|
+
- Long operations (multi-file generation, large refactors, overnight work)
|
|
377
|
+
- Anything that might fill the context window
|
|
378
|
+
- Switching to a different task
|
|
324
379
|
|
|
325
|
-
The state is saved per-agent and overwrites any previous checkpoint.`,
|
|
380
|
+
Also call periodically during long sessions to avoid losing state. The state is saved per-agent and overwrites any previous checkpoint.`,
|
|
326
381
|
{
|
|
327
382
|
current_task: z.string().describe('What you are currently working on'),
|
|
328
383
|
decisions: z.array(z.string()).optional().default([])
|
|
@@ -350,11 +405,12 @@ The state is saved per-agent and overwrites any previous checkpoint.`,
|
|
|
350
405
|
};
|
|
351
406
|
|
|
352
407
|
store.saveCheckpoint(AGENT_ID, state);
|
|
408
|
+
log(AGENT_ID, 'checkpoint', `"${params.current_task}" decisions=${params.decisions.length} files=${params.active_files.length}`);
|
|
353
409
|
|
|
354
410
|
return {
|
|
355
411
|
content: [{
|
|
356
412
|
type: 'text' as const,
|
|
357
|
-
text: `Checkpoint saved
|
|
413
|
+
text: `Checkpoint saved: "${params.current_task}" (${params.decisions.length} decisions, ${params.active_files.length} files)`,
|
|
358
414
|
}],
|
|
359
415
|
};
|
|
360
416
|
}
|
|
@@ -414,18 +470,44 @@ Use this at the start of every session or after compaction to pick up where you
|
|
|
414
470
|
} catch { /* recall failure is non-fatal */ }
|
|
415
471
|
}
|
|
416
472
|
|
|
417
|
-
//
|
|
473
|
+
// Consolidation on restore:
|
|
474
|
+
// - If idle >5min but last consolidation was recent (graceful exit ran it), skip
|
|
475
|
+
// - If idle >5min and no recent consolidation, run full cycle (non-graceful exit fallback)
|
|
418
476
|
const MINI_IDLE_MS = 5 * 60_000;
|
|
477
|
+
const FULL_CONSOLIDATION_GAP_MS = 10 * 60_000; // 10 min — if last consolidation was longer ago, run full
|
|
419
478
|
let miniConsolidationTriggered = false;
|
|
479
|
+
let fullConsolidationTriggered = false;
|
|
480
|
+
|
|
420
481
|
if (idleMs > MINI_IDLE_MS) {
|
|
421
|
-
|
|
422
|
-
|
|
482
|
+
const sinceLastConsolidation = checkpoint?.lastConsolidationAt
|
|
483
|
+
? now - checkpoint.lastConsolidationAt.getTime()
|
|
484
|
+
: Infinity;
|
|
485
|
+
|
|
486
|
+
if (sinceLastConsolidation > FULL_CONSOLIDATION_GAP_MS) {
|
|
487
|
+
// No recent consolidation — graceful exit didn't happen, run full cycle
|
|
488
|
+
fullConsolidationTriggered = true;
|
|
489
|
+
try {
|
|
490
|
+
const result = consolidationEngine.consolidate(AGENT_ID);
|
|
491
|
+
store.markConsolidation(AGENT_ID, false);
|
|
492
|
+
log(AGENT_ID, 'consolidation', `full sleep cycle on restore (no graceful exit, idle ${Math.round(idleMs / 60_000)}min, last consolidation ${Math.round(sinceLastConsolidation / 60_000)}min ago) — ${result.edgesStrengthened} strengthened, ${result.memoriesForgotten} forgotten`);
|
|
493
|
+
} catch { /* consolidation failure is non-fatal */ }
|
|
494
|
+
} else {
|
|
495
|
+
// Recent consolidation exists — graceful exit already handled it, just do mini
|
|
496
|
+
miniConsolidationTriggered = true;
|
|
497
|
+
consolidationScheduler.runMiniConsolidation(AGENT_ID).catch(() => {});
|
|
498
|
+
}
|
|
423
499
|
}
|
|
424
500
|
|
|
425
501
|
// Format response
|
|
426
502
|
const parts: string[] = [];
|
|
427
503
|
const idleMin = Math.round(idleMs / 60_000);
|
|
428
|
-
|
|
504
|
+
const consolidationNote = fullConsolidationTriggered
|
|
505
|
+
? ' (full consolidation — no graceful exit detected)'
|
|
506
|
+
: miniConsolidationTriggered
|
|
507
|
+
? ' (mini-consolidation triggered)'
|
|
508
|
+
: '';
|
|
509
|
+
log(AGENT_ID, 'restore', `idle=${idleMin}min checkpoint=${!!checkpoint?.executionState} recalled=${recalledMemories.length} lastWrite=${lastWrite?.concept ?? 'none'}${fullConsolidationTriggered ? ' FULL_CONSOLIDATION' : ''}`);
|
|
510
|
+
parts.push(`Idle: ${idleMin}min${consolidationNote}`);
|
|
429
511
|
|
|
430
512
|
if (checkpoint?.executionState) {
|
|
431
513
|
const s = checkpoint.executionState;
|
|
@@ -437,6 +519,7 @@ Use this at the start of every session or after compaction to pick up where you
|
|
|
437
519
|
if (checkpoint.checkpointAt) parts.push(`_Saved at: ${checkpoint.checkpointAt.toISOString()}_`);
|
|
438
520
|
} else {
|
|
439
521
|
parts.push('\nNo explicit checkpoint saved.');
|
|
522
|
+
parts.push('\n**Tip:** Use memory_write to save important learnings, and memory_checkpoint before long operations so you can recover state.');
|
|
440
523
|
}
|
|
441
524
|
|
|
442
525
|
if (lastWrite) {
|
|
@@ -510,7 +593,7 @@ Tasks automatically get high salience so they won't be discarded.`,
|
|
|
510
593
|
return {
|
|
511
594
|
content: [{
|
|
512
595
|
type: 'text' as const,
|
|
513
|
-
text: `Task created: ${
|
|
596
|
+
text: `Task created: "${params.concept}" (${params.priority})`,
|
|
514
597
|
}],
|
|
515
598
|
};
|
|
516
599
|
}
|
|
@@ -552,7 +635,7 @@ server.tool(
|
|
|
552
635
|
return {
|
|
553
636
|
content: [{
|
|
554
637
|
type: 'text' as const,
|
|
555
|
-
text: `
|
|
638
|
+
text: `Updated: "${updated.concept}" → ${updated.taskStatus} (${updated.taskPriority})`,
|
|
556
639
|
}],
|
|
557
640
|
};
|
|
558
641
|
}
|
|
@@ -620,16 +703,182 @@ Use this when you finish a task or need to decide what to do next.`,
|
|
|
620
703
|
}
|
|
621
704
|
);
|
|
622
705
|
|
|
706
|
+
// --- Task Bracket Tools ---
|
|
707
|
+
|
|
708
|
+
server.tool(
|
|
709
|
+
'memory_task_begin',
|
|
710
|
+
`Signal that you're starting a significant task. Auto-checkpoints current state and recalls relevant memories.
|
|
711
|
+
|
|
712
|
+
CALL THIS when starting:
|
|
713
|
+
- A multi-step operation (doc generation, large refactor, migration)
|
|
714
|
+
- Work on a new topic or project area
|
|
715
|
+
- Anything that might fill the context window
|
|
716
|
+
|
|
717
|
+
This ensures your state is saved before you start, and primes recall with relevant context.`,
|
|
718
|
+
{
|
|
719
|
+
topic: z.string().describe('What task are you starting? (3-15 words)'),
|
|
720
|
+
files: z.array(z.string()).optional().default([])
|
|
721
|
+
.describe('Files you expect to work with'),
|
|
722
|
+
notes: z.string().optional().default('')
|
|
723
|
+
.describe('Any additional context'),
|
|
724
|
+
},
|
|
725
|
+
async (params) => {
|
|
726
|
+
// 1. Checkpoint current state
|
|
727
|
+
const checkpoint = store.getCheckpoint(AGENT_ID);
|
|
728
|
+
const prevTask = checkpoint?.executionState?.currentTask ?? 'None';
|
|
729
|
+
|
|
730
|
+
store.saveCheckpoint(AGENT_ID, {
|
|
731
|
+
currentTask: params.topic,
|
|
732
|
+
decisions: [],
|
|
733
|
+
activeFiles: params.files,
|
|
734
|
+
nextSteps: [],
|
|
735
|
+
relatedMemoryIds: [],
|
|
736
|
+
notes: params.notes || `Started via memory_task_begin. Previous task: ${prevTask}`,
|
|
737
|
+
episodeId: null,
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
// 2. Auto-recall relevant memories
|
|
741
|
+
let recalledSummary = '';
|
|
742
|
+
try {
|
|
743
|
+
const results = await activationEngine.activate({
|
|
744
|
+
agentId: AGENT_ID,
|
|
745
|
+
context: params.topic,
|
|
746
|
+
limit: 5,
|
|
747
|
+
minScore: 0.05,
|
|
748
|
+
useReranker: true,
|
|
749
|
+
useExpansion: true,
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
if (results.length > 0) {
|
|
753
|
+
const lines = results.map((r, i) => {
|
|
754
|
+
const tags = r.engram.tags?.length ? ` [${r.engram.tags.join(', ')}]` : '';
|
|
755
|
+
return `${i + 1}. **${r.engram.concept}** (${r.score.toFixed(3)})${tags}\n ${r.engram.content.slice(0, 150)}${r.engram.content.length > 150 ? '...' : ''}`;
|
|
756
|
+
});
|
|
757
|
+
recalledSummary = `\n\n**Recalled memories (${results.length}):**\n${lines.join('\n')}`;
|
|
758
|
+
|
|
759
|
+
// Track recall
|
|
760
|
+
store.updateAutoCheckpointRecall(AGENT_ID, params.topic, results.map(r => r.engram.id));
|
|
761
|
+
}
|
|
762
|
+
} catch { /* recall failure is non-fatal */ }
|
|
763
|
+
|
|
764
|
+
log(AGENT_ID, 'task:begin', `"${params.topic}" prev="${prevTask}"`);
|
|
765
|
+
|
|
766
|
+
return {
|
|
767
|
+
content: [{
|
|
768
|
+
type: 'text' as const,
|
|
769
|
+
text: `Started: "${params.topic}" (prev: ${prevTask})${recalledSummary}`,
|
|
770
|
+
}],
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
server.tool(
|
|
776
|
+
'memory_task_end',
|
|
777
|
+
`Signal that you've finished a significant task. Writes a summary memory and auto-checkpoints.
|
|
778
|
+
|
|
779
|
+
CALL THIS when you finish:
|
|
780
|
+
- A multi-step operation
|
|
781
|
+
- Before switching to a different topic
|
|
782
|
+
- At the end of a work session
|
|
783
|
+
|
|
784
|
+
This captures what was accomplished so future sessions can recall it.`,
|
|
785
|
+
{
|
|
786
|
+
summary: z.string().describe('What was accomplished? Include key outcomes, decisions, and any issues.'),
|
|
787
|
+
tags: z.array(z.string()).optional().default([])
|
|
788
|
+
.describe('Tags for the summary memory'),
|
|
789
|
+
},
|
|
790
|
+
async (params) => {
|
|
791
|
+
// 1. Write summary as a memory
|
|
792
|
+
const salience = evaluateSalience({
|
|
793
|
+
content: params.summary,
|
|
794
|
+
eventType: 'decision',
|
|
795
|
+
surprise: 0.3,
|
|
796
|
+
decisionMade: true,
|
|
797
|
+
causalDepth: 0.5,
|
|
798
|
+
resolutionEffort: 0.5,
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
const engram = store.createEngram({
|
|
802
|
+
agentId: AGENT_ID,
|
|
803
|
+
concept: 'Task completed',
|
|
804
|
+
content: params.summary,
|
|
805
|
+
tags: [...params.tags, 'task-summary'],
|
|
806
|
+
salience: Math.max(salience.score, 0.7), // Always high salience for task summaries
|
|
807
|
+
salienceFeatures: salience.features,
|
|
808
|
+
reasonCodes: [...salience.reasonCodes, 'task-end'],
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
connectionEngine.enqueue(engram.id);
|
|
812
|
+
|
|
813
|
+
// Generate embedding asynchronously
|
|
814
|
+
embed(`Task completed: ${params.summary}`).then(vec => {
|
|
815
|
+
store.updateEmbedding(engram.id, vec);
|
|
816
|
+
}).catch(() => {});
|
|
817
|
+
|
|
818
|
+
// 2. Update checkpoint to reflect completion
|
|
819
|
+
const checkpoint = store.getCheckpoint(AGENT_ID);
|
|
820
|
+
const completedTask = checkpoint?.executionState?.currentTask ?? 'Unknown task';
|
|
821
|
+
|
|
822
|
+
store.saveCheckpoint(AGENT_ID, {
|
|
823
|
+
currentTask: `Completed: ${completedTask}`,
|
|
824
|
+
decisions: checkpoint?.executionState?.decisions ?? [],
|
|
825
|
+
activeFiles: [],
|
|
826
|
+
nextSteps: [],
|
|
827
|
+
relatedMemoryIds: [engram.id],
|
|
828
|
+
notes: `Task completed. Summary memory: ${engram.id}`,
|
|
829
|
+
episodeId: null,
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
store.updateAutoCheckpointWrite(AGENT_ID, engram.id);
|
|
833
|
+
log(AGENT_ID, 'task:end', `"${completedTask}" summary=${engram.id} salience=${salience.score.toFixed(2)}`);
|
|
834
|
+
|
|
835
|
+
return {
|
|
836
|
+
content: [{
|
|
837
|
+
type: 'text' as const,
|
|
838
|
+
text: `Completed: "${completedTask}" [${salience.score.toFixed(2)}]`,
|
|
839
|
+
}],
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
);
|
|
843
|
+
|
|
623
844
|
// --- Start ---
|
|
624
845
|
|
|
625
846
|
async function main() {
|
|
626
847
|
const transport = new StdioServerTransport();
|
|
627
848
|
await server.connect(transport);
|
|
849
|
+
|
|
850
|
+
// Start hook sidecar (lightweight HTTP for Claude Code hooks)
|
|
851
|
+
const sidecar = startSidecar({
|
|
852
|
+
store,
|
|
853
|
+
agentId: AGENT_ID,
|
|
854
|
+
secret: HOOK_SECRET,
|
|
855
|
+
port: HOOK_PORT,
|
|
856
|
+
onConsolidate: (agentId, reason) => {
|
|
857
|
+
console.error(`[mcp] consolidation triggered: ${reason}`);
|
|
858
|
+
const result = consolidationEngine.consolidate(agentId);
|
|
859
|
+
store.markConsolidation(agentId, false);
|
|
860
|
+
console.error(`[mcp] consolidation done: ${result.edgesStrengthened} strengthened, ${result.memoriesForgotten} forgotten`);
|
|
861
|
+
},
|
|
862
|
+
});
|
|
863
|
+
|
|
628
864
|
// Log to stderr (stdout is reserved for MCP protocol)
|
|
629
865
|
console.error(`AgentWorkingMemory MCP server started (agent: ${AGENT_ID}, db: ${DB_PATH})`);
|
|
866
|
+
console.error(`Hook sidecar on 127.0.0.1:${HOOK_PORT}${HOOK_SECRET ? ' (auth enabled)' : ' (no auth — set AWM_HOOK_SECRET)'}`);
|
|
867
|
+
|
|
868
|
+
// Clean shutdown
|
|
869
|
+
const cleanup = () => {
|
|
870
|
+
sidecar.close();
|
|
871
|
+
consolidationScheduler.stop();
|
|
872
|
+
stagingBuffer.stop();
|
|
873
|
+
store.close();
|
|
874
|
+
};
|
|
875
|
+
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
876
|
+
process.on('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
630
877
|
}
|
|
631
878
|
|
|
632
879
|
main().catch(err => {
|
|
633
880
|
console.error('MCP server failed:', err);
|
|
634
881
|
process.exit(1);
|
|
635
882
|
});
|
|
883
|
+
|
|
884
|
+
} // end else (non-incognito)
|