◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/API & SDK
⬡ API & SDK/2026-05-31Advanced

Make Agent Failures Reproducible: Deterministic Replay and Event Sourcing

An autonomous agent that fails at 2 a.m. can't be reproduced by simply running it again. Record every nondeterminism boundary as an append-only event log and replay the failed run deterministically — with working code and operational lessons.

Claude Agent SDK13event sourcingobservability21production111replay

✦ Premium Article

I've been building apps on my own since 2014, and across more than 50 million cumulative downloads, the failures that hurt the most were never the loud crashes. They were the quiet ones: a backend automation that failed exactly once in the middle of the night, and by morning the logs no longer told me why.

Put an autonomous agent into production with the Claude Agent SDK and this problem takes on a sharper shape. Agents depend heavily on external state. The model's output differs slightly every time, the APIs your tools call return different data as hours pass, Date.now() naturally advances, and retry branches shift with jitter and latency. In other words, "just run the failed execution again" will never lead you back to the same failure.

Both of my grandfathers were miyadaiku — traditional shrine carpenters — and I grew up watching the things they assembled last for decades. Anything built to last shares one trait: it can be inspected after the fact. That's the property I care about most when designing agents, and this article is about exactly that — designing for after-the-fact reproduction, through deterministic replay and event sourcing, from both the implementation and operations sides.

Why "run it again" can't reproduce the failure

What blocks reproduction is the set of nondeterminism boundaries scattered through an agent run. The logic itself may be deterministic, but the moments where it touches the outside world receive different values every time. From what I've observed in production, those boundaries collapse into four kinds.

First, model output: even at temperature: 0, exact reproduction isn't guaranteed, and a single different character in a tool-call argument changes the branch taken. Second, tool I/O: external APIs, databases, and files change over time. Third, the clock: any branch based on Date.now() or the date walks down a different path on each run. Fourth, randomness and retries: jittered backoff and sampling wobble between runs.

This is where many teams install an observability stack — metrics, traces, dashboards — and call it done. But that shows you statistics about what happened; it doesn't let you run one specific execution again. Postmortems need the latter. Flip the approach: record every value that passes through those four boundaries into an append-only log, and on re-execution read those values back from the log. The run becomes deterministically reproducible. That's what it means to apply event sourcing to an agent.

Defining the event log schema

Start by issuing a unique runId for each execution, then append an event with a monotonic seq every time that run crosses a boundary. The crucial part is symmetry: in "record" mode the event stores the real value, and in "replay" mode it reads that value back.

// events.ts — append-only event type and a minimal store
export type BoundaryKind = "model" | "tool" | "clock" | "random";
 
export interface AgentEvent {
  runId: string;
  seq: number;            // strictly increasing within a run
  kind: BoundaryKind;
  key: string;            // logical key of the call (e.g. "tool:fetch_orders")
  input: unknown;         // input at record time (used for matching)
  output: unknown;        // the real value the boundary returned
  tokensIn?: number;      // model events only
  tokensOut?: number;
  costUsd?: number;
  ts: string;             // ISO8601 — record time, not the clock's true value
}
 
export interface EventStore {
  append(e: AgentEvent): Promise<void>;
  load(runId: string): Promise<AgentEvent[]>;
}

In production, keeping the EventStore strictly append-only is the lifeline. Rewriting it midstream destroys reproducibility. I run two implementations: JSON Lines (one event per line) in object storage for production, and a plain array in development. Always carry kind and key in the schema — they're the keys the replay step uses to verify "same order, same call."

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦Identify the nondeterminism boundaries (model output, tool I/O, clock, randomness) and record them as an append-only event log, with concrete code
✦A replay harness that reproduces a single failed run deterministically and entirely offline
✦An operational workflow that attributes cost and tokens per run from the same log
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

⬡ API & SDK2026-05-01
Claude API Telemetry on ClickHouse: A Production Guide to Cost, Latency, and Error Analytics
Stream per-request Claude API telemetry into ClickHouse, build sub-second dashboards with materialized views, and detect cost spikes, retry loops, and silent failures with practical SQL recipes.
⬡ API & SDK2026-04-30
Building a Production Claude API Pipeline on Cloudflare Queues: Fault Tolerance, Backpressure, and Cost Control
A practical, code-first walkthrough for routing Claude API calls through Cloudflare Queues — covering producer/consumer code, retry-vs-DLQ branching, priority lanes, and token budgeting for production workloads.
⬡ API & SDK2026-04-29
Production Semantic Cache for Claude API — Similarity Thresholds, Pollution Defense, and What to Track
A production playbook for adding a semantic cache in front of Claude API — threshold tuning, multi-tenant isolation, pollution prevention, fallbacks, how it stacks with Anthropic's prompt caching, and the metrics that actually prove it works.