agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
122 lines • 6.2 kB
JavaScript
;
/**
* Earlier answers' tool results that were IN FRONT OF THE MODEL when it
* answered — proven by the record's witness, never by end-of-run state.
*
* The witness rule, in typed terms:
* N = the `iteration` of the LAST `stream.llm_start` before `turn_end` (the
* answering iteration). Context assembly for an iteration PRECEDES its
* `iteration_start`, and `context.injected` carries no iteration of its
* own, so the rule keys on the compose STAGE:
* the witnesses are the `context.injected` rows with `slot: 'messages'`,
* `source: 'tool-result'`, `sourceId: <toolCallId>` whose
* `meta.runtimeStageId` equals that of the `context.slot_composed` with
* `slot: 'messages'` and `iteration: N`.
* The content is parsed from the matching `history` message (by `toolCallId`).
*
* Only EARLIER answers' results: `distance` (messages a PERSON wrote after the
* result, up to and including the current one — `common.ts` · `isPersonEntry`,
* never a frame the library wrote in the user role) must be at least 1, and a call of this run
* is never "in view" — on the flagship recording the answering compose also
* witnesses this run's own `get_array_inventory` result (`events[68]`), which
* must never become an in-view line.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.readInView = exports.witnessesOf = exports.answeringIteration = exports.MAX_IN_VIEW = 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 common_js_1 = require("./common.js");
/** At most this many in-view results are listed, newest first. */
exports.MAX_IN_VIEW = 3;
/** N — the answering iteration. `undefined` when no `llm_start` is recorded. */
function answeringIteration(view) {
const end = view.last('agent.turn_end');
const starts = view
.ofType('stream.llm_start')
.filter((e) => end === undefined || e.index < end.index);
return (0, view_js_1.num)(starts[starts.length - 1]?.payload.iteration);
}
exports.answeringIteration = answeringIteration;
/** The witness rows of the answering iteration's messages compose. */
function witnessesOf(view, iteration) {
if (iteration === undefined)
return [];
const composed = view
.ofType('context.slot_composed')
.filter((e) => e.payload.slot === 'messages' && e.payload.iteration === iteration);
const stage = (0, view_js_1.str)(composed[composed.length - 1]?.meta.runtimeStageId);
if (stage === undefined)
return [];
return view.ofType('context.injected').filter((e) => e.meta.runtimeStageId === stage &&
e.payload.slot === 'messages' &&
e.payload.source === 'tool-result' &&
// A witness whose call id the record keeps out is still a witness: a
// result was in view, whose call the record will not name.
((0, view_js_1.keptOut)(e, 'sourceId') || typeof e.payload.sourceId === 'string'));
}
exports.witnessesOf = witnessesOf;
function readInView(ctx, callIds) {
const history = (0, common_js_1.historyOf)(ctx.view);
const historyKeptOut = ctx.view.isStateKeptOut('history');
const windowed = typeof ctx.view.first('agent.run_configured')?.payload.window === 'string';
const reads = [];
let keptOut = 0;
for (const witness of witnessesOf(ctx.view, ctx.answeringIteration)) {
// A call id the record keeps out is not one of this run's calls (those are
// ids the run recorded, and kept): an earlier result whose call is unnamed.
if ((0, view_js_1.keptOut)(witness, 'sourceId')) {
keptOut += 1;
continue;
}
const id = witness.payload.sourceId;
if (callIds.has(id))
continue;
if (historyKeptOut) {
keptOut += 1;
continue;
}
const historyIndex = history.findIndex((m) => (0, view_js_1.isRecord)(m) && m.role === 'tool' && m.toolCallId === id);
if (historyIndex < 0)
continue;
const message = history[historyIndex];
const toolName = (0, view_js_1.str)(message.toolName);
if (toolName === undefined)
continue;
const distance = history.slice(historyIndex + 1).filter((m) => (0, common_js_1.isPersonEntry)(m)).length;
if (distance < 1)
continue;
// An EARLIER answer's result: its run's door is not in this record, so the one reader's strict
// recognizers read the history text itself — the only evidence of the door that is left. The
// TOOL's own bytes (`lib/toolBytes.ts` · `toolBytesOf`): a note the inputs layer appended after
// a filled call's result is the library's, and read whole it breaks the parse.
const rowsAt = (0, common_js_1.rowsAtOf)(ctx.declarations, toolName);
const reading = (0, common_js_1.readEmptiness)((0, toolBytes_js_1.toolBytesOf)({ content: message.content, toolChars: message.toolChars }), rowsAt !== undefined ? { rowsAt } : {});
reads.push({
fact: {
toolName: toolName.slice(0, common_js_1.FACT_TEXT_CHARS),
toolCallId: id.slice(0, common_js_1.FACT_TEXT_CHARS),
distance,
windowed,
emptiness: reading.emptiness,
...(0, common_js_1.countedFields)(reading),
pointers: [(0, common_js_1.at)(witness, 'sourceId'), (0, common_js_1.historyAt)(historyIndex, '/toolName', id)],
},
witness,
historyIndex,
reading,
toolName,
tool: (0, render_js_1.v)(toolName, 'library', (0, common_js_1.historyAt)(historyIndex, '/toolName', id), common_js_1.FACT_TEXT_CHARS),
});
}
// Newest first: the result nearest the current question.
reads.sort((a, b) => b.historyIndex - a.historyIndex);
return {
all: reads,
listed: reads.slice(0, exports.MAX_IN_VIEW),
more: Math.max(0, reads.length - exports.MAX_IN_VIEW),
keptOut,
};
}
exports.readInView = readInView;
//# sourceMappingURL=inView.js.map