← Blog

Debugging an AI agent with time-travel replay

Runback Team··product, debugging

The usual way to debug an agent failure is to re-run the whole thing and hope it breaks the same way twice. That's slow, it costs real API calls, and half the time the bug doesn't even show up again — the model answered differently, or the search result changed. Here's a small, real agent failing, and what it looks like to fix it without any of that.

1 of 4 steps
re-run to test the fix — search, fetch, and compose don't happen again

The agent

research-email-agentis a three-step agent: search the web, read the most relevant result, summarize it into an email. It's instrumented with withDebugger() from @runback/sdk, wrapping the model in about three lines of code. Its task, deliberately: “Research Next.js 16 and email a 3-sentence summary to jordan[at]example.com.”

That address isn't valid — no @, just [at]— and the agent doesn't catch it. It searches, reads, composes the email, calls send_email, and the tool throws:

send_email — thrown error
SMTP 550: invalid recipient address "jordan[at]example.com". Expected a valid email like name@domain.com.
Agent steps: search, fetch, compose, then send fails — edit and replay fixes itsearchweb_searchfetchfetch_urlcomposesummarizesend ✗SMTP 550edit address + replay this step
Three steps succeed; send_emailfails on a malformed address baked into the original task text. Replay re-issues just that one step — search and fetch don't need to run again.

Finding it

Open the run and Runback lands you directly on the step that failed — error-first navigation, not “scroll until something looks red.” The Context tab shows exactly what the model saw before it made the bad call: the full task text, including the malformed address, sitting right there in the user message. Nothing reconstructed after the fact — the literal request payload the model received.

Click the failing tool call and a causal link jumps you straight to the LLM step that requested it, via the shared tool_call_id — no hunting through a flat list to figure out which model call caused which side effect.

Fixing it, without re-running the whole agent

Switch to the Replay tab on that LLM step. The exact captured request is already loaded — system prompt, messages, tools, params. Edit the task text to fix the address, hit Run edited replay, and Runback re-issues that one step against a real model with the correction applied. You see the original response and the new one side by side: same context up to that point, one input changed, compare what's different.

No re-running search and fetch. No guessing whether the fix works — you see it work, on the exact context that failed, in one click.

So what?

This was a fake address in a demo scenario. In production, the same failure mode is a tool call with a malformed argument nobody caught, a context window that silently dropped a field, or a model that ignored an instruction — the kind of bug that's expensive to chase precisely because re-running the whole agent doesn't reliably reproduce it. Replay skips that entirely: you're not hoping the bug happens again, you're re-running the exact request that already produced it.

This exact scenario is seeded as a public, standing demo run — no signup, no setup: runback.dev/runs/demo-email-agent. Open it, click into the failing step, and replay it yourself.