UNPKG

agentfootprint

Version:

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

91 lines • 4.06 kB
"use strict"; /** * 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. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.answerCarriersOf = void 0; const normalize_js_1 = require("../../../lib/normalize.js"); /** * 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. */ function answerCarriersOf(grounded, corpus) { if (corpus.truncated) return undefined; // Insertion order of the Map IS the order the corpus first listed each result. const counts = new Map(); const seen = new Set(); for (const { value, forms } of grounded) { const canonical = (0, normalize_js_1.canonicalForm)(value); if (seen.has(canonical)) continue; seen.add(canonical); const ids = []; for (const form of forms) { const entry = corpus.carriers.get(form); if (entry === undefined) continue; if (entry.truncated === true) return undefined; for (const id of entry.toolCallIds) if (!ids.includes(id)) ids.push(id); } for (const id of ids) { let held = counts.get(id); if (held === undefined) { held = { values: 0, only: 0 }; counts.set(id, held); } held.values += 1; if (ids.length === 1) held.only += 1; } } return Object.freeze([...counts].map(([toolCallId, held]) => Object.freeze({ toolCallId, values: held.values, only: held.only }))); } exports.answerCarriersOf = answerCarriersOf; //# sourceMappingURL=answerCarriers.js.map