10 min read

H25
Playbook · Agent UX & Human Interaction

Rewind, Branching & Side Effects

Edit-and-regenerate is a rollback promise your tools cannot keep, and the checkpoint restores your filesystem rather than the world.

Claude Code's checkpoints only capture edits made through its own file-editing tools, so a git push, a sent email or a row written through a shell command survives the rewind that appeared to erase it — and the user, who saw the transcript rewind, believes otherwise. The control is still worth shipping; what has to change is the claim it makes. Name the boundary of what you can restore in the rewind UI itself, put your branch points before irreversible steps rather than after them, and treat rewinding the conversation and rewinding the world as two separate controls, because they already are.

Step 1The affordance came from chat, where it was honest, and stopped being honest the moment the model got tools.

Edit a message, regenerate, branch the thread: in a pure text assistant these are exactly what they look like. Nothing happened except tokens, so re-deriving from an earlier point is a complete and truthful undo. The user's model of the interaction — the conversation is a document I can revise — matches the implementation precisely.

Give the same loop a tool belt and the mental model survives while the implementation underneath it does not. The transcript is now a log of things that happened to systems outside the transcript. Rewinding it edits the log. The classic shape of the resulting bug report is a user who rewound to before "send the summary to the customer", edited their instruction, re-ran, and now has two emails in the customer's inbox and one in the transcript — and who describes this, correctly from where they are standing, as the product sending an email it was told not to send.

This is not an argument for removing the control. Rewind is one of the highest-value affordances in agent UX precisely because it lets you lower friction everywhere else, which is the case undo and reversibility makes at length. It is an argument that the control is making a claim, that the claim is currently unqualified, and that the gap between the claim and the implementation is where your incidents live.

Step 2Your checkpoint boundary is your tool surface — and users will assume it is the world.

The concrete version is worth stating because the leading implementation is explicit about it and people still miss it. Claude Code creates a checkpoint at every user prompt, keeps them across sessions, cleans them up after thirty days, and lets you restore code, conversation, or both. It tracks edits made through its own file-editing tools. Changes made another way — a shell command that writes a file, anything that leaves the machine — are outside the checkpoint, and there is no sub-prompt granularity, so the unit of rewind is "one of your turns", not "one of the agent's actions".

Every one of those is a reasonable engineering decision. Together they define a boundary that is legible to the implementer and invisible to the user, who sees "restore checkpoint" and reads "put things back". Write down your own boundary explicitly, in these four terms, before you design the screen:

  • Which effects are captured. Usually: writes through your own tool implementations, because those are the ones you can wrap. Usually not: anything executed through a general-purpose escape hatch like a shell, which is the hole in every implementation that has one.
  • At what granularity. Per user turn is the common choice and it is coarser than users expect — a single turn can contain fifteen agent actions, and restoring it discards all fifteen.
  • For how long. A retention window makes rewind unavailable precisely when it is most wanted, which is when someone noticed the problem a week later.
  • Restoring what, independently. Code, conversation, both — see STEP 4; this is the one that is routinely collapsed into a single button and should not be.

The escape hatch is the real hole. An agent that can run shell commands can produce any effect your tool wrappers do not see, and the more capable your agent is, the more of its real work flows through that path. If you ship checkpoints and a shell in the same product, the honest UI says so — "restores files Claude edited; does not undo commands Claude ran" is one line, it fits, and it converts a future incident into a known limitation.

Step 3Sort tools into three buckets, because only one of them is actually rewindable.

The engineering question is not "can we snapshot state" but "what kind of reversal does each tool admit", and there are exactly three answers. Tag every tool in your catalogue with one at registration time; the tag is what the rewind screen reads.

  • Rewindable. You hold the prior value and can restore it unilaterally: a file write, a draft, an in-app record you own. Snapshot before, restore after, done. This is the only bucket where the word "undo" is literally true.
  • Compensatable. You cannot restore the prior state, but you can issue a second action that cancels the first: refund a charge, delete the row you created, send a correction, close the ticket you opened. The system never returns to the earlier state — there is a refund in the ledger — but the user-visible outcome is restored. Compensation needs a handle returned by the original call, which means it has to be designed into the tool, not bolted on at rewind time.
  • Irreversible. Nothing you can send makes it un-happen: a sent message, a published post, a payment that cleared, a deleted object with no backup, a notification someone already read. The only control that works here is the one that happens before — a gate, per approval and confirmation UX.

The design consequence falls straight out of the taxonomy: put the branch point before the first irreversible action, not at the end of the turn. A run that reads six sources, drafts a reply, and sends it should offer its rewind target at "drafted, not sent", because that is the last state a rewind can genuinely restore. Checkpointing on turn boundaries instead means the only offer you can make spans the send, and the offer is therefore partly false.

This also gives you a cheap, honest fallback for the irreversible bucket when no gate is wanted: a hold window. Queue the irreversible action for thirty seconds with a visible cancel, and during that window it is rewindable for free, because it has not happened yet.

Step 4Rewinding the transcript and rewinding the world are different operations, and users want them separately.

Collapsing these into one button is the most common design error here, and the split is not a power-user nicety — each of the four combinations is a real request someone makes weekly.

  • Both. "That whole direction was wrong." The default, and the only one most products ship.
  • World only, keep the conversation. "The code it wrote is bad but the discussion that got us here was good." Discards the artifacts, keeps the reasoning, lets the user re-steer from a context the agent still has. This is the most frequently wanted and most frequently missing option.
  • Conversation only, keep the world. "Keep what it built, forget how we argued about it." Usually wanted because the context window is full of a dead end that is now actively misleading the model — a compaction request wearing a rewind costume.
  • Neither, just fork. "Try a second approach from here without losing the first." Covered in STEP 5, and the one with the sharpest trap.

Keeping the world while rewinding the conversation has a failure mode worth naming, because it is the mirror image of the one this page opened with: the agent now operates on a filesystem containing changes it has no memory of making. It will re-create a file that already exists, re-run a migration it already applied, or contradict its own earlier edit. If you offer conversation-only rewind, re-ground the agent afterwards — a fresh read of the current state beats any amount of transcript, and this is the same reconciliation obligation that interruption and handoff imposes when a human edits underneath a running agent.

Step 5A branch is not a clean re-roll; it is a retry against a world the other branch already changed.

Forking a conversation gives the user two sibling threads from a shared prefix and strongly implies they are independent experiments. They are not. The two branches share exactly one thing — every external system either of them touched — and that shared thing has no branching semantics at all.

Three specific failures follow, and all three read to the user as the agent being erratic rather than as the UI having lied:

  • Duplicated effects. Branch B re-runs "create the Jira ticket" that branch A already ran. Two tickets, one of them orphaned. The fix is an idempotency key derived from the logical action rather than the branch — the same discipline as idempotency and retries, applied to a UI affordance instead of a network retry.
  • Stale reads in the prefix. Both branches inherit tool results captured before the fork. If branch A then changed the thing branch B is reading about, branch B is reasoning from a snapshot that is now wrong, and nothing in its context says so. Timestamp tool results, and invalidate on re-entry rather than on age.
  • Interleaving. Nothing stops a user running both branches at once, and then two agents with the same goal and the same credentials are writing to the same systems with no coordination — a two-agent system that nobody designed, with the coordination failures that implies. Either serialise branch execution or make the second branch's conflicting writes fail loudly.

The honest framing to put in front of the user is that a fork branches the conversation and not the world. If your product cannot enforce that at the tool layer, the second-best option is to make a branched run start by re-reading, rather than trusting, everything it inherited.

Step 6The rewind screen's job is to show what it will not undo.

Everything above converges on one screen, and its content is the inverse of what these screens usually show. A rewind confirmation that lists what will be restored is a reassurance. A rewind confirmation that lists what will not be restored is a decision aid — it is the only part the user cannot work out for themselves, and the only part that prevents the incident.

Three things on it, in this order:

  • The out-of-scope list, first and unmissable. "This will not undo: 1 email sent to dana@…, 1 commit pushed to main." Derived mechanically from the irreversible and uncompensated tool calls in the span being discarded — which is only possible because of the tagging in STEP 3.
  • Offered compensations as checkboxes. For the compensatable bucket, do not silently reverse and do not silently skip: list them with their reversal action pre-checked and let the user keep one. "Close the ticket it opened" is a choice, and the person rewinding is the one qualified to make it.
  • The restore scope, last. Code, conversation, both — a default, not a question posed before the user knows what is at stake.

Instrument the screen for one number: the rate at which users cancel out of it after reading the out-of-scope list. A high rate is not a UI problem, it is your gates being in the wrong place — people are discovering at rewind time that an irreversible action already happened, which means they should have been asked before it did. Feed that signal back into where the approval gates sit rather than into the copy on the dialog.

Ship this in three passes, in this order, and the first one is the one that matters. One: tag every tool rewindable / compensatable / irreversible and put the out-of-scope list in the rewind dialog — a week of work that converts your worst failure mode into a visible disclosure. Two: split restore into code / conversation / both, and move checkpoint boundaries to just before irreversible actions instead of to turn boundaries. Three: add compensations and idempotency keys so the compensatable bucket is actually reversible and forks stop duplicating effects. Teams usually attempt three first because it is the interesting engineering; it is also the one whose absence users forgive, while they do not forgive a rewind that quietly left an email sent. For what to do when the irreversible thing has already happened and nobody rewound in time, see repairing agent side effects.