UNPKG

agentfootprint

Version:

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

110 lines (109 loc) • 5.97 kB
/** * assess — the answer's standing, folded from the committed record. * * Pattern: one pure Fold over the Trace. It reads COMMITTED state only — the * run's `sharedState` (the snapshot's, or a paused run's checkpoint's) * — and never an event, so the running agent and every later reader * fold the same bytes. No clock, network, model or randomness; the * same inputs give the same bytes; it never writes into the run. * Role: core/ layer, pure. The one owner of the answer's standing: the * `/observe` door publishes it (`assessAnswer`), `Agent.assessment` * reads the last run through it, and the answer account's "How sure" * row renders it (`answer-account/facts/howSure.ts` · * `readHowSure`). * Emits: N/A. * * ## The law * * The standing comes from what the record can check, never from how sure the * model sounds — and never "known" from silence. A reason that fires makes it * `unknown`; only a TIE check (a passed enforce `.answerValidation()` report for * these bytes) SUPPORTS `known`; checks that ran and fired nothing make it * `unrefuted` ("consistent with the record"); a record on which nothing could be * checked is `not-applicable` ("not assessed"). A membership pass — a value * found, a result that came back non-empty — keeps a reason from firing and * never supports anything. * * ## This turn only * * The turn begins after the last message a person said * (`lib/saidByPerson.ts` · `isSaidByPerson`, the one owner of that question): a * continued conversation carries earlier turns' results in `history`, and a * library frame (a correction, a nudge) is not a person's turn. The fold reads * the tool results after that message, every coverage row of the run * (`coverageDeclared` is never carried into a next turn), the conflict rows of * this turn — one that names one of those calls, or one stamped with this turn * whose readings the record does not all place in earlier turns * (`conflictOfThisTurn`; the ledger IS carried, so rows about earlier turns' * calls are left out), and the * answer's own rows (`unsupportedValues`, * `stoppedEarly`, `answerValidation`). "Rests on" is every call of the turn: it * may over-report, it never hides. * * ## How the turn ended * * A turn that ended in a PAUSE has no answer yet: a person's reply is what it * waits on. The fold reads that from the committed state every pause leaves — * `pausedToolCallId`, written by each pause the dispatch loop raises and * cleared by each resume — so a snapshot, a checkpoint and a saved recording * all say it, and every pause kind reads `ask`. A turn that ended in an ERROR * has no answer either, and no committed row says so in this version: the * readers settle that before they fold (`Agent.assessment` returns * `undefined`; the answer account says the record shows no answer). */ import type { AnswerAssessment, AssessmentDeclarations, AssessmentRecord } from './types.js'; /** * Fold one answer's standing from its committed record. * * The record of a turn that ended in an ANSWER or in a PAUSE (a pause reads * `ask`, from the committed state the pause leaves — no checkpoint needed). A * turn that ended in an error has no answer, and its committed state does not * say so in this version: do not fold it (`agent.assessment()` returns * `undefined` for one; the answer account says the record shows no answer). * * @param record a recording (`recordRun`), `{ snapshot }`, or a paused * run's `{ checkpoint }` — its `sharedState` is read when * no snapshot is given. * @param declarations what the APP declares — any `AnswerAccountDeclarations` * is accepted as it is: the fold reads only * `tools[name].rowsAt`, where an object result keeps its * rows. * @throws TypeError only on a caller error (a record that is not an object, a * malformed `rowsAt`); a record it cannot read yields * `not-applicable`, never a throw. * * A REDACTED record (an agent's `redact`): when the fold would read a state key * the record keeps out, it gives no verdict — `standing: 'not-assessed'`, with * the keys it needed in `keptOut`. A verdict over the placeholder would read a * kept-out pause as a pause and a kept-out history as an empty one. * * @example * ```ts * import { assessAnswer, recordRun } from 'agentfootprint/observe'; * * const recorder = recordRun(agent); * await agent.run({ message: 'Which ports on switch A are down?' }); * const a = assessAnswer(recorder.toRecording()); * a.standing; // 'not-sure' * a.reasons; // [{ reason: 'empty-undeclared', layer: 3, witness: [...] }] * ``` */ export declare function assessAnswer(record: AssessmentRecord, declarations?: AssessmentDeclarations): AnswerAssessment; /** * `assessAnswer` over a runner's LIVE snapshot (`Agent.assessment()`): the * state is the run's own heap, never served, so a placeholder string in it is * a value. The snapshot's `recorders` row says a policy covered the run — true * of its commit log, never of its live state — so it is not consulted here. * * @internal — `Agent · assessment`'s. */ export declare function assessLive(snapshot: unknown, declarations?: AssessmentDeclarations): AnswerAssessment; /** * The one fold, over a committed state already in hand. `served`: the state * comes from a record served under a redaction policy, so a key holding the * placeholder is a value the record keeps out (`keptOutGuard`). * * @internal — `assessAnswer`'s, and the answer account's, which holds the * state and the marker apart. */ export declare function foldState(recorded: Readonly<Record<string, unknown>>, declarations: AssessmentDeclarations | undefined, served: boolean): AnswerAssessment;