agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
53 lines (52 loc) • 2.78 kB
TypeScript
/**
* middleware/ledger — INTERNAL. Where a chain's decisions become record.
*
* Pattern: one writer for one committed key.
* Role: core/ layer. `runChain` decides; this file is the only thing
* that commits what it decided and emits the matching event, so
* the three call sites cannot record the same fact three
* different ways.
* Emits: `agentfootprint.middleware.decision`, one per row.
*
* ## Why the original value is committed too
*
* A transform makes the trace and the wire disagree — the model saw one
* string, the run remembers another. Committing the pair, with the
* middleware's name and its `why` beside it, is what turns that
* disagreement from a lie into a record. Every slice taken afterwards can
* then find the moment the value changed and who changed it.
*
* The same reasoning covers the `'after-tool'` moment from the other side. A
* result the model was not allowed to read still HAPPENED — the tool ran, the
* side effect landed — so the refusal row carries the real result. A run that
* dropped it would record an agent that called a tool and got nothing back,
* which is not what occurred.
*
* This is not a redaction layer, and it is not trying to be one. For the
* `'input'` phase the seed stage commits the transformed message, so the
* model and the conversation never hold the original — but this row is NOT
* its only copy: the run's input as passed (`run.entry` payload), a crash
* checkpoint's `originalInput` and a paused
* run's checkpoint (`RunnerPauseOutcome.checkpoint`, which `standingAgent`
* stores for a paused session) keep it too — the full list, and what an app
* must redact, is in `./README.md`, the one place it is maintained. An app
* whose threat model says the original must not survive in the RECORD names
* it in `Agent.create({ redact })`: `middlewareDecisions` (this state key —
* every row's `before` / `after`; the `middleware.decision` events below carry
* neither) is then the placeholder in every record. The checkpoints keep the real
* values — a resume runs on them — so where the app stores a checkpoint, it
* protects it.
*/
import type { MiddlewareDecision } from './types.js';
/** The scope surface this file needs. Structurally a `TypedScope<AgentState>`. */
export interface LedgerScope {
middlewareDecisions?: readonly MiddlewareDecision[];
$emit(name: string, payload?: unknown): void;
}
/**
* Append decision rows to the run's ledger and emit one event per row.
*
* No-op on an empty list, so an agent with no middleware never writes the
* key at all and its commit log is the one it always had.
*/
export declare function recordDecisions(scope: LedgerScope, rows: readonly MiddlewareDecision[]): void;