UNPKG

agentfootprint

Version:

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

146 lines • 7.81 kB
/** * prior-turn-evidence — the answer is grounded, and every value in it was * served BEFORE this turn. Filed at the CLAIM seam, as an advisory, never an * accusation. * * Pattern: pure function over one reading of a finished judgement (the * `readLookupResult` → `emptyLookupOf` shape, one seam later). * Role: the decidable fragment of "did this answer come from the data, or * from the conversation?". * * THE MEASURED FAILURE. A consumer's agent answered a data question with ZERO * tool calls, and the evidence gate approved it: `LLM calls 1 · Tool calls 0 · * Iterations 1`, then "All 7 values in the answer were found in what the tools * returned — the answer stands." They were found: in an inventory result from * four turns earlier, fetched for a different question. The user had asked * about array performance; the answer confidently recommended enabling a * collector that had been running for months. Two turns did it back to back. * * Every rail passed honestly. The gate measures GROUNDEDNESS and had no notion * of WHEN a value was grounded, which is exactly the fact the run needed. * * AND THE GATE SAID OTHERWISE. Its two sentences — the correction it sends the * model and the warning it prints an operator — both said the flagged values * "appear in NO tool result FROM THIS TURN" (`../../core/agent/evidence/ * gate.ts`). The index behind them has never been turn-scoped. So the library * was already ASSERTING the boundary it could not measure, in the two places * the assertion is read. That contradiction is the defect this change closes, * and it closes it in the only honest order: the sentences were narrowed to * what the check really reaches, and the turn boundary became something the * index can actually MEASURE and report — off by default, beside the gate, * never folded into its verdict. * * WHAT THE LIBRARY ALREADY KNOWS, and this check is nothing more than reading * it out: * (a) which values in the answer the evidence index could ground — the * gate computes that already, for every armed agent; * (b) which TURN each of those values was last served in — one number the * index stamps during the walk it was already doing. * When every grounded value's newest source is older than the turn in * progress, the answer was assembled from the conversation rather than from * anything this turn went and looked at. That is worth filing. It is not * worth accusing anyone of. * * THE CORPUS IS NOT NARROWED, and that decision is the design. Narrowing the * index to this turn would have made the gate's old sentence true and been the * wrong fix: it would flag every honest follow-up — "and what about that * disk?" legitimately leans on the previous turn's rows — and a check that * cries wolf is a check somebody turns off. The corpus keeps its reach; only * the REPORT gained a time axis. * * WHAT THE CORPUS ACTUALLY REACHES, said plainly because a bound that is * implied is a bound that drifts: `scope.history` as it stands at judgement, * which under `.window()` / `.compaction()` / `tokenBudget` is the LIVE WINDOW * (window.ts rewrites it in place). So the turn ordinals here count the user * turns the run can still SEE — "turn 2 of 4" may be the conversation's turn 9 * of 13 — and the distance reported is a FLOOR. The boundary itself is exact * whatever the window does, because the current request is un-droppable by * every window strategy. And a value that reached the model through * `.memory()` recall or RAG is EXEMPT from grounding altogether, so it is * invisible here: this check can under-report and can never over-report. * * THE CEILING, and it is the whole reason this is an advisory: see * {@link PRIOR_TURN_EVIDENCE_CEILING}. Referring back is not a defect. NOTHING * IN THIS FILE CAN TELL a legitimate reference from a stale one, and nothing * in this file pretends to — the same finding is filed for both. * * WHAT KEEPS THE HONEST FOLLOW-UP QUIET. One grounded value from this turn's * own results is enough to say nothing. That is not a heuristic threshold: the * claim being made is "EVERY value came from before this turn", and one that * did not falsifies it outright. In practice a follow-up that calls a tool * gets this for free, because a lookup keyed on an earlier identifier echoes * that identifier back in its own result — so the value is re-served this turn * and re-stamped with this turn's number. * * Detection only. Nothing here blocks, revises, rewrites or delays anything; * the answer the caller receives is the answer the model wrote, and the * evidence gate's own posture keeps every decision it ever had. */ import type { Disposition } from '../disposition/types.js'; import type { ContextError } from '../finding/types.js'; /** * THE CEILING, as one string with one owner. * * Quoted verbatim into every finding's message, into the check's README and * into its docs page, so the bound cannot drift out of one of them and leave a * reader thinking the library knows more than it does. */ export declare const PRIOR_TURN_EVIDENCE_CEILING: string; /** * What the library could read about one judged answer's grounding. * * Produced by the evidence gate (`checkAnswer`), which is the only component * that knows both which tokens in the answer are DATA and which of them the * index could ground. Passed IN rather than re-derived here, for the reason * `readLookupResult` takes `declaredAbsence` as a parameter: the extractor has * exactly one owner, and a second reading of "what counts as a value" would * eventually disagree with the first. */ export interface AnswerGroundingReading { /** Grounded values whose newest source is a result THIS turn served. */ readonly fromThisTurn: number; /** Grounded values whose newest source is older than the turn in progress. */ readonly fromPriorTurns: number; /** * The newest turn any of those older values came from. Absent when * `fromPriorTurns` is 0. This is the number a reader wants first: "turn 2, * and we are on turn 4". * * Counted over the user turns still in the run's window, so under a window * strategy it is not the conversation's own ordinal and the distance from * `currentTurn` is a FLOOR. */ readonly latestPriorTurn?: number; /** * The turn in progress, on the same window-relative scale. `0` = the * history carries no user turn at all, and then there is no boundary to * measure against. */ readonly currentTurn: number; /** How many `role: 'tool'` results this turn served. `0` is the sharp case. */ readonly toolResultsThisTurn: number; /** * The evidence index hit its ceiling and is INCOMPLETE. A partial index can * miss the very occurrence that would have stamped a value with this turn, * so provenance from one is not something to file on. */ readonly indexTruncated: boolean; } /** One judged answer's outcome: the findings, and the disposition it earns. */ export interface PriorTurnEvidenceEncounter { readonly findings: readonly ContextError[]; /** * The ledger row this judgement files — computed HERE, beside the rules * that decide it, so "the library refused to judge this answer" is provable * without a live agent. */ readonly disposition: Disposition; } /** * Judge one finished answer's grounding provenance. * * @param reading what the evidence gate could see of the answer's values. * @param epoch the run iteration, stamped on every witness. */ export declare function priorTurnEvidenceOf(reading: AnswerGroundingReading, epoch: number): PriorTurnEvidenceEncounter; //# sourceMappingURL=check.d.ts.map