Here's the failure mode that makes replay dangerous if you get the capture wrong: you reconstruct the prompt from your own logs, replay it, watch the bug disappear, ship the fix — and the bug is still there in production. Your reconstruction quietly dropped a tool definition, or got a message role wrong, and you just replayed your best guess at the request, not the request. The fix worked against a request that never actually happened.
One boundary sees everything
Runback's SDK doesn't log at the call sites in your code. It wraps the model itself, at the Vercel AI SDK's middleware boundary — wrapGenerate's doGeneratehook. Tool definitions have already been merged in by the SDK. System messages have already been concatenated from wherever they came from. Message arrays have already been transformed into the provider's API format. That's the one place, after all of it, where the request exists as a single complete object, right before it's handed to the provider.
doGenerate fires — this is the one point where the request exists as a single, complete object before the provider ever sees it.const dbg = withDebugger(model, {
runName: "my-agent",
input: task,
});
const result = await generateText({
model: dbg.model,
tools: dbg.tools(myTools),
prompt: task,
});That's the whole integration. withDebugger() wraps the model with middleware that captures the request at that boundary and the response, usage, and latency on the way back — before the request reaches the provider and after the response comes back, with nothing reconstructed in between.
So what?
When you edit a message and hit replay, you're changing one field of the actual historical request, not rebuilding an approximation of it from scratch and hoping it matches. The fix you see working in the Replay tab is the fix that actually works in production, because it ran against the same request, not a lookalike. That's also what keeps the capture layer thin enough to be three lines of integration instead of a rewrite of your agent.