agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
63 lines (62 loc) • 3.23 kB
TypeScript
/**
* evidence/answerCarriers — which of THIS turn's tool results the answer's
* names and numbers were read from.
*
* Pattern: one pure fold over two things the gate already holds — the
* values a tool result DID carry (`EvidenceVerdict.grounded`) and
* the corpus's carriers (`EvidenceCorpus.carriers`). No second walk,
* no second budget.
* Role: core/ layer leaf. Asked once per judged answer by
* `stages/route.ts · judgeEvidence`; its output rides
* `agentfootprint.agent.evidence_checked` as `carriedBy`.
* Emits: N/A.
*
* ## Why
*
* A turn can fetch a result and then answer from something else — an earlier
* turn's data, a computation over it, a sibling call. A host that puts every
* result the turn fetched under the answer ("here is the data") then shows a
* dataset the answer never used as if the answer stood on it. The host cannot
* work out which results the answer used: that needs the gate's extractor,
* its spellings and its carriers, which are the library's. So the library
* says it, as identities and counts — never a value.
*
* ## What it says, and what it does not
*
* One entry per result of THIS turn that carried at least one of the
* answer's grounded values: how many (`values`), and how many of those no
* other result of this turn carried (`only`). A result of this turn that is
* NOT listed carried none of the names and numbers the answer states. That
* is a fact about tokens, never about meaning: an answer that states no value
* ("yes, the cluster is healthy") can rest on a result it cites nothing from,
* so a reader says "cited nothing from it", never "did not use it".
*
* ## The whole list or none
*
* `undefined` when the corpus hit its token ceiling (`truncated`) or one
* value's carrier list was cut at `MAX_CARRIERS`: a result past the cut is
* invisible, and "not listed ⇒ cited nothing" read off a prefix would be a
* false denial. The contingent check (`findings/contingent.ts`) keeps the
* same law for the same reason.
*
* THIS TURN ONLY: the carriers are cleared at each user-turn boundary, so a
* value an answer took from an earlier turn's result names no carrier here.
*/
import type { EvidenceCorpus } from './evidenceIndex.js';
import type { GroundedValue } from './types.js';
/** One result of this turn that carried at least one of the answer's values. */
export interface AnswerCarrier {
/** The `toolCallId` of the `role: 'tool'` message that carried them. */
readonly toolCallId: string;
/** How many of the answer's distinct grounded values this result carried. */
readonly values: number;
/** How many of those values no other result of this turn carried. */
readonly only: number;
}
/**
* The results of this turn the answer's grounded values were read from, in
* the order the corpus first lists them — or `undefined` when the corpus
* cannot give the whole list (see the header). Two spellings of one value
* share a canonical form and count once. Pure.
*/
export declare function answerCarriersOf(grounded: readonly GroundedValue[], corpus: Pick<EvidenceCorpus, 'carriers' | 'truncated'>): readonly AnswerCarrier[] | undefined;