agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
120 lines • 5.89 kB
JavaScript
;
/**
* What THIS record carries of the part of an answer before a pause — read off
* the record, never assumed absent.
*
* A resumed leg's recording holds that leg's EVENTS only (`view.ts` keeps one
* run's). It also holds the state the run PAUSED with — `snapshot.initialState`
* (footprintjs: the resumed leg's fold base; a fresh run's is empty) — and the
* committed state after it, which carries the part before the pause forward:
* `sharedState.history` keeps each call answered before the pause with what
* came back, and the tracked `coverageDeclared` rows keep what each of those
* tools declared (`core/agent/types.ts` · `coverageDeclared` — a
* limit is a fact about the answer, so it rides every recording).
*
* The boundary is the paused state itself: a call before the pause is a tool
* message in `initialState.history` after its last person entry (the request
* the run was answering when it paused) that is not a call of this leg. Its
* bytes and declaration are read where a pointer can land — the committed
* `sharedState` (matched by call id) — and a call the committed history no
* longer holds is passed over, never guessed.
*
* So, exactly:
* - RECOVERABLE — each call answered before the pause; what came back, read
* by the ONE emptiness reader over the tool's own bytes with the door the
* state holds (its `coverageDeclared` rows: an `absence` row, a `ledger`
* boundary); what its tool declared it checked, did not check and can
* never cover.
* - RECOVERABLE TOO — the routing verdict: RouteTurn commits `turnRoute`
* and the scorer's `entryScores` before the loop, so the state the run
* paused with holds them (`understood.ts` · `routingVerdictOf`). Only
* what the state does not carry is missing: whether the verdict was
* decisive and the decider's model.
* - NOT RECOVERABLE — that part's events: what was in front of the model
* on each earlier iteration, a rule's verdict on a call (failed, refused,
* withheld), and timings. The rows that need them say so
* (`wrong.beforePause.held.events`; `understood.resumed` and
* `wrong.beforePause.held` only when the state holds no verdict either).
*
* A record that does not hold the paused state (`stateHeld: false` — an
* older or trimmed recording, or a leg the record only LOOKS resumed in) reads
* nothing of the part before the pause, and the rows say it is not in this
* record.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.readPausedLeg = void 0;
const toolBytes_js_1 = require("../../lib/toolBytes.js");
const render_js_1 = require("../render.js");
const view_js_1 = require("../view.js");
const calls_js_1 = require("./calls.js");
const common_js_1 = require("./common.js");
const NOT_RESUMED = { stateHeld: false, calls: [] };
/**
* On a resumed leg: the part before the pause, read from the committed state
* this record holds — the calls in history after the person's current request
* that are not calls of this leg, each with what came back and what its tool
* declared.
*/
function readPausedLeg(ctx, calls) {
const pausedWith = ctx.view.pausedWith;
if (!ctx.resumedLeg || pausedWith === undefined)
return NOT_RESUMED;
const before = pausedHistory(pausedWith.history);
const history = (0, common_js_1.historyOf)(ctx.view);
const coverageRows = ctx.view.state?.[calls_js_1.COVERAGE_STATE_KEY];
const rows = Array.isArray(coverageRows) ? coverageRows : [];
const out = [];
for (const id of before) {
if (calls.ids.has(id))
continue;
const i = history.findIndex((m) => (0, view_js_1.isRecord)(m) && m.role === 'tool' && m.toolCallId === id);
const m = history[i];
if (!(0, view_js_1.isRecord)(m))
continue;
const name = (0, view_js_1.str)(m.toolName);
if (name === undefined)
continue;
const coverage = (0, calls_js_1.readStateCoverage)(rows, id, ctx.view.servedUnderPolicy);
const rowsAt = (0, common_js_1.rowsAtOf)(ctx.declarations, name);
// The door this record holds for the call is its committed declaration — the same rows the
// standing fold reads (`assessment/assess.ts`), so the two cannot disagree about it.
const mine = rows.filter((r) => (0, view_js_1.isRecord)(r) && r.toolCallId === id);
const reading = (0, common_js_1.readEmptiness)((0, toolBytes_js_1.toolBytesOf)({ content: m.content, toolChars: m.toolChars }), {
...(rowsAt !== undefined && { rowsAt }),
...(mine.length > 0 && {
door: {
absent: mine.some((r) => r.kind === 'absence'),
bounded: mine.some((r) => r.kind === 'ledger'),
},
}),
});
out.push({
toolName: name,
toolCallId: id,
historyIndex: i,
tool: (0, render_js_1.v)(name, 'library', (0, common_js_1.historyAt)(i, '/toolName', id), common_js_1.FACT_TEXT_CHARS),
reading,
...(coverage !== undefined && { coverage }),
});
}
return { stateHeld: true, calls: out };
}
exports.readPausedLeg = readPausedLeg;
/** The call ids of the tool messages after the last person entry of the paused state's history. */
function pausedHistory(history) {
let current = -1;
history.forEach((m, i) => {
if ((0, common_js_1.isPersonEntry)(m))
current = i;
});
const ids = [];
history.forEach((m, i) => {
if (i <= current || !(0, view_js_1.isRecord)(m) || m.role !== 'tool')
return;
const id = (0, view_js_1.str)(m.toolCallId);
if (id !== undefined && !ids.includes(id))
ids.push(id);
});
return ids;
}
//# sourceMappingURL=pausedLeg.js.map