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.
@@ -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.log(`Query expander loaded in-process: ${MODEL_ID}`);
33
+ console.error(`Query expander loaded in-process: ${MODEL_ID}`);
34
34
  return inProcessInstance;
35
35
  });
36
36
  return inProcessInitPromise;
@@ -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.log(`Re-ranker model loaded in-process: ${MODEL_ID}`);
38
+ console.error(`Re-ranker model loaded in-process: ${MODEL_ID}`);
39
39
  })();
40
40
  return initPromise;
41
41
  }
@@ -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
- const lines = results.map((r, i) => {
465
- // Confidence-adaptive output (Paper 3: cognitive teaming). When the caller
466
- // requested 'compact' or 'auto' granularity, surface the engine-computed
467
- // summary instead of the full content same engram, less to read.
468
- const body = r.summary ?? r.engram.content;
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
@@ -13,7 +13,10 @@
13
13
  */
14
14
 
15
15
  import { PGlite } from '@electric-sql/pglite';
16
- import { vector } from '@electric-sql/pglite/vector';
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 {