agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
133 lines • 5.98 kB
JavaScript
;
/**
* assessment/witness — the answer layer's committed witness rows: two verdicts
* the Route decider computes that were EVENTS only until the answer layer
* (honesty layer 4) needed them on the record.
*
* Pattern: Map. Three row types, their builders and their well-formedness
* check — the one owner of the `grounded`, `steps-unfinished` and
* `script-shown` row kinds.
* `findings/types.ts` · `FindingsRow` imports the types; the rows
* are filed through the one writer (`findings/ledger.ts` ·
* `recordFindings`) by the Route decider, and ONLY while the answer
* layer is armed (`stages/route.ts` · `judgeEvidence`,
* `judgeUnfinishedSteps`).
* Role: core/ layer leaf of the answer layer. Static on purpose: the
* checkpoint door (`core/runCheckpoint.ts` · `validateCheckpoint`,
* a synchronous door) asks `witnessRowIsWellFormed`, so this module
* imports nothing but the script labels (`../scripts/label.ts`, a
* leaf) and stays small.
* Emits: N/A. A witness row emits nothing of its own — the verdict's event
* (`agentfootprint.agent.evidence_checked`,
* `agentfootprint.skill.steps_unfinished`) already fired, beside the
* row, from the same stage.
*
* ## Why a row, when the event already says it
*
* The answer's standing is folded from COMMITTED rows only
* (`assessment/assess.ts` · `assessAnswer`), never from events, so the running
* agent and every later reader fold the same bytes. A verdict that exists only
* as an event cannot be folded: the gate's clean pass could not say "the names
* and numbers check ran", and an answer given before its declared steps
* finished could not say so at all. Each row is the verdict, not a copy of the
* event: names, enums and counts — never a value from the answer, a quote, or
* a step's note.
*
* ## Always stamped with the turn
*
* The ledger crosses turns on a continued conversation and `iteration`
* restarts at 1 every run, so a witness row always carries `turn`
* (`AgentState.turnNumber`): the fold reads this turn's witness rows only.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.witnessRowIsWellFormed = exports.stepsUnfinishedRowFrom = exports.groundedRowFrom = exports.scriptShownRowsFrom = void 0;
const match_js_1 = require("../scripts/match.js");
const POSTURES = Object.freeze(['assist', 'guard', 'rails']);
/** The script match's verdicts as rows — one per block read, ids and labels only. */
function scriptShownRowsFrom(blocks, at) {
return blocks.map((b) => b.label === 'unmatched'
? {
kind: 'script-shown',
turn: at.turn,
iteration: at.iteration,
block: b.block,
label: 'unmatched',
reason: b.reason,
}
: {
kind: 'script-shown',
turn: at.turn,
iteration: at.iteration,
block: b.block,
label: b.label,
toolCallId: b.toolCallId,
tool: b.tool,
dispatch: b.dispatch,
...(b.exitCode !== undefined && { exitCode: b.exitCode }),
});
}
exports.scriptShownRowsFrom = scriptShownRowsFrom;
/** The gate's clean verdict as a row — built from the event's own counts. */
function groundedRowFrom(verdict) {
return {
kind: 'grounded',
turn: verdict.turn,
iteration: verdict.iteration,
posture: verdict.posture,
candidates: verdict.candidates,
lookedUp: verdict.lookedUp,
...(verdict.afterRevision && { afterRevision: true }),
};
}
exports.groundedRowFrom = groundedRowFrom;
/** The step judge's accepted or cut-short verdict as a row — each step's position and tool only. */
function stepsUnfinishedRowFrom(verdict) {
return {
kind: 'steps-unfinished',
turn: verdict.turn,
iteration: verdict.iteration,
skillId: verdict.skillId,
remaining: verdict.remaining.map((s) => ({ index: s.index, tool: s.tool })),
total: verdict.total,
action: verdict.action,
};
}
exports.stepsUnfinishedRowFrom = stepsUnfinishedRowFrom;
const isCount = (value) => typeof value === 'number' && Number.isInteger(value) && value >= 0;
/**
* Whether a value read from a stored checkpoint is a witness row this library
* could have written: the kind, a numeric `turn` and `iteration`, and every
* other field in its own shape. The checkpoint door's two arms
* (`core/runCheckpoint.ts`). An older runtime refuses a checkpoint that
* carries either kind.
*/
function witnessRowIsWellFormed(r) {
if (typeof r.turn !== 'number' || typeof r.iteration !== 'number')
return false;
if (r.kind === 'grounded') {
return (typeof r.posture === 'string' &&
POSTURES.includes(r.posture) &&
isCount(r.candidates) &&
isCount(r.lookedUp) &&
(r.afterRevision === undefined || r.afterRevision === true));
}
if (r.kind === 'script-shown') {
// The verdict's fields, by the ONE rule every reader of the shape uses
// (`scripts/match.ts` · `blockVerdictOf`) — an exit code its label does
// not carry included. A row never carries `alsoMatches`.
return r.alsoMatches === undefined && (0, match_js_1.blockVerdictOf)(r) !== undefined;
}
if (r.kind === 'steps-unfinished') {
return (typeof r.skillId === 'string' &&
isCount(r.total) &&
(r.action === 'accepted' || r.action === 'cut-short') &&
Array.isArray(r.remaining) &&
r.remaining.every((s) => s !== null &&
typeof s === 'object' &&
isCount(s.index) &&
typeof s.tool === 'string'));
}
return false;
}
exports.witnessRowIsWellFormed = witnessRowIsWellFormed;
//# sourceMappingURL=witness.js.map