agent-recall-core 3.4.27 → 3.4.30
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/dist/palace/awareness.d.ts.map +1 -1
- package/dist/palace/awareness.js +6 -2
- package/dist/palace/awareness.js.map +1 -1
- package/dist/relevance/injector.d.ts +51 -0
- package/dist/relevance/injector.d.ts.map +1 -0
- package/dist/relevance/injector.js +121 -0
- package/dist/relevance/injector.js.map +1 -0
- package/dist/relevance/precision.d.ts +109 -0
- package/dist/relevance/precision.d.ts.map +1 -0
- package/dist/relevance/precision.js +279 -0
- package/dist/relevance/precision.js.map +1 -0
- package/dist/relevance/promoter.d.ts +60 -0
- package/dist/relevance/promoter.d.ts.map +1 -0
- package/dist/relevance/promoter.js +336 -0
- package/dist/relevance/promoter.js.map +1 -0
- package/dist/relevance/stager.d.ts +61 -0
- package/dist/relevance/stager.d.ts.map +1 -0
- package/dist/relevance/stager.js +251 -0
- package/dist/relevance/stager.js.map +1 -0
- package/dist/relevance/surfacer.d.ts +34 -0
- package/dist/relevance/surfacer.d.ts.map +1 -0
- package/dist/relevance/surfacer.js +333 -0
- package/dist/relevance/surfacer.js.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Precision meter — `injection_precision = used / injected`
|
|
3
|
+
* (v3.5.0 P-C, Ambient Relevance Loop, spec §5 + §6 "Precision guardrail").
|
|
4
|
+
*
|
|
5
|
+
* Purpose: this is the north-star metric that proves "intelligence, not
|
|
6
|
+
* storage." Of the memories AgentRecall surfaces (`injected`), how many
|
|
7
|
+
* does the agent actually act on (`used`)? A falling number is the
|
|
8
|
+
* early-warning that the Surfacer threshold is mis-tuned (noise > signal).
|
|
9
|
+
*
|
|
10
|
+
* Storage shape — append-only JSONL, one file per kind, per project:
|
|
11
|
+
*
|
|
12
|
+
* ~/.agent-recall/projects/<slug>/_precision/injections.jsonl
|
|
13
|
+
* ~/.agent-recall/projects/<slug>/_precision/usages.jsonl
|
|
14
|
+
*
|
|
15
|
+
* Design contract:
|
|
16
|
+
* • `recordInjection`/`recordUsage` are fire-and-forget — they NEVER
|
|
17
|
+
* throw. Losing a measurement line is acceptable; killing a user turn
|
|
18
|
+
* is not. Errors are swallowed silently (mirrors stager.ts).
|
|
19
|
+
* • `getPrecisionKPI` is tolerant of malformed lines (skipped, not
|
|
20
|
+
* fatal), missing files (treated as empty), and missing fields.
|
|
21
|
+
* • `precision` is **null** (not NaN) when `injected === 0` — the
|
|
22
|
+
* dashboard can render "—" instead of "NaN%".
|
|
23
|
+
* • `precision` is clamped to `[0, 1]` via `Math.min(1, used / injected)`
|
|
24
|
+
* (matches the existing pattern in storage/corrections.ts:373 — under
|
|
25
|
+
* data corruption / duplicate-usage events, the raw ratio can exceed
|
|
26
|
+
* 1.0, which is nonsense as a "precision" metric).
|
|
27
|
+
* • `per_source` groups by the prefix of `candidate_id` before ':'
|
|
28
|
+
* (e.g. `palace:room:slug` → bucket `palace`). Unprefixed ids land in
|
|
29
|
+
* the `unknown` bucket so they're still visible.
|
|
30
|
+
*
|
|
31
|
+
* Worker done-definition self-check:
|
|
32
|
+
* (1) error paths: `readJsonl` returns [] on read failure, JSON.parse
|
|
33
|
+
* wrapped in try/catch per line; recordInjection/Usage's outer
|
|
34
|
+
* try/catch swallows fs errors.
|
|
35
|
+
* (2) no global binaries — pure node:fs/path/crypto, no shell-outs.
|
|
36
|
+
* (3) ternary ordering — only branch is `injected > 0 ? clamp : null`,
|
|
37
|
+
* null is the explicit zero-denominator case.
|
|
38
|
+
* (4) time logic — `cutoff` is computed in ms-since-epoch from `Date.now()`
|
|
39
|
+
* which is TZ-agnostic; `ts` strings are ISO-8601 (also TZ-agnostic).
|
|
40
|
+
* No local-date filename is used here so no TZ bug surface.
|
|
41
|
+
*/
|
|
42
|
+
import * as fs from "node:fs";
|
|
43
|
+
import * as path from "node:path";
|
|
44
|
+
import { sanitizeProject } from "../storage/paths.js";
|
|
45
|
+
import { ensureDir } from "../storage/fs-utils.js";
|
|
46
|
+
import { getRoot } from "../types.js";
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Internal helpers
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
/** Resolve the per-project precision directory. Mirrors stager's `_staging/` convention. */
|
|
51
|
+
function precisionDir(project) {
|
|
52
|
+
const safe = sanitizeProject(project);
|
|
53
|
+
return path.join(getRoot(), "projects", safe, "_precision");
|
|
54
|
+
}
|
|
55
|
+
function injectionsPath(project) {
|
|
56
|
+
return path.join(precisionDir(project), "injections.jsonl");
|
|
57
|
+
}
|
|
58
|
+
function usagesPath(project) {
|
|
59
|
+
return path.join(precisionDir(project), "usages.jsonl");
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Parse a JSONL file. Bad lines (truncated writes, manual edits) are
|
|
63
|
+
* silently skipped — never throws. Missing file → empty array.
|
|
64
|
+
*
|
|
65
|
+
* Generic over the row shape; caller is responsible for narrowing.
|
|
66
|
+
*/
|
|
67
|
+
function readJsonl(filePath) {
|
|
68
|
+
if (!fs.existsSync(filePath))
|
|
69
|
+
return [];
|
|
70
|
+
let raw;
|
|
71
|
+
try {
|
|
72
|
+
raw = fs.readFileSync(filePath, "utf-8");
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
const out = [];
|
|
78
|
+
const lines = raw.split("\n");
|
|
79
|
+
for (const line of lines) {
|
|
80
|
+
const trimmed = line.trim();
|
|
81
|
+
if (!trimmed)
|
|
82
|
+
continue;
|
|
83
|
+
try {
|
|
84
|
+
out.push(JSON.parse(trimmed));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Malformed line — skip, never throw.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Append one row to a JSONL file. Wraps the `fs.appendFileSync` call so
|
|
94
|
+
* the public recorders can swallow fs errors at the boundary.
|
|
95
|
+
*/
|
|
96
|
+
function appendJsonl(filePath, row) {
|
|
97
|
+
ensureDir(path.dirname(filePath));
|
|
98
|
+
fs.appendFileSync(filePath, JSON.stringify(row) + "\n", "utf-8");
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Extract the source prefix from a candidate id.
|
|
102
|
+
* `palace:room:slug` → `palace`. Unprefixed → `unknown`.
|
|
103
|
+
*/
|
|
104
|
+
function sourceOf(candidateId) {
|
|
105
|
+
if (typeof candidateId !== "string" || candidateId.length === 0)
|
|
106
|
+
return "unknown";
|
|
107
|
+
const idx = candidateId.indexOf(":");
|
|
108
|
+
if (idx <= 0)
|
|
109
|
+
return "unknown";
|
|
110
|
+
return candidateId.slice(0, idx);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Narrow an unknown value to `InjectionEvent` shape (defensive — JSONL may
|
|
114
|
+
* have been edited by hand). Returns null on miss.
|
|
115
|
+
*/
|
|
116
|
+
function asInjectionEvent(v) {
|
|
117
|
+
if (!v || typeof v !== "object")
|
|
118
|
+
return null;
|
|
119
|
+
const o = v;
|
|
120
|
+
if (typeof o.ts !== "string")
|
|
121
|
+
return null;
|
|
122
|
+
if (!Array.isArray(o.candidate_ids))
|
|
123
|
+
return null;
|
|
124
|
+
if (typeof o.turn_hash !== "string")
|
|
125
|
+
return null;
|
|
126
|
+
const cids = o.candidate_ids.filter((c) => typeof c === "string");
|
|
127
|
+
return { ts: o.ts, candidate_ids: cids, turn_hash: o.turn_hash };
|
|
128
|
+
}
|
|
129
|
+
function asUsageEvent(v) {
|
|
130
|
+
if (!v || typeof v !== "object")
|
|
131
|
+
return null;
|
|
132
|
+
const o = v;
|
|
133
|
+
if (typeof o.ts !== "string")
|
|
134
|
+
return null;
|
|
135
|
+
if (typeof o.candidate_id !== "string")
|
|
136
|
+
return null;
|
|
137
|
+
if (typeof o.turn_hash !== "string")
|
|
138
|
+
return null;
|
|
139
|
+
return {
|
|
140
|
+
ts: o.ts,
|
|
141
|
+
candidate_id: o.candidate_id,
|
|
142
|
+
turn_hash: o.turn_hash,
|
|
143
|
+
evidence: typeof o.evidence === "string" ? o.evidence : "",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Public API
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
/**
|
|
150
|
+
* Append one injection event to `_precision/injections.jsonl`.
|
|
151
|
+
*
|
|
152
|
+
* NEVER THROWS. Mirrors `stage()` — the read/write loop continues
|
|
153
|
+
* regardless of fs errors. Losing a measurement is acceptable; killing a
|
|
154
|
+
* user turn is not.
|
|
155
|
+
*/
|
|
156
|
+
export function recordInjection(project, ev) {
|
|
157
|
+
try {
|
|
158
|
+
if (!ev || typeof ev !== "object")
|
|
159
|
+
return;
|
|
160
|
+
const safe = {
|
|
161
|
+
ts: typeof ev.ts === "string" && ev.ts.length > 0
|
|
162
|
+
? ev.ts
|
|
163
|
+
: new Date().toISOString(),
|
|
164
|
+
candidate_ids: Array.isArray(ev.candidate_ids)
|
|
165
|
+
? ev.candidate_ids.filter((c) => typeof c === "string")
|
|
166
|
+
: [],
|
|
167
|
+
turn_hash: typeof ev.turn_hash === "string" ? ev.turn_hash : "",
|
|
168
|
+
};
|
|
169
|
+
appendJsonl(injectionsPath(project), safe);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// Swallow — see contract above.
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Append one usage event to `_precision/usages.jsonl`.
|
|
177
|
+
*
|
|
178
|
+
* NEVER THROWS. One row per used candidate; the caller is expected to
|
|
179
|
+
* dedupe at the detection layer (one usage row per (turn_hash, candidate_id)).
|
|
180
|
+
*/
|
|
181
|
+
export function recordUsage(project, ev) {
|
|
182
|
+
try {
|
|
183
|
+
if (!ev || typeof ev !== "object")
|
|
184
|
+
return;
|
|
185
|
+
const safe = {
|
|
186
|
+
ts: typeof ev.ts === "string" && ev.ts.length > 0
|
|
187
|
+
? ev.ts
|
|
188
|
+
: new Date().toISOString(),
|
|
189
|
+
candidate_id: typeof ev.candidate_id === "string" ? ev.candidate_id : "",
|
|
190
|
+
turn_hash: typeof ev.turn_hash === "string" ? ev.turn_hash : "",
|
|
191
|
+
evidence: typeof ev.evidence === "string" ? ev.evidence : "",
|
|
192
|
+
};
|
|
193
|
+
if (!safe.candidate_id)
|
|
194
|
+
return; // can't bucket without an id
|
|
195
|
+
appendJsonl(usagesPath(project), safe);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Swallow.
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Compute the precision KPI over the trailing `windowDays` (default 14).
|
|
203
|
+
*
|
|
204
|
+
* Algorithm (spec §6):
|
|
205
|
+
* • cutoff = now - windowDays * 86400_000 ms
|
|
206
|
+
* • injected = Σ ev.candidate_ids.length over injections with ts ≥ cutoff
|
|
207
|
+
* • used = count of usage rows with ts ≥ cutoff
|
|
208
|
+
* • precision = injected > 0 ? Math.min(1, used / injected) : null
|
|
209
|
+
* • per_source = { <prefix>: { injected, used } } grouped by id-prefix
|
|
210
|
+
*
|
|
211
|
+
* Invariants:
|
|
212
|
+
* • `precision` is never NaN; it is either a finite number in [0, 1] or null.
|
|
213
|
+
* • Malformed jsonl lines are tolerated (skipped by `readJsonl`).
|
|
214
|
+
* • Empty project → injected=0, used=0, precision=null, per_source={}.
|
|
215
|
+
*/
|
|
216
|
+
export function getPrecisionKPI(project, windowDays) {
|
|
217
|
+
const days = typeof windowDays === "number" && Number.isFinite(windowDays) && windowDays > 0
|
|
218
|
+
? windowDays
|
|
219
|
+
: 14;
|
|
220
|
+
const cutoffMs = Date.now() - days * 86400_000;
|
|
221
|
+
const rawInjections = readJsonl(injectionsPath(project));
|
|
222
|
+
const rawUsages = readJsonl(usagesPath(project));
|
|
223
|
+
const injections = [];
|
|
224
|
+
for (const r of rawInjections) {
|
|
225
|
+
const ev = asInjectionEvent(r);
|
|
226
|
+
if (!ev)
|
|
227
|
+
continue;
|
|
228
|
+
const t = Date.parse(ev.ts);
|
|
229
|
+
if (!Number.isFinite(t))
|
|
230
|
+
continue; // unparseable ts → skip
|
|
231
|
+
if (t < cutoffMs)
|
|
232
|
+
continue;
|
|
233
|
+
injections.push(ev);
|
|
234
|
+
}
|
|
235
|
+
const usages = [];
|
|
236
|
+
for (const r of rawUsages) {
|
|
237
|
+
const ev = asUsageEvent(r);
|
|
238
|
+
if (!ev)
|
|
239
|
+
continue;
|
|
240
|
+
const t = Date.parse(ev.ts);
|
|
241
|
+
if (!Number.isFinite(t))
|
|
242
|
+
continue;
|
|
243
|
+
if (t < cutoffMs)
|
|
244
|
+
continue;
|
|
245
|
+
usages.push(ev);
|
|
246
|
+
}
|
|
247
|
+
let injected = 0;
|
|
248
|
+
const perSource = {};
|
|
249
|
+
for (const ev of injections) {
|
|
250
|
+
for (const cid of ev.candidate_ids) {
|
|
251
|
+
injected++;
|
|
252
|
+
const src = sourceOf(cid);
|
|
253
|
+
if (!perSource[src])
|
|
254
|
+
perSource[src] = { injected: 0, used: 0 };
|
|
255
|
+
perSource[src].injected++;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const used = usages.length;
|
|
259
|
+
for (const ev of usages) {
|
|
260
|
+
const src = sourceOf(ev.candidate_id);
|
|
261
|
+
if (!perSource[src])
|
|
262
|
+
perSource[src] = { injected: 0, used: 0 };
|
|
263
|
+
perSource[src].used++;
|
|
264
|
+
}
|
|
265
|
+
// Clamp [0, 1]: data corruption (duplicate usages, late-arriving rows after
|
|
266
|
+
// injections were rotated out, etc.) can push raw used/injected above 1.0.
|
|
267
|
+
// Matches storage/corrections.ts:373 — "150% precision" is nonsense.
|
|
268
|
+
// null (not NaN) is the explicit zero-denominator case so the dashboard
|
|
269
|
+
// can render "—" rather than "NaN%".
|
|
270
|
+
const precision = injected > 0 ? Math.min(1, used / injected) : null;
|
|
271
|
+
return {
|
|
272
|
+
window_days: days,
|
|
273
|
+
injected,
|
|
274
|
+
used,
|
|
275
|
+
precision,
|
|
276
|
+
per_source: perSource,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=precision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"precision.js","sourceRoot":"","sources":["../../src/relevance/precision.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promoter — curates the staging buffer into the palace/journal at session
|
|
3
|
+
* boundaries (v3.5.0 P-A, spec §4 "Promote", §5).
|
|
4
|
+
*
|
|
5
|
+
* Flow:
|
|
6
|
+
* 1. Read up to N most-recent staged exchanges via `readStaging`.
|
|
7
|
+
* 2. Apply a pure-function durability heuristic — `isDurableSignal` — to
|
|
8
|
+
* drop chitchat, single-word turns, tool-only exchanges.
|
|
9
|
+
* 3. Dedupe survivors against the project's recent journal titles
|
|
10
|
+
* (`tooSimilarToExistingJournal`, threshold 0.7).
|
|
11
|
+
* 4. Write the survivors via the existing `journalWrite` tool, then clear
|
|
12
|
+
* the staging file up to the newest promoted timestamp.
|
|
13
|
+
*
|
|
14
|
+
* Design notes:
|
|
15
|
+
* • The heuristic is intentionally cheap and pure. It is the FIRST pass —
|
|
16
|
+
* the in-room model (called separately by `sessionEndReflect`) is the
|
|
17
|
+
* true fine-grain judge. Here we just trim the obvious noise.
|
|
18
|
+
* • `opts.dryRun` runs the whole pipeline without writing the journal or
|
|
19
|
+
* clearing staging — used by tests and the dashboard preview.
|
|
20
|
+
* • Threshold ordering inside `isDurableSignal` is high-confidence first
|
|
21
|
+
* (explicit markers) then weaker signals (length + lexical proxy)
|
|
22
|
+
* (worker done-def rule 3).
|
|
23
|
+
*/
|
|
24
|
+
import { type StagedExchange } from "./stager.js";
|
|
25
|
+
export interface PromoteResult {
|
|
26
|
+
promoted: number;
|
|
27
|
+
skipped: number;
|
|
28
|
+
/** Counters keyed by reason for downstream dashboards and tests. */
|
|
29
|
+
reasons: Record<string, number>;
|
|
30
|
+
}
|
|
31
|
+
export interface PromoteOptions {
|
|
32
|
+
/** When true, runs the heuristic + dedup but writes nothing. */
|
|
33
|
+
dryRun?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Decide whether a staged exchange carries a durable signal.
|
|
37
|
+
*
|
|
38
|
+
* Returns TRUE on any of:
|
|
39
|
+
* 1. Explicit marker (`decision`, `bug`, `gotcha`, …).
|
|
40
|
+
* 2. Correction shape (`no … wrong`, `actually`, `correction`, `the right way`).
|
|
41
|
+
* 3. Contains a wikilink or fenced code block (durable artifact).
|
|
42
|
+
* 4. Length ≥ 120 chars AND has both an "important word" and an action verb
|
|
43
|
+
* (noun-verb proxy — catches substantive sentences).
|
|
44
|
+
*
|
|
45
|
+
* Returns FALSE for chitchat ("ok", "thanks"), single-sentence questions,
|
|
46
|
+
* and tool-only turns (no user/assistant text).
|
|
47
|
+
*/
|
|
48
|
+
export declare function isDurableSignal(e: StagedExchange): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Promote durable staged exchanges into the journal.
|
|
51
|
+
*
|
|
52
|
+
* Idempotent within a session: once promoted, the corresponding staging
|
|
53
|
+
* entries are cleared (only the newest-promoted timestamp is used as the
|
|
54
|
+
* `beforeTs` cursor, so any post-promote stage() calls survive).
|
|
55
|
+
*
|
|
56
|
+
* `opts.dryRun = true` runs the full pipeline (incl. dedup) but writes
|
|
57
|
+
* nothing and clears nothing. Useful for tests and dashboard previews.
|
|
58
|
+
*/
|
|
59
|
+
export declare function promote(project: string, opts?: PromoteOptions): Promise<PromoteResult>;
|
|
60
|
+
//# sourceMappingURL=promoter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promoter.d.ts","sourceRoot":"","sources":["../../src/relevance/promoter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAoC5E,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,gEAAgE;IAChE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,cAAc,GAAG,OAAO,CAmB1D;AAqFD;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,aAAa,CAAC,CA+JxB"}
|