agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
101 lines (100 loc) • 5.36 kB
TypeScript
/**
* ledger — what a clean result does NOT rule out.
*
* Pattern: a wrapper the framework recognizes, minted by a helper (the
* `absent()` shape one level up: same vocabulary, different claim).
* Role: core/ layer, pure. `coverage()` is what a tool author writes;
* `readCoverageLedger` is what the dispatch loop calls.
* Emits: N/A (the caller emits `agentfootprint.tools.coverage_declared`).
*
* ## The problem, stated as narrowly as it deserves
*
* An agent answers confidently from partial coverage. "Everything looks fine"
* is produced from four checks that passed, and arrives with no way to tell
* whether *fine* means **verified** or **unexamined** — whether the fifth
* thing was healthy or was never looked at. The evidence gate (9.35.0) is the
* sibling of this: the gate catches invented VALUES, this catches unstated
* LIMITS. Both are ways an answer can be false while every token in it is
* real.
*
* A ledger is not a caveat and not a disclaimer. It is three lists the TOOL
* knows and the model does not: what it checked, what it did not check, and
* what it can never cover. Only the tool knows the third one, which is why
* this cannot be prompt engineering.
*
* ## Why the wrapper, and not a field on the result
*
* The alternative — teaching the framework to read a `coverage` key off any
* returned object — would make every domain object that happens to have one
* change behavior. The wrapper is opt-in by construction: `coverage(x, …)`
* produces a shape that did not exist before, so nothing that ever ran can
* become a ledger by accident. Same argument as the effects envelope's strict
* recognizer, same guarantee.
*/
import type { Coverage, CoverageLedgerDeclaration, CoveredResult, DeclaredCoveredResult, DeclaredLedgerDeclaration } from './types.js';
import { COVERAGE_MARKER, readCoverageLedger } from './recognize.js';
export { COVERAGE_MARKER, readCoverageLedger };
/**
* The static sentence every ledger carries. The last clause is the OFFER half
* of survival — the model is told to carry the limits into its answer. The
* ENFORCEMENT half (`.limitsTravelWithTheAnswer()`) does not depend on the
* model obeying it; see `answer.ts` for why both exist.
*
* "the call this result answers", not "this call" (9.86.1): the note rides a
* tool result, which is re-read on every later call of the turn, and a bare
* `this call` there denotes whichever call is reading it. The container
* deictic's own anchor names the call instead.
*/
export declare const COVERAGE_NOTE: string;
/**
* Return a verdict with its own boundary attached.
*
* The model reads `{ af_coverage: {…}, result: <your value> }` — boundary
* first, deliberately: a limit placed after a long result is a limit that gets
* skimmed past. The framework records the ledger and, with
* `.limitsTravelWithTheAnswer()` configured, appends it to the run's final
* answer where the model cannot drop it.
*
* `period` (honesty step 7b) says what time the read behind the value
* covered — `{ queried, held, readAt? }`, ISO 8601 instants with a zone; it is
* a boundary on its own, served inside `af_coverage` before `result`, and the
* results layer judges it. `coverage()` takes no `provenance`.
*
* `inProgress` says what the read found still RUNNING — its outcome not known
* yet (a backup in progress, a replication session still synchronizing). The
* tool decides what is in flight; this library never reads a vendor's state
* name. Served as `in_progress` (the dispatch door adds one static clause after
* the note), recorded, and printed under `.limitsTravelWithTheAnswer()`; it
* never changes the answer's standing (`inProgress.ts`). It needs `checked`
* beside it — an item in progress is ground the call read.
*
* Refuses (throws, where it is called) a boundary that declares nothing, a
* malformed item or period, and any key the boundary does not have — naming the
* spelling meant when the key is a casing slip (`not_checked` →
* `notChecked`), so a list declared from plain JavaScript or JSON cannot
* vanish without a word. Every refusal starts `refused: `: inside `execute`
* it becomes the call's error result, which the model reads.
*
* @example the highest-stakes tool in a triage agent
* defineTool({
* name: 'replication_health',
* description: 'Replication health across the estate',
* inputSchema: { type: 'object', properties: {} },
* execute: async () => {
* const { verdict, ndmTimedOut } = await checkReplication();
* return coverage(verdict, {
* checked: ['SRDF pair state on all 4 arrays (live query)'],
* notChecked: ndmTimedOut
* ? [{ what: 'NDM migration sessions', why: 'the API timed out — ask again' }]
* : [],
* cannotCover: [
* { what: 'host-side multipathing', why: 'no collector runs on the ESX hosts' },
* ],
* });
* },
* });
*/
export declare function coverage<T>(content: T, decl: DeclaredLedgerDeclaration): DeclaredCoveredResult<T>;
export declare function coverage<T>(content: T, decl: CoverageLedgerDeclaration): CoveredResult<T>;
/** The ledger's coverage, in the normalized three-list shape. */
export declare function coverageOfLedger(covered: CoveredResult): Coverage;