agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
56 lines (55 loc) • 3.41 kB
TypeScript
/**
* contextLedger — the post-run bookkeeper (see types.ts for the WHY).
*
* DESIGN: a pure POST-RUN analyzer over the run's own commit log — no live
* recorder lifecycle, no event-order coupling. Everything it counts is
* already durably recorded by the engine (dogfooding: offers come from
* `commitValueAt` folds, answer attribution from `sliceForKey` — the same
* canonical queries every triage surface uses):
*
* offers — the context IN EFFECT at each LLM call. Call marker: a
* commit that wrote `totalInputTokens` (monotonic — never
* net-change-dropped) and did NOT write `userMessage` (which
* only the seed writes). At each call index, fold
* `activeInjections` + `dynamicToolSchemas`, and add the
* STATIC tool registry (duck-read from the runner's public
* `getUIGroup().extra.toolNames` — static registries live in a
* closure, never in scope state). NOTE offers are deliberately
* NOT "per context-key commit": the net-change filter drops
* identical re-commits, so stable context appears once in the
* log while still being offered (and paying tokens) per call.
* Static-registry tools count offers with approxTokens 0 in L1
* (their schema JSON isn't in the log) — earnRate, the gate
* signal, is unaffected; dynamicToolSchemas carries real sizes.
* uses — tool: assistant messages' toolCalls in the final history;
* skill: `activatedInjectionIds`;
* injection: its SLOT's write sits on the final answer's
* backward slice (slot-granular, labeled).
* outcome — consumer label per run, credited to every offered piece.
*
* The slot→slice join needs NO id conventions: for each slot key
* (INJECTION_KEYS.*), `findLastWriter` names the commit that fed the final
* LLM call; membership of that writer in the answer slice IS the signal.
*
* Every read goes through footprintjs's BASIS twins (`commitValueAtWithBasis`,
* `findLastWriterWithBasis`): an answer that rests on nested rows, the
* pre-run state, a redaction or a delete says so on `RecordedRun.basis`
* (per key, absent when every answer was exact) instead of being counted as
* if it were exact.
*
* UNDER A REDACTION POLICY (an agent's `redact`, `src/redaction/`) the log
* holds the placeholder wherever the policy selected a value — footprintjs
* scrubs it as it is written. The gates decide what LATER runs are offered,
* so a placeholder must never read as "never used":
* - a FINAL value the log keeps out (the history the tool calls are counted
* from, the slot records) is read from the run's LIVE end state when the
* source is a runner of this library (`sourceOf` — `core/runnerLive.ts`);
* only ids and counts reach a row, never a value;
* - a read nothing can answer (a value per call — the log is the only record
* of what each call was offered — or any read from a snapshot handed in)
* leaves its kind UNMETERED for the run (`READS_OF`): neither its offers
* nor its uses are counted, `RecordedRun.unmetered` names it, and a gate
* keeps offering it as it does a piece with too few offers to judge.
*/
import type { ContextLedger } from './types.js';
export declare function contextLedger(): ContextLedger;