agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
116 lines • 5.52 kB
JavaScript
;
/**
* window/folded — join a summary sitting in a conversation back to what it
* stands for.
*
* Pattern: Pure lookup over recorded facts. No I/O, no inference.
* Role: core/ layer, and the ONE public door onto retained originals.
* Emits: N/A.
*
* ── Why a fingerprint and not an index ──────────────────────────────────────
* The obvious join key is "the summary is at `history[3]`". It is also wrong.
* A later fold swallows the span that CONTAINS an earlier summary, and every
* index after it moves; a conversation that folded four times would need its
* stored indices rewritten on every fold, and any reader holding an old one
* would silently be pointing at somebody else's message.
*
* So the join key is a fingerprint of the summary message's full content —
* authored frame included. Three things follow, and all three are the point:
*
* • it costs NO extra bytes on the wire (no id token planted in the frame);
* • it survives JSON, storage, and being read by a runtime that has never
* heard of this function;
* • it cannot be FORGED. A model that writes a message opening with the
* frame's own words produces different content, therefore a different
* fingerprint, therefore no match — `isCompactedSummary` alone says "this
* LOOKS like a frame", and this says "this IS the frame of a fold that was
* recorded". The two answers are different questions and the gap between
* them is exactly where a forgery would live.
*
* ── Absent is an answer ─────────────────────────────────────────────────────
* `undefined` means "no fold was recorded for this message" and never "there
* were no originals". A conversation stored by 8.1 has summaries and no spans
* at all; one folded under `retain: 'discard'` has a span whose `messages` is
* absent. Those are three different facts and the caller can tell them apart:
* no span, a span with no messages, a span with messages.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.foldedMessages = exports.foldedSpanFor = exports.spanFingerprint = exports.summaryFingerprint = void 0;
const helpers_js_1 = require("../../slots/helpers.js");
/**
* The join key for one summary message: a fingerprint of its whole content.
*
* @internal Consumers join through {@link foldedSpanFor}; this is exported for
* the strategy that stamps the span and for tests that pin the two agree.
*/
function summaryFingerprint(content) {
return (0, helpers_js_1.fnv1a)(content);
}
exports.summaryFingerprint = summaryFingerprint;
/**
* The join key for one SPAN — the messages a fold would replace.
*
* Same hash family as {@link summaryFingerprint}, different subject: that one
* identifies the claim, this one identifies what the claim would be about.
* `summarizeOldest` uses it to remember which spans it has already been
* refused on, so a deterministic "no" is paid for once instead of once per
* iteration for the rest of the run.
*
* Role and content, both — a span whose messages are identical but whose roles
* differ is a different transcript and deserves to be asked about again.
*
* @internal
*/
function spanFingerprint(messages) {
return (0, helpers_js_1.fnv1a)(messages.map((m) => `${m.role}:${m.content}`).join('