agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
92 lines (91 loc) • 4.92 kB
TypeScript
/**
* assessment/sources — what each reason rests on
* (`AnswerAssessment.reasons[].source`): specific calls, the answer itself, or
* the run as a whole.
*
* Pattern: a projection inside the one Fold (`assess.ts` · `assessAnswer`).
* The KIND is declared per reason (`reasons.ts` · `REASONS`); for a
* `calls` reason this module reads the calls off the very rows the
* reason's witnesses point at — a history result carries its call id,
* a committed row names its call — and the call's tool name off the
* same row, or off the run's `history` for a row that names only an id.
* It reads the committed record only, and adds nothing to it.
* Role: core/ layer, pure. Asked once per fired reason by the fold; every
* reader takes the source from the fold — the answer layer's line
* (`compose.ts` · `standingLineOf`) and the answer account's "How
* sure" counts (`answer-account/facts/howSure.ts`) — and never
* re-derives it.
* Emits: N/A.
*
* ## Names and ids only
*
* A source holds call ids and registered tool names, never a value, an
* argument or a result: the rows it reads hold more, and it copies two strings
* out of each.
*
* ## Which calls a row names
*
* - a tool result in `history` — its own call;
* - a coverage, argument, period or middleware row — its own call (the row's
* `toolCallId`, with the row's `toolName`);
* - a conflict row — the calls of the readings that disagree (`witnesses`);
* - a contingent row — the results the value came from (`carriers`), and an
* argument row behind `value-contingent` — the result it names (`result`):
* the reason is that a value came from results the model itself set aside;
* - an answer check that names its tool and not its call
* (`declaration-overclaimed`, the report row's `tools` — kept by the one
* projection onto the report, `negativeClaims/delivery.ts` ·
* `reportWithNegatives`) — every call of that tool this turn: it may
* over-report, it never hides (the fold's rule for "rests on").
*
* A row that names no call, a call whose tool's name the record does not
* hold, and every entry of a list (a conflict's readings, a contingent value's
* carriers, an answer check's tools) that names no call — beside the entries
* that do — is COUNTED in `unnamed`, never dropped and never guessed. Only a
* known call id is said once.
*
* ## A list is read by index, never through its own code
*
* Every list on a row — a conflict's readings, a contingent value's carriers,
* an answer check's tools, a request's `toolCalls` — is read through ONE
* reader, `rowList` (`lib/boundedJson.ts` · `readOwnList`, the bounded-copy
* convention as a reader): a plain array only, its own `length`, the own data
* property at each index, at most `MAX_ROW_LIST_ENTRIES`. A record handed to
* `assessAnswer` may be host-built, so a list can be sparse, carry its own
* iterator or put a getter at an index: the iterator and the getter are never
* called. A hole or a getter's entry names no call; the entries past the bound
* are counted, never read; a value that is not a plain array is not trusted as
* a list — one call the record cannot name. Each is counted in `unnamed`.
*/
import { type OwnList } from '../../../lib/boundedJson.js';
import type { AssessmentPointer, AssessmentReason, AssessmentReasonSource } from './types.js';
/** The most entries the fold reads off one list on a row; the rest are counted, never read. */
export declare const MAX_ROW_LIST_ENTRIES = 1000;
/**
* A list on a row, as the fold reads it — THE one reader, shared by the
* projection here and the fold's placement (`assess.ts` · `conflictOfThisTurn`,
* `callsNamedBy`): `readOwnList` at `MAX_ROW_LIST_ENTRIES`. `undefined` when
* the value is not a plain array.
*/
export declare function rowList(value: unknown): OwnList | undefined;
/** What the projection reads: the committed state, and this turn's results (for a tool-name join). */
export interface SourceRead {
readonly state: Readonly<Record<string, unknown>>;
readonly turnResults: readonly {
readonly toolCallId?: string;
readonly toolName?: string;
}[];
}
/**
* What one fired reason rests on — its declared `kind`, and for `calls` the
* calls its witnesses' rows name, each once, in witness order.
*
* @example
* ```ts
* reasonSourceOf('declared-absent', 'calls', [historyPointer], read);
* // { kind: 'calls', calls: [{ toolCallId: 'c1', toolName: 'load_attachment' }] }
* reasonSourceOf('stopped-early', 'run', [statePointer('stoppedEarly')], read);
* // { kind: 'run' }
* ```
*/
export declare function reasonSourceOf(reason: AssessmentReason, kind: AssessmentReasonSource['kind'], witness: readonly AssessmentPointer[], read: SourceRead): AssessmentReasonSource;