Status: Phase 1 + 2 implemented — snapshot store, capture seam, the Esc-Esc /
/rewind CLI picker, and the desktop hover-rewind, with the full Claude Code menu:
restore code / conversation / both, fork-from-here, and summarize from / up to
here. Snapshot-based and aligned with Claude Code. An optional git-backed mode is
the remaining (lower-priority) follow-up. Tracks the most requested missing
capability from v1 — an edit safety net / undo.
This document describes rewind snapshots. For the autonomous-run rule about when
the agent should pause and ask the user, see
TASK_CONTRACT.md.
Let a user rewind a session to a previous point and restore code,
conversation, or both — without touching their git history. Aligned with
Claude Code's rewind (Esc-Esc / /rewind), driven identically from the CLI and
the desktop.
Like Claude Code (and v1's checkpoints.ts), checkpoints are file snapshots,
independent of git:
- Zero git pollution — never commits, stages, or touches
.git/. Works in a non-git directory. - Tracks only previewable edit-tool changes —
write_file/edit_file/multi_edit. File moves viamove_filefollow the same workspace permission boundary, but are not yet represented in checkpoint previews.bashside effects are not tracked (no way to know what a shell command touched), exactly as Claude Code. Risky bash is already permission-gated. - Full pre-edit content snapshots (simple; storage bounded by retention, below).
An optional git-backed mode (v1's auto-git-rollback) is a possible Phase 2
for users who want git-level safety; it is explicitly out of scope here.
- One checkpoint per user turn. A checkpoint opens when a turn starts
(
Controller.Send/runTurn), labelled with the user prompt. - Pre-edit snapshot. In
agent.(*Agent).executeOne, before running a tool whoseReadOnly()is false and which implementstool.Previewer, callPreview(args)→diff.Change{Path, Kind, OldText}and record a snapshot of that file into the active checkpoint.tool.Previeweralready exists and the file-writers implement it, so this is one centralized seam — no per-tool code.- Dedup per path per turn: only the first touch is snapshotted (that is the file's turn-start content).
Kind == create(file did not exist) → storeContent = nilso a restore deletes it.modify/delete→ storeOldText.bashhas noPreviewer, so it is naturally excluded — matching the "edit-tools only" contract.
type FileSnap struct {
Path string // workspace-relative
Content *string // nil → file did not exist at the anchor (restore deletes it)
}
type Checkpoint struct {
Turn int // user-message index this anchors (0-based)
Time time.Time
Prompt string // user message text — the picker label
Files []FileSnap // distinct files touched during this turn, turn-start state
}- Sidecar to the session, under
config.SessionDir():<session-id>.ckpt/. It is separate from the message JSONL (agent.Session.Save), so the session format is unchanged. - Persists across sessions — resuming a session re-loads its checkpoints, so rewind works after a restart (Claude Code parity).
- Schema v3 layout: each turn is a directory:
turns/<turn>/meta.jsonplus rawfiles/NNNN.beforepayloads. New captures do not duplicate preimages in the content-addressed blob store. v1/v2 JSON and blobs remain readable for upgrade compatibility; transaction/undo payloads may still use blobs. Each v3 turn also writes a payload-free v2 compatibility marker (turn-<turn>.json). A previous Reasonix version can therefore keep turn numbering monotonic after a downgrade, but cannot restore the v3 file payload represented by that marker. The marker is also the v3 turn's liveness record: if an older reader truncates the marker, a later upgrade ignores the leftover directory instead of resurrecting the future turn. - Retention: keep the newest 100 v3 turn directories by default and remove an expired turn as one directory. Raw v3 preimages also have a soft 1 GiB budget; the current or transaction-protected turn may temporarily exceed it, and older whole turns are removed once they are unprotected. Legacy blobs use the same budget value in their separate compatibility store. Session cleanup removes the whole sidecar.
Checkpoints live on control.Controller, beside SetPlanMode / Compact /
NewSession, so the terminal TUI, the desktop webview, and the HTTP/SSE server
drive rewind identically and none re-implement it.
type RewindScope int // Code | Conversation | Both
func (c *Controller) Checkpoints() []CheckpointMeta
func (c *Controller) PrepareRewind(turn int, scope RewindScope) (RewindPlan, error)
func (c *Controller) CommitRewind(planID string) (RewindResult, error)- Code: for every checkpoint from
turnto the latest, take the earliestFileSnapper path and restore each file to that content (delete ifnil) — i.e. undo all edits made at or afterturn. Path-escape re-checked against the live workspace root. - Conversation: fork a new session at the turn boundary. The parent
transcript is never truncated. See
SESSION_OWNERSHIP.md. - Both: fork first, then restore files. A file conflict keeps the new
branch and reports
partial=true.
A Rewound event (or reuse of a history-replace event) lets every frontend
re-render uniformly.
Esc Escwith an empty composer, or/rewind, opens a picker listing each user turn (time + which files it changed).chat_tuialready tracks the double-Esc timing.- Select a turn → sub-menu:
[code+conversation] [conversation] [code] [cancel]. - On a conversation/both restore, the selected prompt is prefilled into the composer.
- Each user message in the transcript gets a hover rewind control → menu: rewind code / rewind conversation / both / fork-from-here.
- It calls the same prepare/commit rewind API over the Wails binding; the controller's event stream pushes the restored state and React re-renders. No rewind logic in the frontend.
- bash / external side effects (
rm,mv, DB writes, deploys) are not tracked — rewind cannot undo them (Claude Code parity). - External edits between turns: restore compares the current existence, SHA-256, and mode with Reasonix's last after-image. A mismatch is reported as a conflict and is not overwritten.
- Deletions: an edit-tool deletion is restorable (snapshot has the content); a
bash rmis not. - Large files: full snapshots, with a 32 MiB per-file capture limit. The turn-count and soft byte budgets bound retained history; a protected or current turn may temporarily exceed the byte budget.
- Phase 1: snapshot store +
executeOnecapture seam + controller prepare/commit (code/conversation/both) + CLI picker (Esc-Esc +/rewind). - Phase 2: desktop hover-rewind UI; "fork from here"; "summarize from/up to here"; optional git-backed mode.
- Snapshot on
/compactand onNewSessionboundaries? - Whether to expose the 100-turn retention and 1 GiB soft byte limits in
[checkpoints]config.