返回 DeepSeek-Reasonix
CHECKPOINTS.md
根目录 / docs / CHECKPOINTS.md
1 # Design: Checkpoints & Rewind
2
3 Status: **Phase 1 + 2 implemented** — snapshot store, capture seam, the Esc-Esc /
4 `/rewind` CLI picker, and the desktop hover-rewind, with the full Claude Code menu:
5 restore code / conversation / both, fork-from-here, and summarize from / up to
6 here. Snapshot-based and aligned with Claude Code. An optional git-backed mode is
7 the remaining (lower-priority) follow-up. Tracks the most requested missing
8 capability from v1 — an edit safety net / undo.
9
10 This document describes rewind snapshots. For the autonomous-run rule about when
11 the agent should pause and ask the user, see
12 [`TASK_CONTRACT.md`](TASK_CONTRACT.md).
13
14 ## Goal
15
16 Let a user rewind a session to a previous point and restore **code**,
17 **conversation**, or **both** — without touching their git history. Aligned with
18 Claude Code's rewind (Esc-Esc / `/rewind`), driven identically from the CLI and
19 the desktop.
20
21 ## Mechanism: file snapshots, not git
22
23 Like Claude Code (and v1's `checkpoints.ts`), checkpoints are **file snapshots**,
24 independent of git:
25
26 - **Zero git pollution** — never commits, stages, or touches `.git/`. Works in a
27 non-git directory.
28 - **Tracks only previewable edit-tool changes** — `write_file` / `edit_file` / `multi_edit`.
29 File moves via `move_file` follow the same workspace permission boundary, but
30 are not yet represented in checkpoint previews.
31 `bash` side effects are **not** tracked (no way to know what a shell command
32 touched), exactly as Claude Code. Risky bash is already permission-gated.
33 - Full pre-edit content snapshots (simple; storage bounded by retention, below).
34
35 An optional **git-backed mode** (v1's `auto-git-rollback`) is a possible Phase 2
36 for users who want git-level safety; it is explicitly out of scope here.
37
38 ## Anchors & capture
39
40 - **One checkpoint per user turn.** A checkpoint opens when a turn starts
41 (`Controller.Send` / `runTurn`), labelled with the user prompt.
42 - **Pre-edit snapshot.** In `agent.(*Agent).executeOne`, before running a tool
43 whose `ReadOnly()` is false and which implements `tool.Previewer`, call
44 `Preview(args)` → `diff.Change{Path, Kind, OldText}` and record a snapshot of
45 that file into the active checkpoint. `tool.Previewer` already exists and the
46 file-writers implement it, so this is one centralized seam — no per-tool code.
47 - Dedup per path per turn: only the **first** touch is snapshotted (that is the
48 file's turn-start content).
49 - `Kind == create` (file did not exist) → store `Content = nil` so a restore
50 *deletes* it. `modify`/`delete` → store `OldText`.
51 - `bash` has no `Previewer`, so it is naturally excluded — matching the
52 "edit-tools only" contract.
53
54 ## Data model
55
56 ```go
57 type FileSnap struct {
58 Path string // workspace-relative
59 Content *string // nil → file did not exist at the anchor (restore deletes it)
60 }
61
62 type Checkpoint struct {
63 Turn int // user-message index this anchors (0-based)
64 Time time.Time
65 Prompt string // user message text — the picker label
66 Files []FileSnap // distinct files touched during this turn, turn-start state
67 }
68 ```
69
70 ## Storage
71
72 - **Sidecar to the session**, under `config.SessionDir()`: `<session-id>.ckpt/`
73 with one JSON per checkpoint plus a small index (v1's layout — cheap delete, a
74 corrupt snapshot only loses itself). Kept separate from the message JSONL
75 (`agent.Session.Save`) so the session format is unchanged.
76 - **Persists across sessions** — resuming a session re-loads its checkpoints, so
77 rewind works after a restart (Claude Code parity).
78 - **Retention**: prune with the session (default ~30 days, configurable), to bound
79 disk from full-content snapshots.
80
81 ## Controller API (the one seam both frontends drive)
82
83 Checkpoints live on `control.Controller`, beside `SetPlanMode` / `Compact` /
84 `NewSession`, so the terminal TUI, the desktop webview, and the HTTP/SSE server
85 drive rewind identically and none re-implement it.
86
87 ```go
88 type RewindScope int // Code | Conversation | Both
89
90 func (c *Controller) Checkpoints() []CheckpointMeta // for the picker
91 func (c *Controller) Rewind(turn int, scope RewindScope) error
92 ```
93
94 - **Code**: for every checkpoint from `turn` to the latest, take the earliest
95 `FileSnap` per path and restore each file to that content (delete if `nil`) —
96 i.e. undo all edits made at or after `turn`. Path-escape re-checked against the
97 live workspace root.
98 - **Conversation**: truncate `Session.Messages` to just before turn `turn`'s user
99 message, re-`Save`, and emit the truncated history as events so the frontend
100 re-renders. The turn's prompt is restored into the composer for re-send/edit
101 (Claude Code behavior).
102 - **Both**: code + conversation.
103
104 A `Rewound` event (or reuse of a history-replace event) lets every frontend
105 re-render uniformly.
106
107 ## CLI UX (aligned with Claude Code)
108
109 - **`Esc Esc`** with an empty composer, or **`/rewind`**, opens a picker listing
110 each user turn (time + which files it changed). `chat_tui` already tracks the
111 double-Esc timing.
112 - Select a turn → sub-menu: **`[code+conversation] [conversation] [code] [cancel]`**.
113 - On a conversation/both restore, the selected prompt is prefilled into the
114 composer.
115
116 ## Desktop UX (aligned with the VS Code extension)
117
118 - Each user message in the transcript gets a hover **rewind** control → menu:
119 **rewind code / rewind conversation / both / fork-from-here**.
120 - It calls the same `controller.Rewind` over the Wails binding; the controller's
121 event stream pushes the restored state and React re-renders. No rewind logic in
122 the frontend.
123
124 ## Non-goals & edge cases
125
126 - **bash / external side effects** (`rm`, `mv`, DB writes, deploys) are not
127 tracked — rewind cannot undo them (Claude Code parity).
128 - **External edits between turns**: a snapshot holds the file's turn-start
129 content, so restoring overwrites edits made outside reasonix in the meantime.
130 - **Deletions**: an edit-tool deletion is restorable (snapshot has the content); a
131 `bash rm` is not.
132 - **Large files**: full snapshots — retention cleanup bounds disk; revisit dedup
133 (content-addressed snapshots) if it becomes a problem.
134
135 ## Phasing
136
137 1. **Phase 1**: snapshot store + `executeOne` capture seam + `Controller.Rewind`
138 (code/conversation/both) + CLI picker (Esc-Esc + `/rewind`).
139 2. **Phase 2**: desktop hover-rewind UI; "fork from here"; "summarize from/up to
140 here"; optional git-backed mode.
141
142 ## Open questions
143
144 - Snapshot on `/compact` and on `NewSession` boundaries?
145 - Default retention window and whether to expose it in `[checkpoints]` config.
146 - Content-addressed dedup vs one-file-per-snapshot from the start.
147
147 lines MARKDOWN