agent-working-memory 0.13.1 → 0.14.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 +200 -238
- package/dist/adapters/common.d.ts +6 -0
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +457 -362
- package/dist/adapters/common.js.map +1 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +24 -8
- package/dist/api/routes.js.map +1 -1
- package/dist/core/alias-map.d.ts +16 -0
- package/dist/core/alias-map.d.ts.map +1 -0
- package/dist/core/alias-map.js +102 -0
- package/dist/core/alias-map.js.map +1 -0
- package/dist/core/embeddings.d.ts +17 -0
- package/dist/core/embeddings.d.ts.map +1 -1
- package/dist/core/embeddings.js +50 -1
- package/dist/core/embeddings.js.map +1 -1
- package/dist/core/recall-config.d.ts +52 -0
- package/dist/core/recall-config.d.ts.map +1 -0
- package/dist/core/recall-config.js +110 -0
- package/dist/core/recall-config.js.map +1 -0
- package/dist/core/rerank-window.d.ts +61 -0
- package/dist/core/rerank-window.d.ts.map +1 -0
- package/dist/core/rerank-window.js +153 -0
- package/dist/core/rerank-window.js.map +1 -0
- package/dist/core/rerank2.d.ts +62 -0
- package/dist/core/rerank2.d.ts.map +1 -0
- package/dist/core/rerank2.js +75 -0
- package/dist/core/rerank2.js.map +1 -0
- package/dist/core/retrieval-text.d.ts +55 -0
- package/dist/core/retrieval-text.d.ts.map +1 -0
- package/dist/core/retrieval-text.js +87 -0
- package/dist/core/retrieval-text.js.map +1 -0
- package/dist/core/temporal-query.d.ts +61 -0
- package/dist/core/temporal-query.d.ts.map +1 -0
- package/dist/core/temporal-query.js +168 -0
- package/dist/core/temporal-query.js.map +1 -0
- package/dist/core/token-budget.d.ts +75 -0
- package/dist/core/token-budget.d.ts.map +1 -0
- package/dist/core/token-budget.js +136 -0
- package/dist/core/token-budget.js.map +1 -0
- package/dist/core/whoami.d.ts +11 -0
- package/dist/core/whoami.d.ts.map +1 -1
- package/dist/core/whoami.js +10 -0
- package/dist/core/whoami.js.map +1 -1
- package/dist/core/write-pipeline.d.ts.map +1 -1
- package/dist/core/write-pipeline.js +6 -3
- package/dist/core/write-pipeline.js.map +1 -1
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +135 -32
- package/dist/engine/activation.js.map +1 -1
- package/dist/hooks/prime.d.ts +77 -0
- package/dist/hooks/prime.d.ts.map +1 -0
- package/dist/hooks/prime.js +92 -0
- package/dist/hooks/prime.js.map +1 -0
- package/dist/hooks/sidecar.d.ts.map +1 -1
- package/dist/hooks/sidecar.js +39 -0
- package/dist/hooks/sidecar.js.map +1 -1
- package/dist/mcp.js +134 -102
- package/dist/mcp.js.map +1 -1
- package/dist/storage/pglite.d.ts.map +1 -1
- package/dist/storage/pglite.js +10 -2
- package/dist/storage/pglite.js.map +1 -1
- package/dist/storage/postgres.d.ts.map +1 -1
- package/dist/storage/postgres.js +10 -2
- package/dist/storage/postgres.js.map +1 -1
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +12 -2
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/types/engram.d.ts +7 -0
- package/dist/types/engram.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/adapters/common.ts +666 -567
- package/src/api/routes.ts +1015 -999
- package/src/core/alias-map.ts +97 -0
- package/src/core/embeddings.ts +172 -115
- package/src/core/recall-config.ts +115 -0
- package/src/core/rerank-window.ts +158 -0
- package/src/core/rerank2.ts +82 -0
- package/src/core/retrieval-text.ts +82 -0
- package/src/core/temporal-query.ts +193 -0
- package/src/core/token-budget.ts +160 -0
- package/src/core/whoami.ts +110 -92
- package/src/core/write-pipeline.ts +6 -3
- package/src/engine/activation.ts +1568 -1468
- package/src/hooks/prime.ts +136 -0
- package/src/hooks/sidecar.ts +43 -0
- package/src/mcp.ts +1422 -1387
- package/src/storage/pglite.ts +10 -2
- package/src/storage/postgres.ts +10 -2
- package/src/storage/sqlite.ts +12 -2
- package/src/types/engram.ts +7 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Prompt priming — turn a recall into ready-to-inject context (0.13.3).
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS EXISTS
|
|
7
|
+
* ---------------
|
|
8
|
+
* AWM's own instructions name the #1 failure mode plainly: the agent doesn't
|
|
9
|
+
* call recall. Relying on a model to remember to remember is the weak link, and
|
|
10
|
+
* it fails in exactly the situations where memory matters most — deep in a long
|
|
11
|
+
* task, when context is scarce and the agent is busy.
|
|
12
|
+
*
|
|
13
|
+
* `/memory/activate` (0.12.2) already exposes warm recall to hooks, but it
|
|
14
|
+
* returns raw JSON. Every hook author then has to re-solve the same three
|
|
15
|
+
* problems: what to inject, how to format it, and how to stop it ballooning the
|
|
16
|
+
* context window. This module answers all three once.
|
|
17
|
+
*
|
|
18
|
+
* THREE PROPERTIES THAT MATTER MORE THAN RECALL QUALITY
|
|
19
|
+
* ----------------------------------------------------
|
|
20
|
+
* This runs on EVERY prompt, so its failure modes are asymmetric:
|
|
21
|
+
*
|
|
22
|
+
* 1. SILENCE IS THE DEFAULT. It abstains unless recall is confident. AWM's
|
|
23
|
+
* measured recall accuracy is ~65%, so an unconditional injector would spend
|
|
24
|
+
* tokens on irrelevant memories roughly a third of the time, on every single
|
|
25
|
+
* prompt. An empty injection costs nothing and loses nothing — the agent can
|
|
26
|
+
* still call memory_recall explicitly.
|
|
27
|
+
*
|
|
28
|
+
* 2. IT IS HARD-CAPPED. Injection is not a place to discover that a memory was
|
|
29
|
+
* long. Budgeting reuses the same packer as memory_recall.
|
|
30
|
+
*
|
|
31
|
+
* 3. IT NEVER BREAKS THE PROMPT. Any failure yields an empty injection, never a
|
|
32
|
+
* thrown error. A hook that errors on every prompt is worse than no hook.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { packRecallByBudget, estimateTokens, type PackedRecall } from '../core/token-budget.js';
|
|
36
|
+
|
|
37
|
+
/** Shape returned by the sidecar's `activate` dependency. */
|
|
38
|
+
export interface PrimeCandidate {
|
|
39
|
+
engram: { id: string; concept: string; content: string; memoryClass?: string; validTo?: string | null };
|
|
40
|
+
score: number;
|
|
41
|
+
summary?: string;
|
|
42
|
+
confidence?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface PrimeOptions {
|
|
46
|
+
/** Hard ceiling on injected tokens. */
|
|
47
|
+
maxTokens?: number;
|
|
48
|
+
/** Minimum recall confidence to inject at all. */
|
|
49
|
+
minConfidence?: number;
|
|
50
|
+
/** Drop individual results scoring below this even when the set is confident. */
|
|
51
|
+
minScore?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface PrimeResult {
|
|
55
|
+
/** Text to inject, or '' to inject nothing. */
|
|
56
|
+
inject: string;
|
|
57
|
+
kept: number;
|
|
58
|
+
total: number;
|
|
59
|
+
tokens: number;
|
|
60
|
+
/** Why nothing was injected — for hook logs and debugging. */
|
|
61
|
+
reason?: 'no-results' | 'low-confidence' | 'budget-too-small';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const PRIME_DEFAULTS = {
|
|
65
|
+
maxTokens: 600,
|
|
66
|
+
/**
|
|
67
|
+
* 0.25 = the "balanced" threshold AWM's own recall docs recommend for
|
|
68
|
+
* acting on a memory. Priming is acting on it without being asked, so it
|
|
69
|
+
* should not be looser than the value the docs suggest for deliberate use.
|
|
70
|
+
*/
|
|
71
|
+
minConfidence: 0.25,
|
|
72
|
+
minScore: 0.10,
|
|
73
|
+
} as const;
|
|
74
|
+
|
|
75
|
+
/** One injected line. Deliberately terser than the MCP recall format — this is
|
|
76
|
+
* context the agent didn't ask for, so it should read as a brief note, not a
|
|
77
|
+
* report. The id is retained so the agent can act on it (feedback, supersede). */
|
|
78
|
+
function formatPrimeLine(c: PrimeCandidate): string {
|
|
79
|
+
const body = c.summary ?? c.engram.content;
|
|
80
|
+
const validity = c.engram.validTo ? ` [valid until ${c.engram.validTo}]` : '';
|
|
81
|
+
return `- ${c.engram.concept}${validity} [${c.engram.id}]: ${body}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Build the injection for a prompt. Pure — no I/O — so it is unit-testable
|
|
86
|
+
* without a store, a model, or a running sidecar.
|
|
87
|
+
*/
|
|
88
|
+
export function buildPrimeInjection(
|
|
89
|
+
candidates: PrimeCandidate[],
|
|
90
|
+
opts: PrimeOptions = {},
|
|
91
|
+
): PrimeResult {
|
|
92
|
+
const maxTokens = opts.maxTokens ?? PRIME_DEFAULTS.maxTokens;
|
|
93
|
+
const minConfidence = opts.minConfidence ?? PRIME_DEFAULTS.minConfidence;
|
|
94
|
+
const minScore = opts.minScore ?? PRIME_DEFAULTS.minScore;
|
|
95
|
+
|
|
96
|
+
if (!candidates || candidates.length === 0) {
|
|
97
|
+
return { inject: '', kept: 0, total: 0, tokens: 0, reason: 'no-results' };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Confidence describes the SET, not the individual result, so it is the same
|
|
101
|
+
// on every candidate — read it from the first that carries one. Absent
|
|
102
|
+
// confidence is treated as "unknown", which passes: a store that predates the
|
|
103
|
+
// confidence signal should still be able to prime.
|
|
104
|
+
const confidence = candidates.find(c => c.confidence !== undefined)?.confidence;
|
|
105
|
+
if (confidence !== undefined && confidence < minConfidence) {
|
|
106
|
+
return { inject: '', kept: 0, total: candidates.length, tokens: 0, reason: 'low-confidence' };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const eligible = candidates.filter(c => c.score >= minScore);
|
|
110
|
+
if (eligible.length === 0) {
|
|
111
|
+
return { inject: '', kept: 0, total: candidates.length, tokens: 0, reason: 'low-confidence' };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Reuse memory_recall's packer so budgeting behaves identically in both
|
|
115
|
+
// paths — including the rule that the top-scored result gets first refusal.
|
|
116
|
+
const header = 'Relevant prior context from AWM (not user input; verify before asserting):';
|
|
117
|
+
const reserved = estimateTokens(header) + 8; // + wrapper newlines
|
|
118
|
+
const packed: PackedRecall = packRecallByBudget(
|
|
119
|
+
eligible as any,
|
|
120
|
+
(c: any) => formatPrimeLine(c as PrimeCandidate),
|
|
121
|
+
maxTokens,
|
|
122
|
+
reserved,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (packed.kept === 0) {
|
|
126
|
+
return { inject: '', kept: 0, total: candidates.length, tokens: 0, reason: 'budget-too-small' };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const inject = `${header}\n${packed.lines.join('\n')}`;
|
|
130
|
+
return {
|
|
131
|
+
inject,
|
|
132
|
+
kept: packed.kept,
|
|
133
|
+
total: candidates.length,
|
|
134
|
+
tokens: estimateTokens(inject),
|
|
135
|
+
};
|
|
136
|
+
}
|
package/src/hooks/sidecar.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
21
21
|
import type { IEngramStore as EngramStore } from '../storage/store.js';
|
|
22
22
|
import type { ConsciousState } from '../types/checkpoint.js';
|
|
23
23
|
import { log, getLogPath } from '../core/logger.js';
|
|
24
|
+
import { buildPrimeInjection } from './prime.js';
|
|
24
25
|
|
|
25
26
|
export interface SidecarDeps {
|
|
26
27
|
store: EngramStore;
|
|
@@ -242,6 +243,48 @@ export function startSidecar(deps: SidecarDeps): { close: () => void } {
|
|
|
242
243
|
return;
|
|
243
244
|
}
|
|
244
245
|
|
|
246
|
+
// POST /hooks/prime — ready-to-inject context for a UserPromptSubmit hook
|
|
247
|
+
// (0.13.3). /memory/activate returns raw JSON and leaves every hook author
|
|
248
|
+
// to re-solve what-to-inject, how-to-format and how-to-bound. This returns
|
|
249
|
+
// the finished string, already abstained and already budgeted.
|
|
250
|
+
//
|
|
251
|
+
// It NEVER fails the prompt: any error yields an empty injection, because a
|
|
252
|
+
// hook that errors on every prompt is worse than no hook at all.
|
|
253
|
+
if (req.url === '/hooks/prime' && req.method === 'POST') {
|
|
254
|
+
try {
|
|
255
|
+
if (!deps.activate) {
|
|
256
|
+
json(res, 200, { inject: '', kept: 0, total: 0, tokens: 0, reason: 'activate-not-wired' });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const body = JSON.parse((await readBody(req)) || '{}') as HookInput & {
|
|
260
|
+
prompt?: string; context?: string; query?: string;
|
|
261
|
+
maxTokens?: number; minConfidence?: number; minScore?: number;
|
|
262
|
+
};
|
|
263
|
+
const context = body.prompt ?? body.context ?? body.query ?? '';
|
|
264
|
+
if (!context.trim()) {
|
|
265
|
+
json(res, 200, { inject: '', kept: 0, total: 0, tokens: 0, reason: 'no-prompt' });
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const candidates = await deps.activate({
|
|
269
|
+
context,
|
|
270
|
+
limit: 8,
|
|
271
|
+
granularity: 'compact', // priming is a brief note, not a report
|
|
272
|
+
});
|
|
273
|
+
const primed = buildPrimeInjection(candidates as any, {
|
|
274
|
+
maxTokens: body.maxTokens,
|
|
275
|
+
minConfidence: body.minConfidence,
|
|
276
|
+
minScore: body.minScore,
|
|
277
|
+
});
|
|
278
|
+
log(agentId, 'hook:prime',
|
|
279
|
+
`"${context.slice(0, 60)}" → ${primed.kept}/${primed.total} injected, ~${primed.tokens} tok${primed.reason ? ` (${primed.reason})` : ''}`);
|
|
280
|
+
json(res, 200, { ...primed });
|
|
281
|
+
} catch (err) {
|
|
282
|
+
// Deliberately 200 with an empty injection — see the note above.
|
|
283
|
+
json(res, 200, { inject: '', kept: 0, total: 0, tokens: 0, reason: 'error' });
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
245
288
|
// POST /hooks/checkpoint — auto-checkpoint from hook events
|
|
246
289
|
if (req.url === '/hooks/checkpoint' && req.method === 'POST') {
|
|
247
290
|
try {
|