diff --git a/docs/advanced-usage/available-tools/apply-patch.md b/docs/advanced-usage/available-tools/apply-patch.md
index 9e60c56d..5a1a54c0 100644
--- a/docs/advanced-usage/available-tools/apply-patch.md
+++ b/docs/advanced-usage/available-tools/apply-patch.md
@@ -108,3 +108,10 @@ The patch format uses custom headers followed by unified diff blocks:
- [`apply_diff`](/advanced-usage/available-tools/apply-diff): Use for single-file search-and-replace with fuzzy matching
- `apply_patch`: Use for multi-file operations with unified diff format
- [`write_to_file`](/advanced-usage/available-tools/write-to-file): Use for creating entire new files
+
+---
+
+## Write safety
+
+This tool publishes through the guarded write path: the write is checked against the file's observed version, and a rejected write (for example, the file changed after it was read) returns a re-read-then-retry error the agent can act on. See [File-Write Safety](/features/file-write-safety).
+
diff --git a/docs/advanced-usage/available-tools/edit-file.md b/docs/advanced-usage/available-tools/edit-file.md
index bfce915c..a17df325 100644
--- a/docs/advanced-usage/available-tools/edit-file.md
+++ b/docs/advanced-usage/available-tools/edit-file.md
@@ -89,3 +89,10 @@ When the `edit_file` tool is invoked, it follows this process:
- [`apply_diff`](/advanced-usage/available-tools/apply-diff): Use for precise, context-aware edits with fuzzy matching
These are different implementations of search-and-replace with varying capabilities.
+
+---
+
+## Write safety
+
+This tool publishes through the guarded write path: the write is checked against the file's observed version, and a rejected write (for example, the file changed after it was read) returns a re-read-then-retry error the agent can act on. See [File-Write Safety](/features/file-write-safety).
+
diff --git a/docs/advanced-usage/available-tools/edit.md b/docs/advanced-usage/available-tools/edit.md
index 108aaa4d..f09eb4a9 100644
--- a/docs/advanced-usage/available-tools/edit.md
+++ b/docs/advanced-usage/available-tools/edit.md
@@ -89,3 +89,10 @@ When the `edit` tool is invoked, it follows this process:
:::info Deprecated Alias
`SearchAndReplaceTool` is a deprecated internal alias for `EditTool`. They are the same tool.
:::
+
+---
+
+## Write safety
+
+This tool publishes through the guarded write path: the write is checked against the file's observed version, and a rejected write (for example, the file changed after it was read) returns a re-read-then-retry error the agent can act on. See [File-Write Safety](/features/file-write-safety).
+
diff --git a/docs/advanced-usage/available-tools/write-to-file.md b/docs/advanced-usage/available-tools/write-to-file.md
index 10b0be2f..dd2b639a 100644
--- a/docs/advanced-usage/available-tools/write-to-file.md
+++ b/docs/advanced-usage/available-tools/write-to-file.md
@@ -198,3 +198,10 @@ export function debounce(func, delay) {
18
```
+
+---
+
+## Write safety
+
+This tool publishes through the guarded write path: the write is checked against the file's observed version, and a rejected write (for example, the file changed after it was read) returns a re-read-then-retry error the agent can act on. See [File-Write Safety](/features/file-write-safety).
+
diff --git a/docs/basic-usage/the-chat-interface.md b/docs/basic-usage/the-chat-interface.md
index 05e4f062..ca35c852 100644
--- a/docs/basic-usage/the-chat-interface.md
+++ b/docs/basic-usage/the-chat-interface.md
@@ -69,6 +69,10 @@ For more productivity tips, check out our [Tips & Tricks](/tips-and-tricks) guid
* **Copying Text:** You can copy text from the chat history by selecting it and using the standard copy command (Ctrl/Cmd + C). Some elements, like code blocks, have a dedicated "Copy" button.
* **Expanding and Collapsing**: Click on a message to expand or collapse it.
+### Change Cards
+
+When a step changes files, a change card appears in the chat listing each file with its additions and removals. The card offers **Rollback step** (restore every file the step touched) and a per-file rollback button, each confirmed before anything is written. Cards are powered by per-write checkpoints and the per-task change journal — see [File-Write Safety](/features/file-write-safety).
+
### Mermaid Diagrams
Zoo Code renders Mermaid diagrams directly in chat. Diagram backgrounds, labels, lines, and exported PNGs follow your active IDE color theme, including light and high-contrast themes. If you change themes while a conversation is open, existing diagrams update automatically.
diff --git a/docs/features/checkpoints.mdx b/docs/features/checkpoints.mdx
index e380d7eb..ed25c141 100644
--- a/docs/features/checkpoints.mdx
+++ b/docs/features/checkpoints.mdx
@@ -54,6 +54,7 @@ Access checkpoint settings in Zoo Code settings under the "Checkpoints" section:
1. Open Settings by clicking the gear icon → Checkpoints
2. Configure checkpoint behavior:
- Check or uncheck the "Enable automatic checkpoints" checkbox
+ - Check or uncheck "Checkpoint after each file write" (on by default) — takes a snapshot after every successful file write by the agent, which powers the per-step change cards and rollback. See [File-Write Safety](/features/file-write-safety)
- Adjust the "Checkpoint initialization timeout" (10-60 seconds, default: 30s)
diff --git a/docs/features/file-write-safety.md b/docs/features/file-write-safety.md
new file mode 100644
index 00000000..1dc64622
--- /dev/null
+++ b/docs/features/file-write-safety.md
@@ -0,0 +1,127 @@
+---
+description: How Zoo Code guards every file the agent writes — version-checked writes that fail loudly instead of silently clobbering, per-write checkpoints, per-step change cards, and one-click rollback.
+keywords:
+ - file write safety
+ - guarded writes
+ - version guard
+ - atomic writes
+ - write rejection
+ - stale version
+ - change cards
+ - rollback
+ - per-write checkpoints
+ - change journal
+---
+
+# File-Write Safety
+
+When the agent writes a file, Zoo Code checks the on-disk state against what the agent actually saw before the write is published. If the file changed underneath — by you, another agent instance, or a background process — the write is **rejected loudly** with a message that tells the agent exactly what to do (re-read the file, then retry) instead of silently overwriting your changes.
+
+Every accepted write is also **atomic** (the file either holds the old content or the new one, never a partial write) and is recorded: a checkpoint snapshot per write and a one-line journal entry per change, which power the per-step change cards and the rollback controls in the chat.
+
+---
+
+## How a write is guarded
+
+Three pieces work together:
+
+- **Version token** — a fingerprint of the on-disk file state: device, inode, size, and the file's modification and change times (in nanoseconds). Two reads of an unchanged file produce the same token; any change produces a different one.
+- **Observation** — reading a file records its version token for the task. A write is only allowed to proceed against a file the agent has observed (read) first.
+- **Guarded publish** — the write is re-checked against the observed token at publish time:
+
+ | Write kind | Guard |
+ |---|---|
+ | Create a new file | Fails if the file already exists and was not read first |
+ | Update (overwrite) | Publishes only if the on-disk token still matches the observed one |
+ | Edit (literal replace / patch) | Requires a prior observation, then publishes only on a token match |
+
+Writes to the same file are ordered through a per-path queue, so concurrent tool calls cannot interleave half-written states. The actual bytes are published by the atomic write primitive: write to a temporary file in the same directory, then rename over the target — a crash mid-write leaves either the old or the new content, never a truncated file.
+
+---
+
+## When a write is rejected
+
+A rejected write does not happen silently. The tool returns an error to the agent that names the problem and the fix:
+
+- File not read yet -- read the file, then retry. — the agent tried to edit a file it never read. It reads the file, then retries the same edit.
+- File already exists at (path) and was not read before this write -- read the file first, then retry. — a create-style write targeted an existing file the agent had not read.
+- Stale version -- the file changed since you read it (expected (token), current (token)); re-read the file, then retry. — the file was modified after the agent read it. The write is refused and the agent re-reads, so your newer content becomes the base for the retry.
+- File was deleted after it was read -- the version recorded at read time (token) no longer exists; re-read the file, then retry. — the file was removed after the read. The agent re-reads (creating a fresh observation) before retrying.
+
+Real I/O failures (permission denied, disk error) are surfaced as I/O errors, not guard verdicts — the guard only rejects on a state mismatch.
+
+Because a rejection is a normal tool error, the agent recovers on its own: it re-reads the file, re-bases its edit on the current content, and retries. You do not need to intervene unless you want to.
+
+---
+
+## Per-write checkpoints
+
+With checkpoints enabled, Zoo Code snapshots the workspace **after every successful agent write**, in addition to the task-start baseline. The snapshots live in the same shadow Git store that powers the existing [Checkpoints](/features/checkpoints) feature — no new storage.
+
+The behavior is controlled by the **Checkpoint after each file write** setting (on by default) in Settings → Checkpoints:
+
+- **On** (default): each accepted write produces its own checkpoint, so a change card can roll back to the state *before* that specific write.
+- **Off**: checkpoints are only taken at the existing coarser cadence.
+
+---
+
+## Per-step change cards
+
+After a step in which the agent changed files, a **change card** appears in the chat:
+
+
+
+*The summary card lists each changed file with its additions and removals, plus a step-level rollback button.*
+
+- **Summary** (default): file names with added/removed line counts.
+- **Full**: the unified diff for each file, shown inline in the card. Selected with the **Show full diff in change cards** setting:
+
+
+
+The card data comes from the per-task change journal (changes.jsonl), which records one line per written file: the file path, the operation (created or modified), and the checkpoint the write produced. The journal is appended one line at a time; if a line is ever torn, the loader repairs the tail and keeps the readable prefix.
+
+---
+
+## Rollback
+
+The change card offers two rollback scopes:
+
+- **Per file** — the rollback button on a file row restores that one file to the content it had at the step's checkpoint. The other files of the step are untouched.
+- **Per step** — the **Rollback step** button restores every file the step touched.
+
+Each action asks for confirmation before anything is written:
+
+
+
+Rollback reuses the existing checkpoint restore service — the same shadow Git store and the same restore primitive the Checkpoints UI uses — so a rolled-back file is restored exactly to the checkpointed content. Failures are reported per file in the card: a rollback that cannot restore one file says so next to that file instead of failing the whole step.
+
+Rollback requires checkpoints to be enabled for the task. If they are not, the card shows a message instead of acting.
+
+---
+
+## Settings
+
+| Setting | Default | Effect |
+|---|---|---|
+| **Checkpoint after each file write** | On | Takes a checkpoint after every successful agent write (see [Checkpoints](/features/checkpoints)). |
+| **Show full diff in change cards** | Off (summary) | Full: show the inline unified diff in change cards. Summary: file list with line counts. |
+
+
+
+Both settings are regular persisted settings: they round-trip through the settings export/import and apply to new tasks once saved.
+
+---
+
+## Default write path
+
+The file-write safety guards apply to every file the agent writes — write-to-file, edit-file, search-replace, and apply-diff all publish through the guarded path. The diff-approval behavior that pairs with it: when the agent edits a file, the change is approved in the chat (the change card above) rather than opening a separate diff editor, so the write happens while your focus stays in the conversation.
+
+---
+
+## Where the data lives
+
+| Data | Location |
+|---|---|
+| Checkpoint snapshots (per write + task start) | The task's shadow Git checkpoint store (same as the existing Checkpoints feature) |
+| Change journal | changes.jsonl inside the task's storage directory |
+| Version tokens | Computed on demand from file statistics; nothing is stored |
diff --git a/docs/features/index.md b/docs/features/index.md
index 8e0aefb1..af380120 100644
--- a/docs/features/index.md
+++ b/docs/features/index.md
@@ -32,6 +32,7 @@ Discover the powerful features that make Zoo Code your ultimate AI-powered codin
### Workflow Management
- [**Task Todo List**](/features/task-todo-list) - Track progress on complex multi-step tasks
- [**Checkpoints**](/features/checkpoints) - Save and restore conversation states
+- [**File-Write Safety**](/features/file-write-safety) - Version-guarded writes, per-write checkpoints, change cards, and rollback
- [**Boomerang Tasks**](/features/boomerang-tasks) - Reusable task templates
- [**Custom Modes**](/features/custom-modes) - Create specialized AI assistants for specific workflows
diff --git a/sidebars.ts b/sidebars.ts
index 33c5b5cd..92660f71 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -27,6 +27,7 @@ const sidebars: SidebarsConfig = {
"features/auto-approving-actions",
"features/boomerang-tasks",
"features/checkpoints",
+ "features/file-write-safety",
"features/code-actions",
"features/codebase-indexing",
"features/custom-instructions",
diff --git a/static/img/file-write-safety/change-card-full.png b/static/img/file-write-safety/change-card-full.png
new file mode 100644
index 00000000..825fa0e3
Binary files /dev/null and b/static/img/file-write-safety/change-card-full.png differ
diff --git a/static/img/file-write-safety/change-card-summary.png b/static/img/file-write-safety/change-card-summary.png
new file mode 100644
index 00000000..3064ec92
Binary files /dev/null and b/static/img/file-write-safety/change-card-summary.png differ
diff --git a/static/img/file-write-safety/rollback-state.png b/static/img/file-write-safety/rollback-state.png
new file mode 100644
index 00000000..4bfeea25
Binary files /dev/null and b/static/img/file-write-safety/rollback-state.png differ
diff --git a/static/img/file-write-safety/settings-checkpoints.png b/static/img/file-write-safety/settings-checkpoints.png
new file mode 100644
index 00000000..83d309db
Binary files /dev/null and b/static/img/file-write-safety/settings-checkpoints.png differ