UNPKG

agentfootprint

Version:

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

163 lines • 7.77 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.findingsContext = exports.findingsContextBytes = exports.MAX_FINDINGS_CONTEXT_BYTES = exports.DEFAULT_FINDINGS_CONTEXT_BYTES = exports.FINDINGS_LAYOUT = void 0; const boundedJson_js_1 = require("../../../lib/boundedJson.js"); const saidByPerson_js_1 = require("../../../lib/saidByPerson.js"); const ledger_js_1 = require("./ledger.js"); const offer_js_1 = require("./offer.js"); const reserved_js_1 = require("./reserved.js"); const serve_js_1 = require("./serve.js"); exports.FINDINGS_LAYOUT = 'late-v1'; exports.DEFAULT_FINDINGS_CONTEXT_BYTES = 16_384; exports.MAX_FINDINGS_CONTEXT_BYTES = 262_144; /** The host owns this bound; a model declaration never changes it. */ function findingsContextBytes(value) { const bytes = value ?? exports.DEFAULT_FINDINGS_CONTEXT_BYTES; if (!Number.isSafeInteger(bytes) || bytes < 1_024 || bytes > exports.MAX_FINDINGS_CONTEXT_BYTES) throw new RangeError('findings.maxContextBytes must be a whole number from 1024 to 262144.'); return bytes; } exports.findingsContextBytes = findingsContextBytes; /** Explicit projections preserve model declarations, not raw ledger internals. * In particular, judge results, argument values and service payloads are not served. */ function declaration(row) { return { toolCallId: row.toolCallId, ...(row.toolName !== undefined && { toolName: row.toolName }), ...(row.ref !== undefined && { ref: row.ref }), ...(row.unknownId === true && { unknownId: true }), standing: row.standing, assertions: row.assertions.map((assertion) => ({ subject: { kind: assertion.subject.kind, id: assertion.subject.id }, predicate: assertion.predicate, value: assertion.value, stratum: assertion.stratum, })), ...(row.line !== undefined && { line: row.line }), ...(row.settles !== undefined && { settles: row.settles }), }; } /** Projection may fail on an unreadable row; the caller withholds the WHOLE * reading, including advisory IDs, instead of salvaging superseded assertions. * Other structured row kinds (judge, arguments, future metadata) are not served. */ function bucketsOf(messages, ledger) { const fold = (0, ledger_js_1.foldLedger)(ledger); const standings = [...fold.standingOf.values()]; const noise = standings.filter((row) => row.standing === 'noise').map((row) => row.toolCallId); const propositions = new Map(); for (const row of ledger) if (row.kind === 'basis' && row.proposition !== undefined) propositions.set(row.toolCallId, row.proposition); const proposed = (row) => ({ ...declaration(row), ...(propositions.has(row.toolCallId) && { proposition: propositions.get(row.toolCallId) }), }); return { advisoryResultIds: (0, offer_js_1.offeredResultIds)(messages, ledger), facts: standings.filter((row) => row.standing === 'fact').map(proposed), evidenceRefs: standings.filter((row) => row.standing === 'open').map(proposed), limitations: standings.filter((row) => row.standing === 'ruled-out').map(proposed), conflicts: fold.conflicts, contingent: ledger.flatMap((row) => row.kind === 'contingent' ? [ { declaredOn: row.declaredOn, value: row.value, carriers: row.carriers.map((carrier) => ({ toolCallId: carrier.toolCallId, standing: carrier.standing, })), }, ] : []), unsettledByAbsence: [...fold.unsettled.values()].map((row) => ({ toolCallId: row.toolCallId, ...(row.notChecked !== undefined && { notChecked: row.notChecked.map((item) => ({ what: item.what, ...(item.why !== undefined && { why: item.why }), })), }), ...(row.cannotCover !== undefined && { cannotCover: row.cannotCover.map((item) => ({ what: item.what, ...(item.why !== undefined && { why: item.why }), })), }), ...(row.tryInstead !== undefined && { tryInstead: row.tryInstead }), })), noise, undeclaredResultIds: (0, offer_js_1.undeclaredIds)((0, offer_js_1.servedToolCallIds)(messages), fold.standingOf), }; } /** Stable JSON field meanings; all mutable values occur after the cached prefix. */ function findingsContext(messages, rows, maxBytes = exports.DEFAULT_FINDINGS_CONTEXT_BYTES) { const limit = findingsContextBytes(maxBytes); // The record may come from a checkpoint. Admit plain bounded data before // folding, so a value's getter/toJSON cannot run during conflict comparison. // If the source cannot be admitted, withhold its reading as a whole: using // an older surviving row would revive a superseded assertion. let sourceUnknown = false; let buckets; try { const admitted = (0, boundedJson_js_1.copyJson)(rows === undefined ? [] : rows, 8_388_608, false); if (!Array.isArray(admitted) || admitted.some((row) => row === null || typeof row !== 'object' || Array.isArray(row) || typeof row.kind !== 'string')) throw new Error('Expected an array of structured findings rows.'); // Build the projection before admitting any of it. Malformed declarations // or unreadable rows must not leave an older, apparently current fact. buckets = bucketsOf(messages, admitted); } catch { sourceUnknown = true; buckets = bucketsOf([], []); } const data = {}; const omitted = {}; for (const [key, entries] of Object.entries(buckets)) { data[key] = []; omitted[key] = entries.length; } const encode = () => `${saidByPerson_js_1.FINDINGS_DATA_FRAME_PREFIX}\n${JSON.stringify({ layout: exports.FINDINGS_LAYOUT, ledgerStatus: sourceUnknown ? 'unknown' : 'captured', // Rejected sources have no trusted row count. Never inspect them again. sourceRowsOmitted: sourceUnknown ? null : 0, meaning: 'Model declarations are not verified facts; recorded observations are not a diagnosis. IDs are advisory, not authority. Omitted entries remain unknown.', data, omitted, })}`; const encoder = new TextEncoder(); // Counts start at their maximum, reserving their serialized space before // entries are admitted. They only decrease, so overflow cannot hide omissions. if (encoder.encode(encode()).byteLength > limit) throw new Error('Findings context metadata exceeds its byte budget.'); for (const [key, entries] of Object.entries(buckets)) { const cap = key.endsWith('Ids') || key === 'noise' ? reserved_js_1.FINDINGS_OFFER_CAP : serve_js_1.FINDINGS_PIECE_LIMITS.bucketLines; for (const entry of entries.slice(0, cap)) { let value; try { value = (0, boundedJson_js_1.copyJson)(entry, limit, false); } catch { continue; } // Unrepresentable or oversized data is counted, never coerced. data[key].push(value); omitted[key]--; if (encoder.encode(encode()).byteLength > limit) { data[key].pop(); omitted[key]++; } } } return encode(); } exports.findingsContext = findingsContext; //# sourceMappingURL=context.js.map