Time travel debugging: replaying yesterday's failure
A customer's ticket failed on a Tuesday. I reproduced it exactly on my M2 Ultra on Thursday, using their real state. No guessing, no shrugging about how it works on my machine. The tool that made that possible is the same checkpoint history most teams set up for crash recovery and then never look at again.
the graph remembers every step
Every time a node runs against a checkpointer, it writes a checkpoint: a full snapshot of state at that instant. Calling get_state_history on a thread hands you that whole chain, newest to oldest. Most teams treat it as insurance for resume-after-crash. It's also a time machine, and the debugging workflow that comes with it changed how I handle production failures.
Tuesday, forked onto Thursday
When that ticket failed, I pulled its thread out of the Postgres checkpointer, walked the history, and found the checkpoint right before the node that threw. Then I forked from it, passing that checkpoint's id back in the config, and replayed forward locally. The failure reproduced on the first try, because I wasn't approximating the input, I was resuming the actual captured state. From there it's a normal bug. I could even edit the state at the fork point, patch the malformed field the upstream node had written, and replay forward to confirm the fix carried the thread all the way to the end. Reproduction went from an hour of reconstructing context to about five minutes of walking history.
Forking a thread rewinds your graph's memory, not the world it was talking to.
the duplicate email that taught me the limits
Time travel has two edges that will cut you, and I found both the hard way. The first: side effects re-fire. The very first thread I forked to debug ran forward through the send node, and that node sends real email, so a customer got a second copy of a refund-approved message, two days late, for a ticket they had long forgotten. Now I stub every side-effecting tool before I replay, or fork onto a branch that short-circuits them. Replay is not a dry run unless you make it one.
the world moved on
The second edge is quieter, and you can't stub your way around it. Your state is frozen at Tuesday, but any node that fetches live data, like an order status or a current balance, reads Thursday's world. The order the customer asked about got refunded on Wednesday. The account they were locked out of got reset. So the replay diverges from the original run not because your code changed but because reality did, and the further back you fork, the wider the two histories drift. Time travel replays your memory faithfully and lies about everything external, which is usually fine for a logic bug and useless for anything that hinged on the outside world at the time.
Both limits share one root: a checkpoint captures your graph, never its environment. Hold that in mind and forking a thread becomes the fastest debugging tool the checkpointer gives you, especially paired with node-level tracing to find the checkpoint worth forking. Trace to find where it broke, fork to make it break again on demand.