agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
100 lines (99 loc) • 5.29 kB
TypeScript
/**
* scripts/match — is a fenced block the program ONE call of this turn
* submitted? Identity against the record, never evidence for a value.
*
* Pattern: pure function over the answer and this turn's rows.
* Role: core/ layer. Asked inside the ONE judge closure the Route decider
* builds (`stages/route.ts` · `judgeEvidence`, through `judge.ts`),
* before the names-and-numbers extractor reads the text — so the
* draft and the delivered candidate are read alike. Only under
* `.namesAndNumbersFromEvidence({ code: 'match' })`.
* Emits: N/A.
*
* ## The match rule: exact, after removing only the wrapper (design § 5.2)
*
* A block matches a call only when its content (`../evidence/codeRegions.ts`)
* equals the program the call SUBMITTED to the tool, byte for byte. Nothing is
* transformed: no trailing-space strip, no line-ending rewrite, no Unicode
* normalization, no shape, no edit distance — each can change a literal and so
* the program (`print(len("""A \nB"""))` prints 5, its copy without the two
* spaces 3; `"é"` written with a combining mark is one more character than its
* NFC form). The strings are compared as they hold the text (UTF-16 code
* units): for well-formed text that is UTF-8 byte equality, and for a lone
* surrogate it is stricter, the safe direction.
*
* - Against the rows of the CURRENT TURN only; earlier turns are counted
* (`earlierTurnRows`), never compared, and a miss says "no submission of
* this turn", never "no submission".
* - A block over `MAX_SCRIPT_BYTES` is `over-size`: no row keeps a program
* that long, so it is never compared.
* - Two calls that submitted byte-identical programs: the block names the
* LATEST, counts the rest (`alsoMatches`) and carries its label.
* - No nearest submission is ever named: a near match is a different program.
*
* WHAT A MATCH YIELDS. The block is script content: it is cut out of the text
* the extractor reads, so it yields no value candidates — and it grounds
* nothing: the evidence corpus is untouched, so a value in the prose that also
* sits in the script still needs a result. A MISS stays in the text and is
* checked as prose, exactly as today.
*
* COST. One linear scan of the answer (`codeRegionsOf`); each of at most
* `MAX_CODE_REGIONS` blocks is looked up once among at most `MAX_SCRIPT_ROWS`
* programs of at most `MAX_SCRIPT_BYTES` — within the 512 comparisons per
* judged answer of design § 4.4. Earlier turns add nothing.
*/
import { type TurnScripts } from './rows.js';
import type { ScriptBlockVerdict, ScriptsVerdict, UnmatchedReason } from './types.js';
/** One answer, read for its scripts. */
export interface ShownScripts {
/** The answer with every MATCHED block cut out — what the extractor reads. */
readonly prose: string;
/** The answer with EVERY block cut out — "the prose", for `unsupported[].region`. */
readonly outsideCode: string;
readonly verdict: ScriptsVerdict;
}
/**
* Read `answer`'s fenced blocks against this turn's submissions. Pure; the
* rows are `rows.ts` · `scriptRowsOfTurn`'s reading (last row per call).
*/
export declare function readShownScripts(answer: string, turn: TurnScripts, scriptsDropped?: number): ShownScripts;
/**
* A verdict as detached plain data — field by field, never a reference into
* state (a value read back through a TypedScope is a live view, and an event
* payload must be plain data).
*/
export declare function copyScriptsVerdict(verdict: ScriptsVerdict): ScriptsVerdict;
/** Every reason a block can be unmatched for. */
export declare const UNMATCHED_REASONS: readonly UnmatchedReason[];
/**
* One block verdict's fields, checked as {@link readShownScripts} writes them
* — a fresh copy, or `undefined` when any field is one no writer of this
* library files: a position or a dispatch that is not a whole number from 1,
* a label outside the vocabulary, an exit code its label does not carry
* (`label.ts` · `exitCodeFits`), an unmatched block that names a call, a
* matched one without its call. Fields beside these (a witness row's `kind`,
* `turn`, `iteration`) are not read.
*/
export declare function blockVerdictOf(value: unknown): ScriptBlockVerdict | undefined;
/** One block verdict read back, at its position in the record's `blocks`. */
export interface RecordedBlock {
readonly index: number;
readonly verdict: ScriptBlockVerdict;
}
/** A saved `evidence_checked.scripts`, read back. */
export interface RecordedBlocks {
/** The verdicts a writer of this library files, at their record position. */
readonly blocks: readonly RecordedBlock[];
/**
* What no writer files, counted once each: a verdict that does not read
* back, every verdict past `MAX_CODE_REGIONS` (the writer reads no more
* blocks), or a `scripts` value that is not `{ blocks: [...] }`.
*/
readonly unread: number;
}
/**
* A saved `evidence_checked.scripts` — THE reader of the shape for a reader
* of a record. `undefined` when the event carries no scripts arm. Bounded by
* the writer's cap: no reader iterates more verdicts than a writer files.
*/
export declare function recordedBlocks(value: unknown): RecordedBlocks | undefined;