UNPKG

agentfootprint

Version:

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

170 lines (169 loc) • 8.96 kB
/** * arguments/checks — the declared-sources checks: where the model SAID a value * came from, checked against the one place that source lives. * * Pattern: Walker, pure. `checkSource` is a function of one argument's value, * its rule, the model's claim about it (`_findings.from`, read by * `sources.ts` · `readSources`) and the corpora the layer is handed * (`SourceCorpus`). The same inputs always give the same verdict; no * clock, no model, no scope. * Role: core/ layer, the inputs layer (honesty layer 2), step 5. Loaded with * the layer's stage bodies (`resolve.ts` imports it; both through * `import()`), never on a plain agent's graph. Imports nothing from * `findings/` (the one-way law): the mount builds the corpus and hands * it in. * Emits: N/A. * * ## One claim, one corpus — and a pass is never support * * Every check is whole-token membership (`lib/normalize.ts` · `tokenize` * and `canonicalForm`, the evidence gate's own "same value", through * `evidence/resultCarries.ts` · `canonicalTokens`) in the ONE place the * claimed source lives: * * | Claim | Checked against | Verified as | * |-----------|-----------------------------------------------------------------------|------------------------------------| * | `user` | the quote in the person's own messages; the value in the quote, or a phrase the author declared for it | `said` (`matched: 'quote' \| 'phrase'`), or a READING | * | `result` | the named result's own bytes (`evidence/resultCarries.ts` · `resultReader`, read once per batch) | `result` (`setAside` when the model set it aside) | * | `turn` | the person's earlier answers to the library's ask (the ledger's `answered` rows) | `answered` (`matched: 'spelling'` across `24h` ↔ `-24h`) | * | `app` | the app's own text (system messages, the composed system prompt, `externalGrounds`) | `app` (`appSource` = the ground's label) | * | `assumed`, nothing | nothing — the model's own | `model` | * * A failed check is FILED as written (`failed`), never repaired and never * retried against another corpus: a false claim is itself a fact. For a failed * claim and for the model's own value, the library still looks the value up * (`coincides`) — a HINT for the lens and the bench, never a source, never * support, never enough to skip an ask. * * Membership can REFUTE a claim ("those words are not in any message you * sent"); a pass proves nothing — the words can be there by coincidence, in a * negation, in another sentence. So no verdict here ever supports "known": a * pass only keeps a reason from firing (`assessment/assess.ts` · * `readArgumentVerdicts`). * * ## The declared default beats the app and a result (V1) * * A value equal to the argument's own `assume` value that the checks did not * verify as the PERSON's (their quoted words, or their answer) is filed * `default`, the claim kept as written — a model that copies a default from a * description chose nothing, and a default must never earn more standing * through `app` or `result` than the same default declared as `assume`. */ import { type PeriodSpelling, type RuledArgument } from '../../../tools/declare.js'; import { type ArgumentCheckFailed, type ArgumentClaim } from './rows.js'; import type { DeclaredSource } from './sources.js'; /** One message a PERSON wrote (`lib/saidByPerson.ts` · `isSaidByPerson`), in the served window. */ export interface PersonWords { readonly text: string; /** Before the current request — an earlier turn. */ readonly earlier?: true; /** * The run's OWN message in a composed run (`AgentInput.messageFrom: * 'composed'`): another runner's words handed on by a composition, never * the person's. A quote found only here fails as `composed-message`. */ readonly composed?: true; } /** One tool result the model was served, by id. */ export interface ServedResult { readonly toolCallId: string; readonly toolName?: string; /** * The TOOL's own bytes as served — cut at the tool-bytes boundary * (`lib/toolBytes.ts` · `toolBytesOf`), so a library note never speaks for * the tool. Absent when the id is known only through the previous batch * (its entry carries no cut): such a claim is `uncheckable`, never "found". */ readonly text?: string; /** Served before the current request — an earlier turn. */ readonly earlier?: true; /** The model's current standing on it — the ledger's fold plus this batch's own `previous[]`. */ readonly standing?: 'fact' | 'open' | 'noise' | 'ruled-out'; } /** One piece of the app's own text. `label` names an `externalGrounds` entry's source. */ export interface AppWords { readonly text: string; readonly label?: string; } /** One answer the person gave the library's ask earlier (an `answered` row on the ledger). */ export interface EarlierAnswer { readonly toolName: string; readonly argument: string; /** The row's value — the tool's own view, clipped; `'REDACTED'` where the view hides it. */ readonly value: string; readonly turn: number; readonly period?: true; /** The answered tool's declared period spelling, on its period argument. */ readonly spelling?: PeriodSpelling; } /** * What the checks read — built by the mount from the served history, the * composed system prompt, the app's `externalGrounds` and the ledger's * standings and answers (`honesty/sourceCorpus.ts` · `sourceCorpusOf`). Never * the library's own frames, notes or served sentences; assistant text only to * say a value was found nowhere else (`only-in-model-answer`). */ export interface SourceCorpus { /** The conversation turn this batch belongs to (`AgentState.turnNumber`). */ readonly turn: number; readonly person: readonly PersonWords[]; readonly results: readonly ServedResult[]; readonly assistant: readonly string[]; readonly app: readonly AppWords[]; readonly answers: readonly EarlierAnswer[]; } /** One argument of one call, as the check is handed it. The value is RAW — never stored. */ export interface SourceSubject { readonly toolName: string; readonly argument: string; readonly value: unknown; /** The argument's rule; absent on a free argument a `from` entry named. */ readonly rule?: RuledArgument; /** The tool's declared period spelling, when this is its period argument. */ readonly spelling?: PeriodSpelling; /** The tool's `argumentsFrom` — the tools whose results ground its arguments. */ readonly argumentsFrom?: readonly string[]; /** The model's `from` entry for this argument; absent when it declared none. */ readonly claim?: DeclaredSource; } /** The check's verdict — enums, flags, one id, one label; never a value. */ export interface SourceCheck { readonly source: 'said' | 'answered' | 'result' | 'app' | 'default' | 'model'; readonly claimed: ArgumentClaim; readonly matched?: 'quote' | 'phrase' | 'spelling' | 'mention'; 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 failed?: ArgumentCheckFailed; } /** * Whether a verdict traces the value to a source the record holds — the * person's words (`said` via the quote or a declared phrase), the person's * answer, a result, the app. A READING (`said` + `reading`), the model's own * value, a hint and a failed claim do not. Under an `ask` rule with declared * sources armed, a value this says no to is asked about (`resolve.ts`). */ export declare function isTraced(check: Pick<SourceCheck, 'source' | 'reading'>): boolean; /** * Where one argument value came from, as the record can check it — the * model's claim (`subject.claim`) checked against the one corpus that source * lives in, then the declared default applied last (V1). The value is read in * memory and never kept: the verdict holds enums, flags, a result id and an * app label only. * * @example * ```ts * checkSource( * { toolName: 'search_logs', argument: 'window', value: '7d', * claim: { argument: 'window', source: 'user', quote: 'over the last week' }, * rule: windowRule }, // choices: [{ value: '7d', said: ['last week'] }, …] * corpus, // person: [{ text: 'errors on checkout over the last week?' }] * ); * // { source: 'said', claimed: 'user', matched: 'phrase' } * ``` */ export declare function checkSource(subject: SourceSubject, corpus: SourceCorpus): SourceCheck;