@atrib/recall 0.14.6 → 1.0.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.
@@ -0,0 +1,314 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * The legacy `trace` / `trace_forward` tools, kept as permanent aliases over
4
+ * @atrib/recall's read surface (the walk shapes of the `recall` verb). Walks
5
+ * a record's informed_by chain backward (the reasoning chain that led to it)
6
+ * or forward (the records that cited it). Reads only the local mirror
7
+ * (~/.atrib/records/*.jsonl), same scope as recall.
8
+ *
9
+ * Moved here from @atrib/trace per the attest/recall rename; @atrib/trace
10
+ * re-exports this module so existing imports keep working, and the
11
+ * `recall` verb reaches the same implementation via shape='walk' with a
12
+ * direction argument.
13
+ */
14
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
15
+ import { z } from 'zod';
16
+ import { resolveEnvContextId, logReadPrimitiveCall } from '@atrib/mcp';
17
+ import { loadAllRecords } from './trace-storage.js';
18
+ import { traceBackward, traceForward } from './trace-walk.js';
19
+ const SHA256_REF_PATTERN = /^sha256:[0-9a-f]{64}$/;
20
+ const HEX_32_PATTERN = /^[0-9a-f]{32}$/;
21
+ export const TraceInput = z.object({
22
+ record_hash: z
23
+ .string()
24
+ .regex(SHA256_REF_PATTERN)
25
+ .describe("The 'sha256:<64-hex>' record_hash to start from. Walks backward via " +
26
+ 'informed_by edges from this record.'),
27
+ context_id: z
28
+ .string()
29
+ .regex(HEX_32_PATTERN)
30
+ .optional()
31
+ .describe('Optional 32-hex context_id scope. When set, edges crossing into a ' +
32
+ 'different context_id surface as dangling rather than expanding the ' +
33
+ 'walk. Defaults to process.env.ATRIB_CONTEXT_ID when valid; omit at ' +
34
+ 'the env-var level to walk cross-context. Used by Inspect-style ' +
35
+ "harnesses to keep each arm's trace inside its own context per D072."),
36
+ depth: z
37
+ .number()
38
+ .int()
39
+ .min(0)
40
+ .max(10)
41
+ .optional()
42
+ .describe('Maximum hop count from the starting record. Use 0 to return only ' +
43
+ 'the start record. Defaults to 3. Bounded ' +
44
+ 'at 10 to keep responses tractable; deeper chains should be walked ' +
45
+ 'in pieces by re-rooting at a returned upstream hash.'),
46
+ max_nodes: z
47
+ .number()
48
+ .int()
49
+ .min(1)
50
+ .max(500)
51
+ .optional()
52
+ .describe('Hard cap on total visited records. Defaults to 200. Prevents pathological ' +
53
+ 'fan-out (a record with hundreds of informed_by entries) from exploding ' +
54
+ 'the response.'),
55
+ compact: z
56
+ .boolean()
57
+ .optional()
58
+ .describe('When true (the default), per-record output omits signature/content_id/' +
59
+ 'spec_version/chain_root to keep the response small. Set false for full ' +
60
+ 'record bytes (useful for re-verification).'),
61
+ include_content: z
62
+ .boolean()
63
+ .optional()
64
+ .describe('When true and compact=true, include the D062 local mirror body as ' +
65
+ 'local_content and local_producer on each visited record. Defaults false ' +
66
+ 'so trace stays cheap for ordinary causal walks.'),
67
+ });
68
+ /** Pull a compact summary from the sidecar's content payload.
69
+ * Exported for unit testing of the per-event_type content-shape handling. */
70
+ export function summarizeSidecar(loadedRecord) {
71
+ if (!loadedRecord?.local)
72
+ return undefined;
73
+ const sc = loadedRecord.local;
74
+ const out = {};
75
+ if (sc.toolName)
76
+ out.tool_name = sc.toolName;
77
+ if (sc.producer)
78
+ out.producer = sc.producer;
79
+ const c = sc.content;
80
+ if (!out.tool_name && c && typeof c['tool_name'] === 'string') {
81
+ out.tool_name = c['tool_name'];
82
+ }
83
+ if (c && typeof c['span_kind'] === 'string')
84
+ out.span_kind = c['span_kind'];
85
+ if (c && typeof c['span_name'] === 'string')
86
+ out.span_name = c['span_name'];
87
+ if (c && typeof c['model_name'] === 'string')
88
+ out.model_name = c['model_name'];
89
+ if (c && typeof c['prompt_version'] === 'string')
90
+ out.prompt_version = c['prompt_version'];
91
+ if (c && typeof c['intent'] === 'string') {
92
+ out.intent = c['intent'].length > 200 ? c['intent'].slice(0, 197) + '...' : c['intent'];
93
+ }
94
+ if (c && typeof c['rationale'] === 'string') {
95
+ out.rationale =
96
+ c['rationale'].length > 200 ? c['rationale'].slice(0, 197) + '...' : c['rationale'];
97
+ }
98
+ if (c && Array.isArray(c['topics'])) {
99
+ out.topics = c['topics']
100
+ .filter((t) => typeof t === 'string')
101
+ .slice(0, 6);
102
+ }
103
+ // Per-event_type human-readable text. The `what` slot here is generic
104
+ // (it's the human-scannable summary regardless of event_type); the
105
+ // priority order pulls the normative D086 field for each shape, with
106
+ // an explicit final fallback to `summary` for legacy records.
107
+ // observation: content.what
108
+ // annotation: content.summary
109
+ // revision: content.new_position (D086-normative). Surface this
110
+ // so trace consumers can read the agent's stance shift
111
+ // without a separate recall call
112
+ // legacy: content.summary (catches pre-D086 extractor records)
113
+ if (c) {
114
+ let primary;
115
+ if (typeof c['what'] === 'string') {
116
+ primary = c['what'];
117
+ }
118
+ else if (typeof c['new_position'] === 'string') {
119
+ primary = c['new_position'];
120
+ }
121
+ else if (typeof c['summary'] === 'string') {
122
+ primary = c['summary'];
123
+ }
124
+ else if (typeof c['prompt'] === 'string') {
125
+ primary = c['prompt'];
126
+ }
127
+ else if (typeof c['output'] === 'string') {
128
+ primary = c['output'];
129
+ }
130
+ if (primary) {
131
+ out.what = primary.length > 200 ? primary.slice(0, 197) + '…' : primary;
132
+ }
133
+ }
134
+ if (c && typeof c['importance'] === 'string')
135
+ out.importance = c['importance'];
136
+ return Object.keys(out).length === 0 ? undefined : out;
137
+ }
138
+ export function compactVisited(v, danglingSet, byHash, includeContent) {
139
+ const indexed = byHash.get(v.record_hash);
140
+ const record = v.record;
141
+ const out = {
142
+ depth: v.depth,
143
+ record_hash: v.record_hash,
144
+ parent_hashes: v.parent_hashes,
145
+ source: v.source,
146
+ event_type: v.record?.event_type ?? null,
147
+ context_id: v.record?.context_id ?? null,
148
+ creator_key: v.record?.creator_key ?? null,
149
+ timestamp: v.record?.timestamp ?? null,
150
+ next_informed_by: v.next_informed_by,
151
+ next_resolved: v.next_informed_by.filter((h) => !danglingSet.has(h)),
152
+ next_dangling: v.next_informed_by.filter((h) => danglingSet.has(h)),
153
+ ...(summarizeSidecar(indexed) ? { sidecar_summary: summarizeSidecar(indexed) } : {}),
154
+ };
155
+ if (record) {
156
+ const informedBy = record.informed_by;
157
+ const toolName = record.tool_name;
158
+ const argsHash = record.args_hash;
159
+ const resultHash = record.result_hash;
160
+ if (Array.isArray(informedBy) && informedBy.length > 0)
161
+ out.informed_by = informedBy;
162
+ if (toolName)
163
+ out.tool_name = toolName;
164
+ if (argsHash)
165
+ out.args_hash = argsHash;
166
+ if (resultHash)
167
+ out.result_hash = resultHash;
168
+ }
169
+ if (includeContent && indexed?.local?.content !== undefined) {
170
+ out.local_content = indexed.local.content;
171
+ }
172
+ if (includeContent && indexed?.local?.producer !== undefined) {
173
+ out.local_producer = indexed.local.producer;
174
+ }
175
+ return out;
176
+ }
177
+ export function extractRecordHashFieldsFromMcpResult(result) {
178
+ const seen = new Set();
179
+ const pattern = /^sha256:[0-9a-f]{64}$/;
180
+ const content = result?.content;
181
+ const text = Array.isArray(content) &&
182
+ typeof content[0]?.text === 'string'
183
+ ? content[0].text
184
+ : undefined;
185
+ let root = result;
186
+ if (text) {
187
+ try {
188
+ root = JSON.parse(text);
189
+ }
190
+ catch {
191
+ root = result;
192
+ }
193
+ }
194
+ walk(root);
195
+ return Array.from(seen);
196
+ function walk(node) {
197
+ if (node === null || node === undefined)
198
+ return;
199
+ if (Array.isArray(node)) {
200
+ for (const item of node)
201
+ walk(item);
202
+ return;
203
+ }
204
+ if (typeof node !== 'object')
205
+ return;
206
+ const obj = node;
207
+ if (typeof obj.record_hash === 'string' && pattern.test(obj.record_hash)) {
208
+ seen.add(obj.record_hash);
209
+ }
210
+ for (const value of Object.values(obj))
211
+ walk(value);
212
+ }
213
+ }
214
+ /** Shared shape: build a compact payload from a TraceResult. */
215
+ function buildPayload(result, byHash, compact, includeContent) {
216
+ const danglingSet = new Set(result.dangling);
217
+ return compact
218
+ ? {
219
+ start_hash: result.start_hash,
220
+ direction: result.direction,
221
+ depth_requested: result.depth_requested,
222
+ depth_reached: result.depth_reached,
223
+ visited: result.visited.map((v) => compactVisited(v, danglingSet, byHash, includeContent)),
224
+ dangling: result.dangling,
225
+ truncated_by_depth: result.truncated_by_depth,
226
+ truncated_by_cap: result.truncated_by_cap,
227
+ warnings: result.warnings,
228
+ }
229
+ : { ...result };
230
+ }
231
+ /**
232
+ * Run one informed_by walk and return the JSON payload. This is the single
233
+ * implementation behind the legacy `trace` / `trace_forward` tools AND the
234
+ * `recall` verb's shape='walk' with a direction, so their results are
235
+ * JSON-identical by construction (read-equivalence conformance family).
236
+ */
237
+ export function runTraceWalk(direction, args) {
238
+ const depth = args.depth ?? 3;
239
+ const maxNodes = args.max_nodes ?? 200;
240
+ const compact = args.compact ?? true;
241
+ const includeContent = args.include_content ?? false;
242
+ // Env-var context_id default: when the caller omitted context_id,
243
+ // fall back to @atrib/mcp's resolveEnvContextId (D078 + D083
244
+ // precedence: ATRIB_CONTEXT_ID, then a registered harness env var
245
+ // like CLAUDE_CODE_SESSION_ID). Explicit args.context_id always wins.
246
+ const contextId = args.context_id ?? resolveEnvContextId();
247
+ const { byHash } = loadAllRecords();
248
+ const walkFn = direction === 'backward' ? traceBackward : traceForward;
249
+ const result = walkFn(args.record_hash, depth, byHash, {
250
+ maxNodes,
251
+ ...(contextId ? { contextId } : {}),
252
+ });
253
+ return buildPayload(result, byHash, compact, includeContent);
254
+ }
255
+ /** Register the legacy `trace` + `trace_forward` tools on a server. */
256
+ export function registerTraceTools(mcp) {
257
+ mcp.registerTool('trace', {
258
+ title: 'trace informed_by chain backward',
259
+ description: "Walk a record's informed_by chain backward to surface the reasoning " +
260
+ 'chain that led to it. Reads from the local signed-record mirror ' +
261
+ '(~/.atrib/records/*.jsonl). Returns the records visited, parent links ' +
262
+ 'so the caller can reconstruct the tree, and dangling hashes (records ' +
263
+ 'referenced via informed_by but not present in the local mirror). ' +
264
+ 'Sibling tool `trace_forward` walks the opposite direction (records ' +
265
+ 'that cited THIS one via their informed_by). Legacy alias: new callers ' +
266
+ "should prefer the `recall` tool with shape='walk', direction='backward'.",
267
+ inputSchema: TraceInput.shape,
268
+ }, async (args) => logReadPrimitiveCall('trace', args, async () => ({
269
+ content: [
270
+ {
271
+ type: 'text',
272
+ text: JSON.stringify(runTraceWalk('backward', args), null, 2),
273
+ },
274
+ ],
275
+ }), extractRecordHashFieldsFromMcpResult));
276
+ // Sibling tool: forward walk (records that cited THIS one). Mirrors
277
+ // the trace input schema + response shape exactly so callers can use
278
+ // it interchangeably; only the direction of the walk differs.
279
+ mcp.registerTool('trace_forward', {
280
+ title: 'trace informed_by chain forward',
281
+ description: 'Walk forward from a record_hash, surfacing the records that cited ' +
282
+ 'it via their informed_by chain. The dual of `trace` (backward). ' +
283
+ 'Answers "I made decision X, what did I do because of it?" Reads from ' +
284
+ 'the local signed-record mirror (~/.atrib/records/*.jsonl). Returns ' +
285
+ 'the records visited, parent links (which upstream record at the ' +
286
+ 'prior level cited this one), and dangling hashes (rare; only if the ' +
287
+ 'reverse index references a record missing from the mirror). Legacy ' +
288
+ "alias: new callers should prefer `recall` with shape='walk', direction='forward'.",
289
+ inputSchema: TraceInput.shape,
290
+ }, async (args) => logReadPrimitiveCall('trace_forward', args, async () => ({
291
+ content: [
292
+ {
293
+ type: 'text',
294
+ text: JSON.stringify(runTraceWalk('forward', args), null, 2),
295
+ },
296
+ ],
297
+ }), extractRecordHashFieldsFromMcpResult));
298
+ }
299
+ /**
300
+ * Wire up the legacy atrib-trace MCP server. Mounts `trace` and
301
+ * `trace_forward` plus the `recall` verb (alias-window rule W1). The
302
+ * @atrib/trace package re-exports this factory.
303
+ */
304
+ export async function createAtribTraceServer() {
305
+ const mcp = new McpServer({
306
+ name: 'atrib-trace',
307
+ version: '0.1.0',
308
+ });
309
+ registerTraceTools(mcp);
310
+ const { registerRecallVerbTool } = await import('./recall-verb.js');
311
+ registerRecallVerbTool(mcp);
312
+ return { mcp };
313
+ }
314
+ //# sourceMappingURL=trace-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-tools.js","sourceRoot":"","sources":["../src/trace-tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Bounded directional walk through informed_by chains.
3
+ *
4
+ * Two directions:
5
+ * - backward (traceBackward): from a record, walk its informed_by chain
6
+ * toward causal ancestors. Answers "what informed this?"
7
+ * - forward (traceForward): from a record, walk the records that cite it
8
+ * via their informed_by. Answers "what was informed by this?" — i.e.
9
+ * what followed from this decision/observation.
10
+ *
11
+ * Each direction breadth-first walks edges until either (a) depth bound
12
+ * reached, (b) no more unresolved hashes remain, or (c) safety cap on
13
+ * total visited nodes hit.
14
+ *
15
+ * Each visited record is annotated with:
16
+ * - depth , hop count from the starting record (0 = the start)
17
+ * - parent_hashes, which records in the previous level referenced this one
18
+ *
19
+ * Records referenced via informed_by but NOT present in the local mirror
20
+ * are surfaced as `dangling` entries: the verifier hasn't seen the upstream
21
+ * locally. This is informational, not a failure, the upstream may live on
22
+ * the public log but not in the agent's local mirror.
23
+ *
24
+ * Forward walk requires a full-mirror reverse index (built on each call
25
+ * from the same IndexedRecord map; O(N) once, then O(out-degree) per hop).
26
+ * For typical mirror sizes (~14K records) this is negligible.
27
+ */
28
+ import type { IndexedRecord } from './trace-storage.js';
29
+ import type { AtribRecord } from '@atrib/mcp';
30
+ export type TraceDirection = 'backward' | 'forward';
31
+ export interface TraceVisited {
32
+ depth: number;
33
+ record_hash: string;
34
+ parent_hashes: string[];
35
+ /** Empty when source mirror lacks this record (dangling). */
36
+ record: AtribRecord | null;
37
+ /** Source file the record was read from, or null if dangling. */
38
+ source: string | null;
39
+ /** Sub-set of informed_by entries that are themselves walkable next-hop. */
40
+ next_informed_by: string[];
41
+ }
42
+ export interface TraceResult {
43
+ start_hash: string;
44
+ direction: TraceDirection;
45
+ depth_requested: number;
46
+ depth_reached: number;
47
+ visited: TraceVisited[];
48
+ dangling: string[];
49
+ truncated_by_depth: boolean;
50
+ truncated_by_cap: boolean;
51
+ warnings: string[];
52
+ }
53
+ export interface TraceOptions {
54
+ /** Hard cap on total visited nodes regardless of depth. Defaults to 200. */
55
+ maxNodes?: number;
56
+ /**
57
+ * When set, scope the walk to records that share this context_id. Edges
58
+ * crossing into a different context_id are treated as dangling: the
59
+ * upstream is real but lies outside the requested scope. Used by
60
+ * Inspect-style harnesses passing ATRIB_CONTEXT_ID to keep each arm's
61
+ * trace inside its own context.
62
+ */
63
+ contextId?: string;
64
+ }
65
+ /**
66
+ * Walk informed_by edges backward from the starting record.
67
+ *
68
+ * Behaviors:
69
+ * - Cycle-safe: every record is visited at most once, even if multiple
70
+ * parents reference it.
71
+ * - Cap-safe: if maxNodes is hit mid-walk, returns partial result with
72
+ * truncated_by_cap=true. Callers can re-run with a smaller depth or
73
+ * start from a sub-tree if needed.
74
+ * - Dangling-aware: informed_by entries pointing at records not in the
75
+ * local mirror surface as `dangling`. Those entries do NOT advance the
76
+ * walk (we have nothing to walk from).
77
+ */
78
+ export declare function traceBackward(startHash: string, depth: number, index: Map<string, IndexedRecord>, options?: TraceOptions): TraceResult;
79
+ /**
80
+ * Build a reverse informed_by index: for each record_hash R, the list of
81
+ * record_hashes that cite R in their informed_by field. O(total
82
+ * informed_by entries) build; O(1) lookup per parent during walk.
83
+ *
84
+ * Exported so callers (or tests) can pre-build the index when running
85
+ * multiple traceForward calls against the same mirror.
86
+ */
87
+ export declare function buildReverseInformedByIndex(index: Map<string, IndexedRecord>): Map<string, string[]>;
88
+ /**
89
+ * Walk informed_by edges FORWARD from the starting record.
90
+ *
91
+ * Answers: "what records cited THIS record via informed_by?" — i.e. what
92
+ * decisions/observations followed from this one in the causal substrate.
93
+ * The dual of traceBackward. Useful for "I made decision X, what did I do
94
+ * because of it?" lookups.
95
+ *
96
+ * Same behaviors as traceBackward:
97
+ * - Cycle-safe via visited-set
98
+ * - Cap-safe via maxNodes
99
+ * - context_id-scoped (if specified): edges into a different context_id
100
+ * are treated as dangling
101
+ * - Dangling: walking forward to a record present in the reverse index
102
+ * but missing from the mirror surfaces it as dangling (defensive; in
103
+ * practice the same mirror that built the reverse index has the
104
+ * record itself)
105
+ */
106
+ export declare function traceForward(startHash: string, depth: number, index: Map<string, IndexedRecord>, options?: TraceOptions): TraceResult;