agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
272 lines (271 loc) • 13.6 kB
TypeScript
/**
* arguments/rows — the inputs layer's verdicts, as rows on the one ledger.
*
* Pattern: Map. One row type, its builders and its well-formedness check —
* the one owner of the `argument` row kind. `findings/types.ts` ·
* `FindingsRow` imports the type; this folder never imports
* `findings/` (the one-way law).
* Role: core/ layer leaf of the inputs layer (honesty layer 2).
* Emits: N/A (the Record stage emits one `agentfootprint.findings.argument`
* per row through the ledger's emit half).
*
* ## What a row may hold
*
* Names, enums and counts — and, in three fields only (`value`, `proposed`,
* `quote`), a value AS THE TOOL'S OWN ARGUMENT VIEW SHOWS IT
* (`core/toolShownArgs.ts` · `shownArgsOf`): a key the view hides reads
* `'REDACTED'`. The layer's checks run on the raw value in memory; the raw
* value is never stored on a row.
*
* ## One row per ruled argument per call, stamped with the turn
*
* `turn` is `AgentState.turnNumber` when the row was filed — the conversation
* turn, not the window position — because the ledger crosses turns on a
* continued conversation and `iteration` restarts at 1 every run. The
* standing fold reads this turn's rows only.
*/
/** Where a ruled argument's value came from, as the library's checks placed it. */
export type ArgumentSource = 'said' | 'answered' | 'result' | 'app' | 'default' | 'model';
/**
* Why the library asked: the call left the value out (`missing`); the value
* was present and its declared source did not trace it (`unverified` — under
* declared sources, on an `ask` rule); the person's answer did not fit the
* property's own schema and the field was asked again (`invalid-answer`).
*/
export type ArgumentAsked = 'missing' | 'unverified' | 'invalid-answer';
/** What the model claimed about a value's source (`_findings.from`; `'none'`: it declared nothing). */
export type ArgumentClaim = 'user' | 'result' | 'turn' | 'app' | 'assumed' | 'none';
/** Which declared-source check failed — the claim is filed as written, never repaired. */
export type ArgumentCheckFailed = 'quote-not-found' | 'composed-message' | 'unknown-result' | 'placed-result' | 'not-in-result' | 'no-earlier-turn' | 'not-in-earlier-turns' | 'only-in-model-answer' | 'not-in-app-text' | 'uncheckable';
/**
* The inputs layer's verdict on ONE ruled argument of ONE tool call — a row on
* `AgentState.findingsLedger` (`kind: 'argument'`).
*
* Filed: `source: 'default'` (the library filled the declared default, or the
* model sent that same value itself — `proposed` says which), `source:
* 'model'` (the model sent another value, and the record does not trace it),
* `asked` with no source (the call left an `ask` argument out and the person
* was asked — `missing` — or asked again after an answer that did not fit —
* `invalid-answer`), and `source: 'answered'` (the person's answer filled the
* value; `free` when it came through a free-text field).
*
* Under declared sources (`.inputsLayer({ argumentSources: true })` or
* `.findings({ argumentSources: true })`) a present
* value's row also carries the model's claim and the library's check of it
* (`sourcedRowOf`): `claimed` (`'none'` when it declared nothing), `source`
* `said` / `result` / `app` / `answered` when the check traced it (`matched`,
* `reading`, `earlier`, `result`, `setAside`, `argumentsFrom`, `appSource`),
* `failed` when it did not, `coincides` for the library's own lookup (a hint,
* never a source), and `asked: 'unverified'` with the model's value as
* `proposed` when an `ask` rule asks about it. A FREE argument a `from` entry
* named is filed with no `rule`. A reader skips a member it does not know.
*/
export interface ArgumentRow {
readonly kind: 'argument';
/** `AgentState.turnNumber` when the row was filed — the conversation turn. */
readonly turn: number;
readonly toolCallId: string;
readonly toolName: string;
readonly iteration: number;
/** The top-level argument name — schema vocabulary, like `toolName`. */
readonly argument: string;
/** The rule on the argument; absent on a free argument a `from` entry named. */
readonly rule?: 'ask' | 'assume';
/** Set on the argument `Tool.period` names. */
readonly period?: true;
/** Where the value came from; absent only on an asked row. */
readonly source?: ArgumentSource;
readonly asked?: ArgumentAsked;
/**
* The value the call runs with, in the tool's own argument view, clipped
* (`integrity/argumentLeaves.ts` · `clipValue`); `'REDACTED'` when the view
* hides the argument.
*/
readonly value?: string;
/**
* The MODEL's own value, same view: on a `default` row, the default the model
* sent itself (absent = the library filled it); on an `answered` row, the
* value the person's answer replaced.
*/
readonly proposed?: string;
readonly claimed?: ArgumentClaim;
/**
* How the value was traced to its source: the quote holds it, a declared
* phrase names it, an earlier answer in another spelling — or, under
* `.time()`, it IS the window of a mention the reader recorded (`mention`:
* the library filled it from that window, or the model's quote named that
* mention and its value equals the window).
*/
readonly matched?: ArgumentMatched;
/**
* The model's `quote`, clipped (`QUOTE_CHARS`) — `'REDACTED'` on an agent
* where ANY tool in reach can hide arguments (it registers a tool that
* carries an argument view, or wires a ToolProvider — whatever it lists): a
* quote is free text, and may hold any value a tool hides, in any spelling.
*/
readonly quote?: string;
readonly reading?: true;
readonly earlier?: true;
readonly result?: string;
readonly setAside?: 'open' | 'noise' | 'ruled-out';
readonly argumentsFrom?: 'listed' | 'unlisted';
readonly appSource?: string;
readonly free?: true;
readonly coincides?: 'person' | 'result' | 'app';
readonly malformed?: number;
readonly failed?: ArgumentCheckFailed;
}
/** How a traced value was matched — see {@link ArgumentRow.matched}. */
export type ArgumentMatched = 'quote' | 'phrase' | 'spelling' | 'mention';
/** The closed vocabularies, for the checkpoint door and the event. */
export declare const ARGUMENT_MATCHED: readonly ArgumentMatched[];
export declare const ARGUMENT_SOURCES: readonly ArgumentSource[];
export declare const ARGUMENT_ASKED: readonly ArgumentAsked[];
export declare const ARGUMENT_CLAIMS: readonly ArgumentClaim[];
export declare const ARGUMENT_CHECKS_FAILED: readonly ArgumentCheckFailed[];
/** The placeholder a hidden argument reads — the redacted mirror's own. */
export declare const HIDDEN_VALUE = "REDACTED";
/**
* One value as a row may hold it: the shown view's value, printed as a string
* (`String()` for a number or a boolean, JSON for anything else) and clipped.
* A value the view replaced with the placeholder stays the placeholder.
*/
export declare function shownValue(value: unknown): string;
/** What the Record stage knows about one ruled argument of one call. */
export interface ArgumentVerdict {
readonly toolCallId: string;
readonly toolName: string;
readonly argument: string;
readonly rule: 'ask' | 'assume';
readonly period?: true;
readonly source: 'default' | 'model';
/** The value the call runs with, in the shown view (the fill, or the model's). */
readonly shownValue: unknown;
/** The model's own value in the shown view — set only when the model sent one. */
readonly shownProposed?: unknown;
}
/** The identity of one ruled argument of one call — what every row builder is handed. */
export interface ArgumentIdentity {
readonly toolCallId: string;
readonly toolName: string;
readonly argument: string;
readonly rule: 'ask' | 'assume';
readonly period?: true;
}
/**
* The row for an argument the person is ASKED for: no `source` (nobody has
* given the value yet) and no `value` (there is none). `invalid-answer` when
* an answer did not fit the property's own schema and the field is asked
* again — the answer itself is not kept.
*/
export declare function askedRowOf(who: ArgumentIdentity, stamp: {
readonly turn: number;
readonly iteration: number;
}, asked?: ArgumentAsked): ArgumentRow;
/**
* The row for a value the person's ANSWER filled (`source: 'answered'`): the
* value the call runs with, in the tool's own argument view; `proposed` only
* when the call had carried a value the answer replaced (absent = the call
* left it out); `free` when the answer came through a free-text field — a
* name the person typed, which is provenance and never support.
*/
export declare function answeredRowOf(who: ArgumentIdentity & {
readonly shownValue: unknown;
readonly shownProposed?: unknown;
readonly free?: true;
}, stamp: {
readonly turn: number;
readonly iteration: number;
}): ArgumentRow;
/**
* The row for one verdict. `proposed` is written only on a `default` row the
* model sent itself — on a `model` row the value IS the proposal, and a second
* copy would say nothing.
*/
export declare function argumentRowOf(verdict: ArgumentVerdict, stamp: {
readonly turn: number;
readonly iteration: number;
}): ArgumentRow;
/**
* The row for a value the library filled from the turn's ONE window of the
* person's (the time layer, `time/bind.ts`): from the window the person
* settled in the time ask (`answered`, `matched: 'mention'`), from a `model`
* reader's unconfirmed reading of their words (`said`, `matched: 'mention'`
* and `reading` — it can support "not sure", never "known"), or from a window
* set in a UI (`app`, `appSource: 'time.window'`). A `said` window with no
* `reading` is read back only from a record an earlier version filed. The
* call's `call-window` row names the window and the form.
*/
export declare function windowRowOf(who: ArgumentIdentity & {
readonly shownValue: unknown;
readonly window: 'said' | 'derived-from-reading' | 'answered' | 'control';
}, stamp: {
readonly turn: number;
readonly iteration: number;
}): ArgumentRow;
/** The `appSource` of a value filled from the run's `time.window` (a UI control). */
export declare const TIME_WINDOW_SOURCE = "time.window";
/**
* A quote's bound on a row — the bound a basis row's one-line texts carry
* (`findings/types.ts` · `PROPOSITION_CHARS`, which this leaf cannot import),
* the cut STATED in the text (`…[clipped N chars]`).
*/
export declare const QUOTE_CHARS = 240;
/** A quote as a row holds it: at most `QUOTE_CHARS`, the cut stated. */
export declare function shownQuote(quote: string): string;
/**
* The declared-sources check's verdict on one argument of one call, as the
* row builder is handed it — enums, flags, ids and labels. The value, the
* model's proposal and the quote arrive ALREADY in the tool's own argument
* view (`shownValue`, `shownProposed`: `'REDACTED'` where the view hides the
* argument; `shownQuoteText`: `'REDACTED'` while any tool in reach can hide
* arguments — a quote is free text and may hold any hidden value), so a raw
* value never reaches a row.
*/
export interface SourcedVerdict {
readonly toolCallId: string;
readonly toolName: string;
readonly argument: string;
/** Absent on a free argument a `from` entry named. */
readonly rule?: 'ask' | 'assume';
readonly period?: true;
/** Absent exactly when the person is asked (`asked: 'unverified'`). */
readonly source?: ArgumentSource;
readonly asked?: 'unverified';
/** The value the call runs with, in the shown view — absent on an asked row. */
readonly shownValue?: unknown;
/** The model's own value, in the shown view — on a `default` row (V1) and an asked row. */
readonly shownProposed?: unknown;
readonly claimed: ArgumentClaim;
readonly matched?: ArgumentMatched;
/** The model's quote — `'REDACTED'` while any tool in reach can hide arguments (`resolve.ts` · `quotesMayShow`). */
readonly shownQuoteText?: string;
readonly reading?: true;
readonly earlier?: true;
readonly result?: string;
readonly setAside?: 'open' | 'noise' | 'ruled-out';
readonly argumentsFrom?: 'listed' | 'unlisted';
readonly appSource?: string;
readonly coincides?: 'person' | 'result' | 'app';
readonly malformed?: number;
readonly failed?: ArgumentCheckFailed;
}
/**
* The row for one argument the declared-sources check judged (honesty layer
* 2, declared sources): the identity, the verdict's
* source (or `asked: 'unverified'` — the person is asked, and nothing runs on
* the model's value), the value in the tool's own view, and every field of the
* claim and the check as the verdict carries it. Written in the one field order
* `ArgumentRow` declares.
*/
export declare function sourcedRowOf(verdict: SourcedVerdict, stamp: {
readonly turn: number;
readonly iteration: number;
}): ArgumentRow;
/**
* Whether a value read from a stored checkpoint is an `argument` row this
* library could have written: the identity strings, a numeric `iteration` and
* `turn`, a `source` or an `asked` in its vocabulary, and every enum field in
* its own. The checkpoint door's `argument` arm (`core/runCheckpoint.ts`).
*/
export declare function argumentRowIsWellFormed(r: Readonly<Record<string, unknown>>): boolean;