UNPKG

agentfootprint

Version:

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

145 lines • 7.87 kB
"use strict"; /** * ledger — what a clean result does NOT rule out. * * Pattern: a wrapper the framework recognizes, minted by a helper (the * `absent()` shape one level up: same vocabulary, different claim). * Role: core/ layer, pure. `coverage()` is what a tool author writes; * `readCoverageLedger` is what the dispatch loop calls. * Emits: N/A (the caller emits `agentfootprint.tools.coverage_declared`). * * ## The problem, stated as narrowly as it deserves * * An agent answers confidently from partial coverage. "Everything looks fine" * is produced from four checks that passed, and arrives with no way to tell * whether *fine* means **verified** or **unexamined** — whether the fifth * thing was healthy or was never looked at. The evidence gate (9.35.0) is the * sibling of this: the gate catches invented VALUES, this catches unstated * LIMITS. Both are ways an answer can be false while every token in it is * real. * * A ledger is not a caveat and not a disclaimer. It is three lists the TOOL * knows and the model does not: what it checked, what it did not check, and * what it can never cover. Only the tool knows the third one, which is why * this cannot be prompt engineering. * * ## Why the wrapper, and not a field on the result * * The alternative — teaching the framework to read a `coverage` key off any * returned object — would make every domain object that happens to have one * change behavior. The wrapper is opt-in by construction: `coverage(x, …)` * produces a shape that did not exist before, so nothing that ever ran can * become a ledger by accident. Same argument as the effects envelope's strict * recognizer, same guarantee. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.coverageOfLedger = exports.coverage = exports.COVERAGE_NOTE = exports.readCoverageLedger = exports.COVERAGE_MARKER = void 0; const inProgress_js_1 = require("./inProgress.js"); const items_js_1 = require("./items.js"); const period_js_1 = require("./period.js"); const refusal_js_1 = require("./refusal.js"); const recognize_js_1 = require("./recognize.js"); /** * The keys `coverage()`'s declaration has — the three lists and, since honesty * step 7b, the period the value's read covered — tied to the type in BOTH * directions, the `COVERAGE_DECLARATION_KEYS` way. That list stays the three * lists alone: a described result's `coverage` shares it, and takes no period * (its period is top-level). */ const LEDGER_DECLARATION_KEYS = Object.keys({ checked: true, notChecked: true, cannotCover: true, period: true, inProgress: true, }); // The recognizer lives in the leaf `recognize.ts` (a post-hoc reader — the one // emptiness reader, the standing fold — asks it without loading this mint); // re-exported here so every import keeps working. const recognize_js_2 = require("./recognize.js"); Object.defineProperty(exports, "COVERAGE_MARKER", { enumerable: true, get: function () { return recognize_js_2.COVERAGE_MARKER; } }); Object.defineProperty(exports, "readCoverageLedger", { enumerable: true, get: function () { return recognize_js_2.readCoverageLedger; } }); /** * The static sentence every ledger carries. The last clause is the OFFER half * of survival — the model is told to carry the limits into its answer. The * ENFORCEMENT half (`.limitsTravelWithTheAnswer()`) does not depend on the * model obeying it; see `answer.ts` for why both exist. * * "the call this result answers", not "this call" (9.86.1): the note rides a * tool result, which is re-read on every later call of the turn, and a bare * `this call` there denotes whichever call is reading it. The container * deictic's own anchor names the call instead. */ exports.COVERAGE_NOTE = 'This result covers only what `checked` lists. `not_checked` is ground the call this result ' + 'answers did not look at, and `cannot_cover` is ground this tool can never see — a clean ' + 'result here is ' + 'NOT evidence about either, and no retry changes `cannot_cover`. Carry these limits into ' + 'any answer you build on this result.'; function coverage(content, decl) { const fn = 'coverage'; if (typeof decl !== 'object' || decl === null) { throw (0, refusal_js_1.refusal)(`${fn}() takes the result and its boundary — coverage(result, { checked?, notChecked?, ` + `cannotCover?, period?, inProgress? }). To return a result with no declared boundary, ` + `return it bare.`); } (0, refusal_js_1.refuseUnknownKeys)(decl, LEDGER_DECLARATION_KEYS); // `checked: 'declared'` — the tool's `search` declares the ground; the // dispatch funnel composes it (the `absent()` twin). It is a boundary, so a // ledger declaring only it is not the empty ledger refused below. const declared = decl.checked === recognize_js_1.DECLARED_CHECKED; const checked = declared ? [] : (0, items_js_1.normalizeCoverageList)('checked', decl.checked, false); const notChecked = (0, items_js_1.normalizeCoverageList)('notChecked', decl.notChecked, false); const cannotCover = (0, items_js_1.normalizeCoverageList)('cannotCover', decl.cannotCover, true); // Honesty step 7b: the period the value's read covered, by the ONE period // rule (`period.ts` · `mintPeriod`) — refused here, in camelCase, when // malformed. A period IS a declared boundary (in time), so a ledger that // declares only a period is not the empty ledger refused below. const period = (0, period_js_1.mintPeriod)(decl.period); // What the read found still running (`inProgress.ts` — the ONE rule set). // Refused without `checked` whether or not the list is empty: a rule that // fired only when something happened to be running would pass every test // and refuse on the first busy night in production (the 7a′ lesson). const inProgress = (0, inProgress_js_1.mintInProgress)(decl.inProgress); if (decl.inProgress != null && checked.length === 0 && !declared) { throw (0, refusal_js_1.refusal)('`inProgress` needs `checked` beside it — an item still running is ground the call ' + 'read, so say what the call read.'); } if (!declared && checked.length === 0 && notChecked.length === 0 && cannotCover.length === 0 && period === undefined) { throw (0, refusal_js_1.refusal)(`all three lists are empty, so this ledger declares no boundary at all — it ` + `would tell a reader nothing while looking like it did, which is worse than saying ` + `nothing. Name what you checked, what you skipped, or what you can never see; or ` + `return the result bare.`); } // Built untyped, then given the type its `checked` decides (the overloads). const minted = { af_coverage: { ...(declared ? { checked: recognize_js_1.DECLARED_CHECKED } : checked.length > 0 && { checked }), ...(notChecked.length > 0 && { not_checked: notChecked }), ...(cannotCover.length > 0 && { cannot_cover: cannotCover }), ...(inProgress.length > 0 && { in_progress: inProgress }), // Serialized BEFORE `result`, with the lists, so a truncated view keeps it. ...(period !== undefined && { period }), note: exports.COVERAGE_NOTE, }, result: content, }; return minted; } exports.coverage = coverage; /** The ledger's coverage, in the normalized three-list shape. */ function coverageOfLedger(covered) { const m = covered.af_coverage; return { checked: m.checked ?? [], notChecked: m.not_checked ?? [], cannotCover: m.cannot_cover ?? [], }; } exports.coverageOfLedger = coverageOfLedger; //# sourceMappingURL=ledger.js.map