UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

166 lines • 6.91 kB
"use strict"; /** * sliceToBacktrackTrace — serialize a footprintjs VARIABLE SLICE * (`sliceToJSON(sliceForKey(...))` from `foottrace`) into the * BacktrackTrace shape agentThinkingUI's <BacktrackView>/<BacktrackOverlay> * renders. * * The sibling of {@link toBacktrackTrace}: that one maps a LOCALIZER REPORT * (scored suspects, ablation verdicts); this one maps a STRUCTURAL SLICE — * the exact dependency chain behind one variable, with no influence claims * at all. Same board, honestly weaker chips: * * - `mode` is ALWAYS 'correlational' — a slice is exact STRUCTURE, but it * never tested influence; the default modeLabel says exactly that. * - every card is `upperBound: true` (the UI's hatched meter + star): the * score is 1/(1+depth) — hop PROXIMITY, a deterministic layout aid, not * evidence. The honesty lines state the formula so nobody reads it as * influence. * - slice honesty rides along verbatim: reads-coverage warning * (readTracking off), truncation, per-node incomplete-sources counts, * and honest ABSENCE — a missing slice renders an empty board whose * honesty lines say why ('never written' → initial state / frozen args / * closure), never a fabricated suspect. * * Pure mapping, no UI dependency, framework-agnostic JSON on both sides — * the human and the LLM triage the SAME artifact (the parity loop: an agent * emits this, a person confirms or overrides on the board). */ Object.defineProperty(exports, "__esModule", { value: true }); exports.sliceToBacktrackTrace = void 0; const foottrace_1 = require("foottrace"); /** The structural-honesty lines every slice board carries. */ const SLICE_CLAIMS_LINES = [ 'scores are hop proximity (1/(1+depth)) — a layout aid, not influence; every card is a path-only upper bound.', 'this board shows exact dependency STRUCTURE; only ablation verdicts make causal claims.', ]; /** * Shortest hop chain root → node over the slice's id-referenced edges * (BFS; edges point child→parent, i.e. away from the root). */ function pathTo(json, targetId) { if (!json.edges || !json.writerId || targetId === json.writerId) return undefined; const prev = new Map(); const queue = [json.writerId]; const seen = new Set(queue); while (queue.length > 0) { const cur = queue.shift(); if (cur === targetId) break; for (const e of json.edges) { if (e.from !== cur || seen.has(e.to)) continue; seen.add(e.to); prev.set(e.to, { from: e.from, key: e.key, kind: e.kind }); queue.push(e.to); } } if (!prev.has(targetId)) return undefined; const hops = []; let cur = targetId; while (cur !== json.writerId) { const p = prev.get(cur); hops.unshift({ key: p.key ?? '', kind: p.kind, via: `${p.from} ← ${cur}` }); cur = p.from; } return hops.length > 1 ? hops : undefined; } /** * Serialize a variable slice for agentThinkingUI's BacktrackView. See the * module doc — structural honesty is the whole design. */ function sliceToBacktrackTrace(json, opts) { const max = opts.maxSuspects ?? 6; const claim = opts.claim ?? `Why is '${json.key}' what it is?`; // Every sentence is footprintjs's own (`HONESTY_CODES`) — this module keeps no copy. const honesty = []; const say = (code, subject) => { honesty.push(`⚠ ${subject !== undefined ? `${subject}: ` : ''}${foottrace_1.HONESTY_CODES[code]}`); }; if (json.missing !== undefined) say(json.missing, `'${json.key}'`); for (const note of json.notes ?? []) say(note.code, `'${json.key}'`); if (json.readsCoverage && json.readsCoverage.steps > 1 && json.readsCoverage.stepsWithReads === 0) { say('reads-not-recorded'); } if (json.truncated) { say('truncated', `slice truncated (${[ json.truncated.byDepth && 'depth', json.truncated.byNodes && 'node budget', ] .filter(Boolean) .join(' + ')})`); } // Honest absence: an empty board whose honesty says why. if (!json.writerId || !json.nodes) { return { claim, mode: 'correlational', modeLabel: opts.modeLabel ?? 'no slice — see honesty', agent: opts.agent, model: opts.model, answer: opts.answer, decidedAt: { id: json.key, label: `'${json.key}' (no recorded writer)`, kind: opts.decidedAtKind ?? 'rule', }, suspects: [], honesty: [...honesty, ...SLICE_CLAIMS_LINES], }; } const writer = json.nodes[json.writerId]; let incompleteCount = 0; // Non-root nodes in their BFS insertion order (sliceToJSON preserves it). const entries = Object.entries(json.nodes).filter(([id]) => id !== json.writerId); const cards = entries.map(([id, node], i) => { if (node.incompleteSources && node.incompleteSources.length > 0) incompleteCount++; const inboundEdge = json.edges?.find((e) => e.to === id); return { kind: 'stage', name: node.stageName, score: Number((1 / (1 + node.depth)).toFixed(2)), rank: i + 1, upperBound: true, edge: inboundEdge ? { key: inboundEdge.key, weight: inboundEdge.weight, kind: inboundEdge.kind } : undefined, path: pathTo(json, id), bornAt: { id, label: node.stageName, via: 'stage commit' }, }; }); if (incompleteCount > 0) { honesty.push(`⚠ ${incompleteCount} step${incompleteCount === 1 ? '' : 's'} in the chain also consumed untracked inputs (args/env/silent reads) — the slice may be incomplete there.`); } const selected = cards.slice(0, max); const dropped = cards.slice(max); const folded = dropped.length > 0 ? `${dropped.length} more step${dropped.length === 1 ? '' : 's'} folded — ` + dropped.map((c) => `#${c.rank} ${c.bornAt.id}`).join(' · ') + ' — every id drillable with the trace toolpack' : undefined; return { claim, mode: 'correlational', modeLabel: opts.modeLabel ?? 'exact dependency chain — structural, not ablation-tested', agent: opts.agent, model: opts.model, answer: opts.answer, decidedAt: { id: json.writerId, label: writer?.stageName, kind: opts.decidedAtKind ?? 'rule', }, suspects: selected, folded, honesty: [...honesty, ...SLICE_CLAIMS_LINES], }; } exports.sliceToBacktrackTrace = sliceToBacktrackTrace; //# sourceMappingURL=sliceToBacktrackTrace.js.map