@zarzalejo/agent-receipts
v0.1.0
Published
Verified replay and cryptographic receipts for agent runs — canonical journal, clock-free SHA-256 fingerprints, replay that serves the recorded script and pinpoints divergence at the exact call. In-process, zero infra, framework-agnostic.
Maintainers
Readme
@zarzalejo/agent-receipts
Verified replay and cryptographic receipts for agent runs. In-process, zero infrastructure, zero dependencies, framework-agnostic.
An agent run can only be non-deterministic through its boundary — the effectful calls it makes (a database read, a model call, a write). Journal every crossing and you hold the entire run: same inputs → same result, or divergence pinpointed at the exact call where the script broke.
This is not observability (tracing dashboards) and not durable execution (workflow runtimes). It is the missing third thing: proof. Record a run once; replaying it serves the recorded script without touching the world — no network, no model calls, no cost — and verifies fingerprint against fingerprint. Every record the agent wrote can carry a compact receipt answering "which run produced you?".
record: run ──► boundary calls execute, each crossing journaled
(args digest · response · response digest)
└─► trace + SHA-256 fingerprint (clock excluded)
replay: run ──► boundary calls served FROM the script, nothing executes
└─► same fingerprint = reproducible, proven
or ReplayDivergence { seq, reason } at the exact callInstall
npm i @zarzalejo/agent-receiptsNode ≥ 20 or any modern browser (WebCrypto only).
Quickstart
import { RunHost, buildReceipt } from '@zarzalejo/agent-receipts';
// ── record ──────────────────────────────────────────────────────────
const host = new RunHost({
mode: 'record',
runner: 'enrich-row',
subject: { rowId },
grants: ['db-read', 'model', 'db-write'], // nothing is implicit
});
const row = await host.call('db-read', 'getRow', { rowId }, fetchRow);
const out = await host.call('model', 'extract', { prompt }, callModel);
await host.call('db-write', 'patch', { rowId, patch }, writeRow);
const finished = await host.finish({ status: 'patched' });
// finished.trace → full, self-contained, replayable
// finished.fingerprint → SHA-256 over the canonical trace, clock excluded
// the receipt travels WITH the destination record (a column, a property):
const receipt = buildReceipt(finished, { status: 'patched' });
// { v, runId, fp, at, runner, calls: ['db-read.getRow#a1b2c3d4', …], status }
// ── replay (later, anywhere) ────────────────────────────────────────
const replay = new RunHost({ mode: 'replay', trace: finished.trace });
// run the SAME business function against `replay` — boundaries are served
// from the script, nothing executes; finish() verifies the whole run:
// fingerprints match → reproducible, proven
// ReplayDivergence → { seq, reason: 'call-mismatch' |
// 'beyond-script' | 'under-consumed' |
// 'output-mismatch' }The two rules the design hangs on
- The fingerprint excludes the clock.
runId,startedAtand per-callmsnever enter the digest — two identical runs hash identically. Canonical form sorts JSON keys recursively; JS key order is not a contract an audit fingerprint should hang from. - Denied by default. A capability that was not granted is never
executed —
CapabilityDeniedfires before touching the world, not after. Failures are journaled too: a recorded failure replays as the same failure, because the error is part of the script.
Divergence taxonomy
| reason | meaning |
|---|---|
| call-mismatch | same seq, different call or different arguments — the run went off-script here |
| beyond-script | the run asks for more crossings than were recorded |
| under-consumed | the run took a shortcut the original did not take |
| output-mismatch | same crossings, different output — the pure derivation changed between record and replay |
What this is not
- Not tracing/evals (LangSmith, Langfuse, Braintrust, Weave): those observe; none re-execute serving the recorded script with fingerprint verification.
- Not durable execution (Temporal, DBOS): those replay workflows and require adopting their runtime; this is one small in-process class.
- Not a cassette library: VCR-style tools stub HTTP; they do not verify a canonical fingerprint, flag divergence at the exact call, or emit receipts for the records the agent wrote.
Status
0.1.x — API small on purpose (RunHost, buildReceipt, canonical
helpers, two error classes). Extracted from a production pipeline where
every video-processing run is recorded, receipted into the destination
row, and replay-verified at zero cost.
