You come back to yesterday’s session expecting your workspace to look like the agent left it. It won’t, and that surprise is where we start. The sessions page opens with the sentence this whole module hangs on: sessions persist the conversation, not the filesystem. An agent’s work leaves two trails, kept in two different places by two different systems. The conversation, every prompt, answer, and tool call, is written to a transcript the SDK can pick back up later. The files the agent edited sit in your workspace, and the transcript knows nothing about restoring them. This module gives the workbench a handle on each trail: resume and fork for the conversation, checkpoints and undo for the files.
flowchart TD subgraph conv["the conversation"] direction TB a["transcript on disk,<br/>one line per message"] --> b["resume: continue<br/>under the same id"] a --> c["fork: continue<br/>under a new id"] end subgraph files["the files"] direction TB d["checkpoint before<br/>each tracked edit"] --> e["undo: rewind to<br/>before a prompt"] end conv ~~~ files
Continue, resume, fork
Every session frame the workbench has sent since module 15 carried a sessionId. That id is a key into the transcript store, pass it back as the resume option on a later query() and the conversation continues where it left off, on a later day, from a different process. Set forkSession: true beside it and the same history continues under a fresh id instead, so the original stays where it was. That is “try a different answer without losing this one” as an API option. Record 0042 covers how the server lists what can be resumed, through the SDK’s own listing functions rather than by reading the transcript files’ directory layout, which is storage, not contract.
Next the workbench UI grows a sessions panel. Each entry shows when the session last moved and the opening words of its first prompt. Choose one and the composer arms so the next prompt goes out with resume; a second button of equal weight forks instead. One detail the client renders truthfully because the demo caught it mattering: the id you asked to fork is not the id you end up in, and only the returned session frame knows the new one. Record 0081 shows both on the turn, under different labels.
Two options from the sessions page the workbench does not use, but you should know they exist. persistSession: false runs a session that writes no transcript at all, useful for stateless one-shot work. A SessionStore adapter mirrors transcript lines to storage you own, which is how a session created on one host resumes on another host running from a matching working directory. The session-storage page documents its own caveats rather than hiding them, and they are worth reading in its words: mirror writes are best-effort, a fork is not a byte copy, and reading messages back returns the post-compaction chain. Durable, monitored, not a database-grade guarantee.
Undo, and the trick it took
Checkpoints are the file trail. With enableFileCheckpointing: true, the SDK snapshots a file before modifying it, and rewindFiles(userMessageId) puts every tracked file back the way it was at that message. The workbench maps that onto one button per turn: “Undo to before this prompt”. The client sends undo with the prompt’s own id, never an SDK message id, the server keeps the mapping and the protocol stays free of a second id namespace.
The server half of that button is a lesson in reading type declarations before you write code. rewindFiles is a control request: it travels inside a live session’s input stream. But the workbench’s prompts are one-shot; by the time anyone clicks undo, the query that took the checkpoint has ended. Record 0047 works the problem. The checkpoint snapshots outlive the session (the fork documentation says forks start without them, which is only worth saying if originals keep them), so the server opens a second, promptless, streaming-input session resumed on the same conversation, delivers the one rewindFiles request, and closes. If the real CLI refuses a promptless resume, the failure is an error frame naming the reason, not a hang. That sentence is in the record because it has not been proven live: this box has no API key, so the entire real path, resume, fork, and rewind, is asserted from the SDK’s type declarations with line citations and from the fake, and the records say so plainly rather than rounding “typed and reasoned” up to “ran”.
Watch the round trip
git checkout module-18
WORKBENCH_FAKE_SDK=1 npm run dev
Time to see both trails move. The sessions panel lists past runs, send a prompt, continue it, fork it, undo it. The demo script does the whole loop as frames, and this transcript is from a real run of it:
[recv] {"type":"session","id":"demo-...","sessionId":"fake-session-0000","model":"fake-model"}
[sessions] 1 resumable
[sessions] fake-session-0000 2026-08-30T16:13:53.742Z "What files are in this directory?"
[recv] {"type":"session","id":"demo-...-resume","sessionId":"fake-session-0000","model":"fake-model"}
[recv] {"type":"session","id":"demo-...-fork","sessionId":"fake-session-fork-0001","model":"fake-model"}
[fork] new sessionId fake-session-fork-0001
[recv] {"type":"undone","id":"demo-...-undo","promptId":"demo-..."}
[undone] prompt demo-... was rewound
Resume comes back under the same id, fork under a new one, and undo answers undone. In fake mode that last frame is bookkeeping, not restoration: record 0048 is explicit that the fake’s undo is a seam that still refuses sessions it never recorded, so a bookkeeping bug fails a test while no file moves on disk. The one place a rewind genuinely restores files is a live session, the first thing to try when you run this track with an API key.
What a checkpoint cannot bring back
Before you trust undo for anything serious, here is what it cannot restore, condensed from the sessions page’s own limitations list:
Tracked: Write, Edit, NotebookEdit
Not tracked: Bash mutations (sed -i, echo >, ...)
subagent edits, except a foreground context: fork skill
Scope: same session only; file content only, so a directory
created, moved, or deleted is not undone; local files only
A checkpoint is a convenient rewind for the agent’s own edits, not a transaction. When the work is correctness-critical, wrap the whole session in something that does roll back everything: run the agent in a temporary git worktree, let checkpoints handle the small rewinds, and make the worktree’s diff-and-commit boundary the real undo. The workbench stays on checkpoints because its workspace is a sandbox. An app whose workspace is somebody’s repository should not.
Same ladder as always, four steps, each one building on the last:
- Run it. Fake mode, three prompts, then continue one and fork another from the panel. Run
node scripts/demo-client.mjs —sessionsfromserver/and match its frames to what the panel showed. - Read one file.
server/src/sessions.js, the seam between the frames and the SDK, with records 0042 and 0049 beside it. - Change one line and see it. Start the server with
WORKBENCH_SESSION_LIMIT=2and watch the panel truncate to the two newest; then read where the limit is enforced and say why the client sorts the entries again anyway (record 0079 answers). - Build. The undone marker names the prompt it rewound. Make it also name the session, using only fields the frames already carry, and prove it with
npm test -w clientstill green.
You wire up undo, click it after a turn where the agent wrote three files with Write, watch all three revert, and conclude you have rollback. Then a turn comes where the agent appends a line with echo >> config.txt, creates a folder, and edits a file inside it. Undo that turn and only the edit reverts. The appended line stays, because Bash mutations are never checkpointed. The folder stays, because checkpoints track file content, not directories. Nothing errors. The undone frame arrives, the marker renders, and your workspace is now a mixture of two points in time that no one asked for.
The fix is to stop calling it rollback. A checkpoint rewinds tracked edits, the table above is the whole list of what is tracked, and the sessions page, not this one, is where that list stays current. For work where a half-rewind is worse than none, put the session in a temporary worktree and throw the worktree away. That boundary catches everything, including the echo.
The two-trails split travels everywhere. Every agent system separates conversation history from workspace state, and confusing them causes the same class of bug in all of them. Resume-by-id and fork are common shapes too. What does not travel is the mechanics: transcript files under a config directory, SessionStore, control requests inside a streaming session, and the exact list of tracked tools are this SDK’s own. The limitation list is the kind of fact that changes between versions, check the sessions page against your installed pin.
Check yourself
- A teammate resumes yesterday’s session and is surprised that the files the agent wrote yesterday are not in the fresh clone they resumed from. Which sentence of this module explains their surprise, and what would have to be true for the resume itself to work from that clone?
- Fork a session, then undo a prompt that ran before the fork, in the original. What does the fork documentation quoted in record 0047 imply about whether the forked session could have done the same rewind?
- The agent ran
Write notes/a.md,sed -ionnotes/b.md, andEditonnotes/c.mdin one turn. After “Undo to before this prompt” succeeds, state each file’s condition.