agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
113 lines (112 loc) • 5.93 kB
TypeScript
/**
* keyedFold — the value of ONE state key at one commit, folded from the run's
* own fold base.
*
* Role: Fold. It reads a finished run's commit log plus the base that log
* was recorded against, and answers "what was `key` at commit `idx`?"
* It executes nothing, records nothing and stores nothing beside the
* log. Its answers are DETACHED — deep-frozen before they are cached,
* because a memo that hands out live references is a fold that lets a
* reader rewrite the run (`keyedFold.ts` · `freezeDeep`).
* Reads: a `FoldSource` — a snapshot (`commitLog` + `initialState`) or a
* subflow subtree (`history` + `initialState`), through footprintjs's
* own `stateAt` (for the base) and `applySmartMerge` (for the replay).
* Emits: N/A.
*
* ── WHY THIS FILE EXISTS ───────────────────────────────────────────────────
* A commit log stores DIFFS. `commitValueAt` folds them for one key and says
* so in its own docstring: a value that came from the run's INITIAL state and
* was never `set` again folds to ABSENT, because the log alone cannot see the
* pre-run base.
*
* That is not an edge case here. A RESUMED agent run is a fresh executor
* seeded from `checkpoint.sharedState`, so the entire pre-pause world —
* `systemPromptInjections`, `history`, `dynamicToolSchemas`, every build-time
* constant `seed` wrote before the pause — is base, not log. Folding the
* resumed run with `commitValueAt` returns an empty system prompt and a
* truncated conversation and declares no gap: a confident falsehood about the
* one question this family exists to answer. footprintjs 9.17 shipped
* `RuntimeSnapshot.initialState` and `stateAt` for exactly this, and every
* read in `epochs.ts` goes through here so it lands on that base.
*
* ── WHY NOT JUST CALL `stateAt` PER READ ───────────────────────────────────
* `stateAt` folds the WHOLE state, and it clones that state once per bundle
* (`applySmartMerge` opens with a `structuredClone` of its base). One fold is
* therefore O(commits × state size) — measured on a real 100-turn agent
* (1,719 commits, 101 epochs): 323 ms for a single fold, and 11.7 s to fold
* once per epoch, against 78 ms for the whole per-epoch scrub before this
* release. A per-epoch reader cannot pay that.
*
* So `stateAt` is used for the ONE thing only it can give — the fold base and
* the honest `basis` verdict, at `commitIdx: -1`, where it folds nothing and
* clones the base once — and the forward replay is done per key, through
* footprintjs's own `applySmartMerge`. There is deliberately no second
* implementation of the trace verbs here: every `set` / `append` / `merge` /
* `delete` is applied by the same function the live commit uses.
* `keyed-fold-equivalence.test.ts` pins the result against `stateAt` itself on
* real runs, including a resumed one and a merge with no `set` anchor — the
* one shape that cannot be folded at all without the base.
*/
import type { CommitBundle } from 'foottrace';
/**
* How a fold was derived — footprintjs's own two answers, restated here so a
* caller of this module does not have to import the engine's trace barrel to
* name them.
*
* - `'initial+log'` — the recording carried its fold base, so a value seeded
* before the run (or before a resume) folds correctly.
* - `'log-only'` — it did not. Anything the log never `set` reads as absent,
* and a reader must say so rather than present the hole as a proof.
*/
export type FoldBasis = 'initial+log' | 'log-only';
/**
* Anything that carries a commit log and, ideally, the base it was recorded
* against — the two field spellings footprintjs already has: `commitLog` on a
* run snapshot, `history` on a subflow subtree.
*/
export interface FoldSourceLike {
readonly commitLog?: readonly CommitBundle[];
readonly history?: readonly unknown[];
readonly initialState?: Record<string, unknown>;
}
/**
* One key's value at one commit — the question, and the answers already
* computed for it.
*/
export interface KeyedFold {
/** How this fold was derived — see {@link FoldBasis}. */
readonly basis: FoldBasis;
/** How many bundles the log holds. */
readonly length: number;
/**
* The value of `key` folded through commit ARRAY INDEX `idx`, inclusive.
* `-1` (or lower) folds nothing and returns the base's value — the state
* before the log's first commit.
*
* DEEP-FROZEN, and the same object on every call for the same question. It
* is detached from the engine's own bundles AND unmutable, so a caller
* cannot rewrite what a later index folds to — `keyedFold.ts` · `freezeDeep`
* says why that is a law here rather than a courtesy. Copy it
* (`structuredClone`, a spread) if you need something to edit.
*/
valueAt(key: string, idx: number): unknown;
}
/**
* The keyed fold for one source, memoized on the source object.
*
* Memoized because a per-epoch reader asks the same source for the same keys
* at many different commits, and the index is the expensive half: building it
* is one pass over the log, and answering a read afterwards is a binary search
* plus a replay from the nearest full-value write (normally exactly one).
*
* @example
* ```ts
* import { keyedFold } from 'agentfootprint';
*
* const fold = keyedFold(agent.getSnapshot());
* fold.basis; // 'initial+log' — the base travelled
* fold.valueAt('history', 12); // the conversation after the 13th commit
* fold.valueAt('history', -1); // …and what the run STARTED from
* ```
*/
export declare function keyedFold(source: FoldSourceLike | undefined): KeyedFold;