agent-working-memory 0.12.0 → 0.13.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 +69 -324
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +36 -7
- package/dist/adapters/common.js.map +1 -1
- package/dist/cli/migrate.js +1 -1
- package/dist/cli/migrate.js.map +1 -1
- package/dist/core/embeddings.js +1 -1
- package/dist/core/embeddings.js.map +1 -1
- package/dist/core/format-recall.d.ts +16 -0
- package/dist/core/format-recall.d.ts.map +1 -0
- package/dist/core/format-recall.js +24 -0
- package/dist/core/format-recall.js.map +1 -0
- package/dist/core/query-expander.js +1 -1
- package/dist/core/query-expander.js.map +1 -1
- package/dist/core/reranker.js +1 -1
- package/dist/core/reranker.js.map +1 -1
- package/dist/hooks/sidecar.d.ts +26 -0
- package/dist/hooks/sidecar.d.ts.map +1 -1
- package/dist/hooks/sidecar.js +30 -0
- package/dist/hooks/sidecar.js.map +1 -1
- package/dist/mcp.js +64 -18
- package/dist/mcp.js.map +1 -1
- package/dist/storage/pglite.d.ts.map +1 -1
- package/dist/storage/pglite.js +4 -1
- package/dist/storage/pglite.js.map +1 -1
- package/package.json +3 -2
- package/src/adapters/common.ts +36 -7
- package/src/cli/migrate.ts +1 -1
- package/src/core/embeddings.ts +1 -1
- package/src/core/format-recall.ts +25 -0
- package/src/core/query-expander.ts +1 -1
- package/src/core/reranker.ts +1 -1
- package/src/hooks/sidecar.ts +55 -0
- package/src/mcp.ts +64 -18
- package/src/storage/pglite.ts +4 -1
|
@@ -30,7 +30,7 @@ async function loadInProcess(): Promise<Text2TextGenerationPipeline> {
|
|
|
30
30
|
if (inProcessInitPromise) return inProcessInitPromise;
|
|
31
31
|
inProcessInitPromise = pipeline('text2text-generation', MODEL_ID, { dtype: 'fp32' }).then(pipe => {
|
|
32
32
|
inProcessInstance = pipe as Text2TextGenerationPipeline;
|
|
33
|
-
console.
|
|
33
|
+
console.error(`Query expander loaded in-process: ${MODEL_ID}`);
|
|
34
34
|
return inProcessInstance;
|
|
35
35
|
});
|
|
36
36
|
return inProcessInitPromise;
|
package/src/core/reranker.ts
CHANGED
|
@@ -35,7 +35,7 @@ async function ensureLoaded(): Promise<void> {
|
|
|
35
35
|
initPromise = (async () => {
|
|
36
36
|
tokenizer = await AutoTokenizer.from_pretrained(MODEL_ID);
|
|
37
37
|
model = await AutoModelForSequenceClassification.from_pretrained(MODEL_ID, { dtype: 'fp32' });
|
|
38
|
-
console.
|
|
38
|
+
console.error(`Re-ranker model loaded in-process: ${MODEL_ID}`);
|
|
39
39
|
})();
|
|
40
40
|
return initPromise;
|
|
41
41
|
}
|
package/src/hooks/sidecar.ts
CHANGED
|
@@ -28,6 +28,28 @@ export interface SidecarDeps {
|
|
|
28
28
|
secret: string | null;
|
|
29
29
|
port: number;
|
|
30
30
|
onConsolidate?: (agentId: string, reason: string) => void;
|
|
31
|
+
/**
|
|
32
|
+
* 0.12.2: warm recall for hooks. The sidecar runs in the same process as
|
|
33
|
+
* the MCP server's activation engine and its loaded ML models, so exposing
|
|
34
|
+
* recall here gives Claude Code hooks (e.g. a UserPromptSubmit PRIME hook)
|
|
35
|
+
* warm-latency recall with no standing server — the process lifecycle is
|
|
36
|
+
* owned by the session that spawned it. Optional so older callers and
|
|
37
|
+
* tests keep working unchanged.
|
|
38
|
+
*/
|
|
39
|
+
activate?: (q: {
|
|
40
|
+
context: string;
|
|
41
|
+
limit?: number;
|
|
42
|
+
requireConfidence?: number;
|
|
43
|
+
granularity?: 'full' | 'compact' | 'auto';
|
|
44
|
+
}) => Promise<Array<{
|
|
45
|
+
engram: {
|
|
46
|
+
id: string; concept: string; content: string;
|
|
47
|
+
createdAt?: string; memoryClass?: string; validTo?: string | null;
|
|
48
|
+
};
|
|
49
|
+
score: number;
|
|
50
|
+
summary?: string;
|
|
51
|
+
confidence?: number;
|
|
52
|
+
}>>;
|
|
31
53
|
}
|
|
32
54
|
|
|
33
55
|
interface HookInput {
|
|
@@ -187,6 +209,39 @@ export function startSidecar(deps: SidecarDeps): { close: () => void } {
|
|
|
187
209
|
return;
|
|
188
210
|
}
|
|
189
211
|
|
|
212
|
+
// POST /memory/activate — warm recall for hooks (0.12.2). Behind the auth
|
|
213
|
+
// gate above. Returns a trimmed subset of the HTTP API's response shape
|
|
214
|
+
// ({results: [{engram, score, summary, confidence}]}) so a hook written
|
|
215
|
+
// against either endpoint needs no branching.
|
|
216
|
+
if (req.url === '/memory/activate' && req.method === 'POST') {
|
|
217
|
+
if (!deps.activate) {
|
|
218
|
+
json(res, 501, { error: 'activate not wired on this sidecar' });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const body = JSON.parse((await readBody(req)) || '{}') as {
|
|
223
|
+
context?: string; query?: string; limit?: number;
|
|
224
|
+
requireConfidence?: number; granularity?: 'full' | 'compact' | 'auto';
|
|
225
|
+
};
|
|
226
|
+
const context = body.context ?? body.query;
|
|
227
|
+
if (!context) {
|
|
228
|
+
json(res, 400, { error: 'context (or query) is required' });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const results = await deps.activate({
|
|
232
|
+
context,
|
|
233
|
+
limit: body.limit,
|
|
234
|
+
requireConfidence: body.requireConfidence,
|
|
235
|
+
granularity: body.granularity,
|
|
236
|
+
});
|
|
237
|
+
log(agentId, 'hook:recall', `"${context.slice(0, 80)}" → ${results.length} results (sidecar)`);
|
|
238
|
+
json(res, 200, { results });
|
|
239
|
+
} catch (err) {
|
|
240
|
+
json(res, 500, { error: (err as Error).message });
|
|
241
|
+
}
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
190
245
|
// POST /hooks/checkpoint — auto-checkpoint from hook events
|
|
191
246
|
if (req.url === '/hooks/checkpoint' && req.method === 'POST') {
|
|
192
247
|
try {
|
package/src/mcp.ts
CHANGED
|
@@ -73,7 +73,9 @@ import type { ConsciousState } from './types/checkpoint.js';
|
|
|
73
73
|
import type { SalienceEventType } from './core/salience.js';
|
|
74
74
|
import type { TaskStatus, TaskPriority } from './types/engram.js';
|
|
75
75
|
import { DEFAULT_AGENT_CONFIG } from './types/agent.js';
|
|
76
|
-
import { embed } from './core/embeddings.js';
|
|
76
|
+
import { embed, getEmbedder } from './core/embeddings.js';
|
|
77
|
+
import { getReranker } from './core/reranker.js';
|
|
78
|
+
import { getExpander } from './core/query-expander.js';
|
|
77
79
|
import { startSidecar } from './hooks/sidecar.js';
|
|
78
80
|
import { initLogger, log, getLogPath } from './core/logger.js';
|
|
79
81
|
import { VERSION } from './version.js';
|
|
@@ -83,6 +85,7 @@ import { queryPeerDecisions, formatPeerDecisions } from './coordination/peer-dec
|
|
|
83
85
|
import { startLoopLagMonitor } from './core/write-telemetry.js';
|
|
84
86
|
import { buildWhoami, formatWhoami } from './core/whoami.js';
|
|
85
87
|
import { renderTaskEndInvitation, validateRecipeWrite, recipeSlug, getRecipe } from './recipes/index.js';
|
|
88
|
+
import { formatRecallResultLine } from './core/format-recall.js';
|
|
86
89
|
|
|
87
90
|
// --- Incognito Mode ---
|
|
88
91
|
// When AWM_INCOGNITO=1, register zero tools. Claude won't see memory tools at all.
|
|
@@ -461,22 +464,11 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
|
|
|
461
464
|
};
|
|
462
465
|
}
|
|
463
466
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
// D8 (2026-07-30): conflict surfacing — a superseded memory that still
|
|
470
|
-
// ranks is shown WITH its replacement pointer instead of silently
|
|
471
|
-
// down-ranked. The model should trust the successor.
|
|
472
|
-
const chain = r.engram.supersededBy
|
|
473
|
-
? ` ⚠ SUPERSEDED by ${r.engram.supersededBy} — treat as historical; recall/fetch the successor before relying on this.`
|
|
474
|
-
: '';
|
|
475
|
-
const validity = r.engram.validTo
|
|
476
|
-
? ` [valid until ${r.engram.validTo}]`
|
|
477
|
-
: '';
|
|
478
|
-
return `${i + 1}. **${r.engram.concept}** (${r.score.toFixed(3)})${validity}: ${body}${chain}`;
|
|
479
|
-
});
|
|
467
|
+
// Confidence-adaptive output (Paper 3: cognitive teaming) and D8
|
|
468
|
+
// (2026-07-30) conflict surfacing both live in the shared formatter now —
|
|
469
|
+
// see core/format-recall.ts for why it's extracted (0.12.1: unit-testable
|
|
470
|
+
// without booting the server) and why the id sits after the score.
|
|
471
|
+
const lines = results.map(formatRecallResultLine);
|
|
480
472
|
|
|
481
473
|
return {
|
|
482
474
|
content: [{
|
|
@@ -564,7 +556,7 @@ Use this when:
|
|
|
564
556
|
|
|
565
557
|
The old memory stays in the database (searchable for history) but is heavily down-ranked in recall so the current version dominates.`,
|
|
566
558
|
{
|
|
567
|
-
old_engram_id: z.string().describe('ID of the outdated memory'),
|
|
559
|
+
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)'),
|
|
568
560
|
new_engram_id: z.string().describe('ID of the replacement memory'),
|
|
569
561
|
reason: z.string().optional().describe('Why the old memory is outdated'),
|
|
570
562
|
},
|
|
@@ -1288,6 +1280,35 @@ async function main() {
|
|
|
1288
1280
|
agentId: AGENT_ID,
|
|
1289
1281
|
secret: HOOK_SECRET,
|
|
1290
1282
|
port: HOOK_PORT,
|
|
1283
|
+
// 0.12.2: warm recall for hooks — the sidecar shares this process's
|
|
1284
|
+
// activation engine and loaded models, so a UserPromptSubmit hook can get
|
|
1285
|
+
// warm-latency recall without any standing server. Trimmed result shape
|
|
1286
|
+
// (no embeddings/phase scores — hooks don't need them and the vectors
|
|
1287
|
+
// alone would 10x the payload).
|
|
1288
|
+
activate: async (q) => {
|
|
1289
|
+
const results = await activationEngine.activate({
|
|
1290
|
+
agentId: AGENT_ID,
|
|
1291
|
+
context: q.context,
|
|
1292
|
+
limit: q.limit,
|
|
1293
|
+
requireConfidence: q.requireConfidence,
|
|
1294
|
+
granularity: q.granularity,
|
|
1295
|
+
});
|
|
1296
|
+
return results.map(r => ({
|
|
1297
|
+
engram: {
|
|
1298
|
+
id: r.engram.id,
|
|
1299
|
+
concept: r.engram.concept,
|
|
1300
|
+
content: r.engram.content,
|
|
1301
|
+
createdAt: r.engram.createdAt instanceof Date
|
|
1302
|
+
? r.engram.createdAt.toISOString()
|
|
1303
|
+
: (r.engram.createdAt as unknown as string | undefined),
|
|
1304
|
+
memoryClass: r.engram.memoryClass,
|
|
1305
|
+
validTo: r.engram.validTo,
|
|
1306
|
+
},
|
|
1307
|
+
score: r.score,
|
|
1308
|
+
summary: r.summary,
|
|
1309
|
+
confidence: r.confidence,
|
|
1310
|
+
}));
|
|
1311
|
+
},
|
|
1291
1312
|
onConsolidate: async (agentId, reason) => {
|
|
1292
1313
|
console.error(`[mcp] consolidation triggered: ${reason}`);
|
|
1293
1314
|
const result = await consolidationEngine.consolidate(agentId);
|
|
@@ -1296,6 +1317,31 @@ async function main() {
|
|
|
1296
1317
|
},
|
|
1297
1318
|
});
|
|
1298
1319
|
|
|
1320
|
+
// 0.12.2: eager warm — fire-and-forget, mirrors index.ts:208-218. Without
|
|
1321
|
+
// this, every Claude Code session paid the full cold cost (~3s measured on
|
|
1322
|
+
// a 29.7k-engram store: slim cache ~0.9s + three model loads ~1.8s) on its
|
|
1323
|
+
// FIRST recall, which is exactly the "first recall is slow → recall gets
|
|
1324
|
+
// avoided" failure mode. Warming here overlaps session startup instead.
|
|
1325
|
+
// All output is stderr-safe (stdout carries JSON-RPC frames).
|
|
1326
|
+
// Escape hatch: AWM_NO_EAGER_WARM=1 restores lazy loading.
|
|
1327
|
+
if (process.env.AWM_NO_EAGER_WARM !== '1') {
|
|
1328
|
+
getEmbedder().catch(err => console.error('Embedding model unavailable:', err.message));
|
|
1329
|
+
getReranker().catch(err => console.error('Reranker model unavailable:', err.message));
|
|
1330
|
+
getExpander().catch(err => console.error('Query expander model unavailable:', err.message));
|
|
1331
|
+
if (BACKEND === 'sqlite') {
|
|
1332
|
+
setImmediate(() => {
|
|
1333
|
+
try {
|
|
1334
|
+
const t0 = Date.now();
|
|
1335
|
+
(store as unknown as EngramStore).warmSlimCache();
|
|
1336
|
+
const stats = (store as unknown as EngramStore).getSlimCacheStats();
|
|
1337
|
+
console.error(`Slim cache warmed: ${stats.size} entries in ${Date.now() - t0}ms`);
|
|
1338
|
+
} catch (err) {
|
|
1339
|
+
console.error(`Slim cache warm failed: ${(err as Error).message}`);
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1299
1345
|
// Coordination MCP tools (opt-in via AWM_COORDINATION=true)
|
|
1300
1346
|
// AWM 0.8.x: coordination requires SQLite (uses store.getDb()). On PGlite,
|
|
1301
1347
|
// coordination is auto-disabled with a warning; re-enable when coordination
|
package/src/storage/pglite.ts
CHANGED
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { PGlite } from '@electric-sql/pglite';
|
|
16
|
-
|
|
16
|
+
// pglite 0.5.0 moved contrib extensions to their own npm packages — the './vector'
|
|
17
|
+
// subpath export was removed from @electric-sql/pglite itself. Same extensions:{vector}
|
|
18
|
+
// usage pattern, new package.
|
|
19
|
+
import { vector } from '@electric-sql/pglite-pgvector';
|
|
17
20
|
import { randomUUID } from 'node:crypto';
|
|
18
21
|
|
|
19
22
|
import type {
|