File Modification Management — Industry Analysis and OpenProgram Design#
This document describes how OpenProgram tracks and rolls back file changes made by the agent: the two industry approaches, the four layers OpenProgram runs, and how they coordinate. Related:
agent-worktree.md,overview.md(entity layer),git-as-entity-memory.md. Code:store/snapshot/checkpoint/,store/shadow_git/,store/snapshot/read_tracking.py,agent/internals/_revert.py,worktree/.
1. Two Approaches: Snapshot vs Sandbox#
For managing file modifications, AI coding agents split into two approaches across the industry:
| Snapshot | Sandbox | |
|---|---|---|
| Approach | The agent operates directly on the user's real files, recording the original state before modification (a backup file or a git commit); if something breaks, it restores from the backup | The agent operates on a copy inside an isolated environment (container/VM); if something breaks, the entire environment is discarded and the host is unaffected |
| Rollback | Restore files from backup | Discard the container/environment |
| Pros | Zero startup latency; the user operates on files directly; good interactive experience | Naturally full coverage (including bash); secure isolation; no side-effect leakage |
| Cons | Hard to track file changes made by bash (unless git is used as a fallback or a unified checkpoint entry is used) | Startup latency; complex environment setup; high resource consumption |
| Best for | Local interactive development | Unattended batch tasks, execution of untrusted code |
It is not an either/or choice. Leading frameworks are already moving toward running both side by side — snapshots address the day-to-day rollback experience (second-level recovery), and sandboxes address security isolation (full bash coverage).
2. Industry Comparison#
2.1 Full Landscape Comparison#
| Framework | Snapshot mechanism | Sandbox mechanism | bash coverage | Independent of user git |
|---|---|---|---|---|
| Claude Code | per-response snapshot, /rewind to roll back |
/sandbox restricts bash write scope |
Snapshot doesn't cover it; sandbox restricts write scope | Yes |
| Cursor | per-edit checkpoint | Seatbelt/Landlock/seccomp system-level sandbox | Covered for agent operations | Yes |
| Hermes | shadow git checkpoint, unified checkpoint before every tool execution | None | Yes (unified entry, including bash) | Yes (shadow store) |
| Aider | automatic git commit on every edit | None | Yes (git naturally covers everything) | No (pollutes user git) |
| opencode | git tree object snapshot | None | Yes (git fallback) | No |
| OpenHands | event-sourcing with replay support | Docker container (primary mode) | Full coverage inside the sandbox | N/A |
| SWE-agent | None | Docker sandbox (SWEEnv) | Full coverage inside the sandbox | N/A |
| Devin | None | Cloud ephemeral sandbox | Full coverage inside the sandbox | N/A |
| OpenClaw | No built-in snapshot (local mode) | Docker/Podman container | Full coverage inside the sandbox | N/A |
2.2 Three Ways to Implement Snapshots#
There are three concrete approaches to snapshot mechanisms in the industry:
| Method | Approach | Representative | bash coverage | Independent of user git |
|---|---|---|---|---|
| File copy | shutil.copy2 to a backup directory before modifying a file |
Claude Code, Cursor | No (can only back up known files) | Yes |
| Git commit | git commit to the user's repository after each edit |
Aider, opencode | Yes (git tracks everything) | No (pollutes user git) |
| Shadow git | Store snapshots as git tree/commit objects in an independent directory | Hermes | Yes (triggered by the unified entry) | Yes |
The three can coexist — they solve different problems:
- File copy: the fastest undo (no dependency on git; works for non-git projects too)
- Git commit: permanent history (queryable via
git log, but pollutes user git) - Shadow git: balances diff traceability with not touching user git
2.3 Terminology#
| Term | Meaning |
|---|---|
| Checkpoint | The industry-standard term (Claude Code, Cursor, and Hermes all use it), referring to a snapshot saved before modifying a file. |
| bash coverage | Whether, when the agent modifies files through the bash tool (sed -i, > file, rm, etc.), those changes can be tracked and rolled back. This is the core challenge for the snapshot camp — edit tools (write/edit) know exactly which file was changed, but bash does not. |
| Independent of user git | Whether the backup mechanism uses its own storage and avoids polluting the user's git history. Aider does git commit directly in the user's repository, so git log gets flooded with AI auto-commits; Hermes and Claude Code use independent storage, keeping the user's git history clean. |
| Shadow git | Uses git's tree/commit mechanism to store snapshots, but in an independent directory (such as ~/.hermes/checkpoints/), without touching the user's .git. Balances git's tracking capability (diff, single-file recovery) with not polluting the user's history. |
| Unified entry trigger | The key design in Hermes: rather than triggering the checkpoint inside each edit tool, it triggers uniformly at the execution entry of all tools. This way a checkpoint is also taken before bash runs, naturally covering bash's blind spot. |
| System-level sandbox | Uses OS kernel mechanisms (Seatbelt / Landlock / seccomp / bubblewrap) to restrict a process's file and network access scope. The process still runs on the host, but is restricted in what it can do. Millisecond-level startup. |
| Container sandbox | Runs the agent inside a Docker/Podman container. Full isolation — operations inside the container don't affect the host. After completion, the output is extracted via a git patch or file mount. |
| Git worktree | Uses git worktree to create an independent copy of the working directory. The agent operates in the copy; if it works out, merge back to the main line; if it goes wrong, discard it. Isolates only files, not processes or the network. |
3. OpenProgram's Approach#
3.1 Design Principles#
- Don't touch user git. Neither Claude Code nor Hermes writes commits into the user's repository; this is an industry consensus.
- Unified entry trigger. Following Hermes, the checkpoint is triggered uniformly before all tool executions, covering bash.
3.2 The Four-Layer Mechanism#
Each of the four layers handles one concern, with no overlap. Removing any one of them leaves a capability gap.
┌─── read-before-edit (concurrency guard, the gate in front of all writes) ───┐
│ before the agent writes: never read / disk changed after read → reject, make it re-read │
└───────────────────────────┬──────────────────────────┘
▼
┌──────────────── Snapshot ────────────────┐ ┌──────────── Sandbox ─────────────┐
│ │ │ │
│ ① Checkpoint ② Shadow git │ │ ③ Worktree ④ System-level sandbox │
│ "rollback" "history" │ │ "file isolation" "permission restriction" │
│ turn-level, temporary independent store, persistent │ │ independent copy restricts bash scope │
│ doesn't touch user git doesn't touch user git │ │ agent enters explicitly config toggle │
│ always on, automatic on by default, automatic │ │ on demand workspace-write default │
│ │ │ │
└──────────────────────────────────────────────────┘ └─────────────────────────────────────────┘
| ① Checkpoint | ② Shadow git | ③ Worktree | ④ System-level sandbox | |
|---|---|---|---|---|
| Approach | Snapshot | Snapshot | Sandbox | Sandbox |
| What it solves | Roll back when something breaks | Permanent history + diff traceability | Discard the copy when something breaks | Restrict what bash can touch |
| Mechanism | Full file copy | git tree/commit objects, stored in ~/.openprogram/shadow-git/<project-hash>/ |
git worktree isolated branch |
OS kernel restrictions (Seatbelt / bubblewrap) |
| Scope | A single turn | Accumulated across the whole session | A stretch of experimental work | The entire session |
| Persistence | Temporary (GC, cap of 100 turns) | Permanent (independent git history) | Until merge / discard | In effect during the session |
| Touches user git? | Never | Never | Uses an independent worktree, only merges back to the main line | Never |
| Trigger | Unified entry (before all tool executions) | End of turn (auto-commits this turn's changes) | Agent explicitly calls worktree_create |
Config toggle |
| bash coverage | Yes (unified entry trigger) | Yes (end-of-turn commit includes bash changes) | N/A (inside the isolated environment) | Yes (kernel-level interception) |
| Default | Always on | On by default | On demand | workspace-write |
| Code | store/snapshot/checkpoint/ |
store/shadow_git/ |
worktree/ |
sandbox/ |
| Rollback entry | /rewind |
/rewind integration |
worktree_discard |
N/A (preventive, no rollback needed) |
3.3 Division of Labor Between ① Checkpoint and ② Shadow git#
Both run simultaneously; it is not an either/or choice:
| Dimension | ① Checkpoint | ② Shadow git |
|---|---|---|
| Speed | Fastest (direct file overwrite) | Fairly fast (git checkout) |
| diff capability | None (only the original file copy) | Yes (git diff, git log, single-file recovery) |
| Non-git projects | Usable | Usable (the shadow store ships its own git, no dependency on the user's repository) |
| GC | Yes (cap of 100 turns) | No GC needed (git compresses naturally) |
| Primary use | First choice for a fast undo | Permanent history traceability, diff comparison, precise single-file recovery |
/rewind integrates the two on rollback: it first restores files from the checkpoint (fastest), and the shadow git records remain queryable.
4. Unified Entry Trigger — How bash Is Covered#
4.1 Implementation#
The three edit tools — write / edit / apply_patch — each call checkpoint_before_edit internally for a precise single-file backup.
bash coverage sits in _execute_tool_calls (agent_loop.py, the single entry for all tools):
#### agent_loop.py — inside _execute_tool_calls
pre_snapshot = _snapshot_cwd(tool_call.name) # returns None for non-bash-like tools
result = tool.execute(...) # write/edit already have a precise backup internally
_checkpoint_changed_files(tool_call.name, pre_snapshot) # compare, supplement a checkpoint for changed files
_snapshot_cwd(tool_name): only takes effect for bash-like tools (_BASH_LIKE_TOOLS). It scans the
working directory recursively (using the worktree path when a worktree is active), records
{path: (mtime_ns, size)}, and copies each file's pre-execution content into a temporary staging
directory — otherwise, backing up from the original path after the command has run would already store
the modified content. Files larger than _STAGE_MAX_BYTES skip staging.
_checkpoint_changed_files: compares the before/after snapshots and checkpoints added/modified files
from their staging copies.
5. Concurrency Guard: read-before-edit (the Front Gate)#
store/snapshot/read_tracking.py. This is the safety foundation of the whole mechanism: the agent never blindly overwrites a file that the user changed but the agent hasn't read since. Every entry that lands in ① checkpoint and ② shadow git is therefore a clean agent change, and rollback cannot damage the user's own edits.
It follows Claude Code's Edit/Write contract:
readrecords the baseline — when reading a file, record its fingerprint(mtime_ns, size, sha1).- Verify before writing —
edit/write overwriting an existing file/apply_patch Updatecompares before writing:- Never read (
NEVER_READ) → reject, prompt to read first. - Read but the disk changed (
STALE, modified by user/linter/another process) → reject, prompt to re-read. The change is not written to disk.
- Never read (
- New files are skipped —
write a new file/apply_patch Adddoesn't require reading first; record the baseline after writing. - A successful write refreshes the baseline — the same file can be modified again without re-reading.
It uses a content hash rather than just mtime: when the user types fast, a change may land in the same mtime tick, and looking only at the timestamp would miss it. The session is resolved via the _store ContextVar; outside of a turn (unit tests / standalone calls) the whole guard is a no-op (UNTRACKED → allow).
6. Implementation Details#
6.1 Coordination Rules#
Rule A: When a worktree is active, the shadow git commit yields
shadow_git.commit_turn_changes first checks find_active_for_session(sid):
- Active worktree exists → skip (the agent's changes are in the worktree copy; committing the original directory is wrong/empty).
- None → proceed as usual.
Rule B: /rewind = restore from checkpoint + keep shadow git queryable
When /rewind reverts:
- Restore files from the checkpoint (the fastest path).
- The shadow git history is not rolled back — it stays queryable, so the user can diff to see what the agent changed.
- gitignored files / non-git folders: the checkpoint is the only fallback.
There's no need to decide between git reset vs revert; the rollback logic stays simple.
6.2 Full Lifecycle#
Take, as an example, a session bound to a real project directory:
session start
│
├─ [turn N starts]
│
├─ agent wants to edit a.py
│ [front gate] read-before-edit verifies a.py freshness (never read / already changed → reject)
│ [unified entry] checkpoint(turn=N, a.py) ← copy old content into <session>/checkpoints/N/ before editing
│ ↓ actually write a.py → refresh read baseline
│
├─ agent runs bash "sed -i 's/old/new/' b.py"
│ [unified entry] record the hash of the working directory's file state
│ ↓ actually run bash
│ [unified entry] compare hashes, b.py changed → supplement checkpoint(turn=N, b.py)
│
├─ [turn N ends]
│ · session-git commit (conversation DAG) ← always happens
│ · ② shadow git commit: ← on by default
│ - active worktree? → skip (Rule A)
│ - otherwise commit all file changes of this turn to the shadow store
│ · GC: gc_evict_old(session) ← delete old checkpoints beyond the cap
│
├─ user clicks ↩ rewind or types /rewind
│ → Rule B: restore files from checkpoint, keep shadow git queryable
│
└─ ……
6.3 Releasing Checkpoints#
Checkpoints are stored in <session>/checkpoints/<turn_id>/, released as follows:
| Trigger | Implementation |
|---|---|
| GC (soft cap of 100 turns) | gc_evict_old is called by the dispatcher at the end of each turn, deleting the oldest over-cap turns by mtime |
| Session deletion | Deleted along with the entire session repo when it is deleted |
Note: a checkpoint is a full file copy (
shutil.copy2), deliberately not a hardlink (the agent'sopen(w)truncates the inode, and a shared hardlink would lose the original content). Disk cost is linear in files×turns, capped by the GC limit.
6.4 Shadow git Storage#
Storage location: ~/.openprogram/shadow-git/<project-hash>/
- One shadow git store per project directory (distinguished by path hash)
- Doesn't touch the user's
.git, fully independent - Auto-commits all file changes of a turn at the end of the turn
- Supports
git diff,git log, single-file recovery - No GC needed (git compresses objects naturally)
6.5 The Relationship Among the Four#
The four layers are independent and solve different problems:
| Rollback | History | File isolation | Permission restriction | |
|---|---|---|---|---|
| ① Checkpoint | ✅ | |||
| ② Shadow git | ✅ | |||
| ③ Worktree | ✅ | |||
| ④ System-level sandbox | ✅ |
Coordination points:
- Checkpoint ↔ Shadow git: run simultaneously, coordinated uniformly by
undo(Rule B). Checkpoint handles fast recovery, Shadow git handles permanent history. - Worktree ↔ Shadow git: Rule A, Shadow git yields when a Worktree is active.
- System-level sandbox: fully orthogonal to the other three layers — it restricts what bash can touch, without affecting the operation of snapshots and file isolation.
6.6 Ad-hoc (Default Project) Sessions#
For casual chats not bound to a real directory:
- ① Checkpoint: as usual.
- ② Shadow git: as usual (the shadow store doesn't depend on the user's repository).
- ③ Worktree: not applicable (no source repo).
- ④ System-level sandbox: as usual (restricting bash scope is independent of project binding).
7. User Decision Matrix#
| I want | Configuration | I get |
|---|---|---|
| Like Claude Code, the undo key is enough | Default | ① Checkpoint + ② Shadow git + ④ system-level sandbox |
| To see what the agent changed (diff) | Default | ② Shadow git provides git diff / git log; ④ remains enabled |
| No extra storage at all | Turn off shadow git | Only ① Checkpoint |
| The agent makes a high-risk large change without messing up the working tree | (agent does it itself) worktree_create |
③ Isolation, merge if it works / discard if it fails |
| Restrict bash writes to the workspace and block configured credentials and network | Default workspace-write |
④ host-native process restriction |
| The safest mode | Worktree + system-level sandbox | ③ + ④ file isolation + permission restriction |
8. Usage Guide#
8.1 Web (webui)#
Rollback operations:
- Every user message has a ↩ button in its top-right corner ("Rewind to here") — after clicking it:
- The message's text returns to the input box (re-editable)
- This message and all subsequent conversation are removed from the interface
- Files are restored to their state before this message (via checkpoint)
- The old conversation is preserved in the DAG as a history branch, not lost
- Type
/rewindin the chat box — lists the recent rollback points (up to 10), each showing a summary and timestamp - Type
/rewind Nin the chat box — roll back to the N-th rollback point (N chosen from the list)
Sandbox:
- The system-level sandbox starts in
workspace-write: writes stay inside the workspace and configured writable roots, configured credential paths are unreadable, and network access is disabled. - Type
/sandboxin the chat box to toggle betweenworkspace-writeand an explicitdanger-full-access.
8.2 CLI#
/rewind— list rollback points/rewind N— roll back to the N-th point/sandbox— toggle the sandbox
8.3 TUI Terminal Interface#
/rewind,/rewind N— same as the CLI/sandbox— same as the CLI
8.4 Features That Take Effect Automatically (No Manual Action Needed)#
These features run automatically in the background, and the user doesn't need to do anything:
| Feature | When it triggers | What it does |
|---|---|---|
| Checkpoint | Before each tool execution | Automatically backs up the files about to be modified (including bash) |
| Shadow git | At the end of each turn | Automatically commits this turn's file changes to the independent git store |
| read-before-edit | Before each file write | Automatically checks whether the file was modified externally, preventing overwrites |
8.5 Tools Available to the Agent#
The only tools the agent (the LLM) can call are the worktree series, used for file isolation:
| Tool | Function |
|---|---|
worktree_create |
Create an independent copy of the working directory |
worktree_merge |
Merge the copy's changes back into the main directory |
worktree_discard |
Discard the copy |
Checkpoint, Shadow git, and the sandbox are all automatically running low-level mechanisms, not exposed as tools. The user interacts with them through the /rewind and /sandbox commands.
9. Sandbox Isolation — ③ Worktree + ④ System-level Sandbox#
The three mechanisms below have different execution environments and operational requirements:
| Solution | Representative framework | Isolation level | Startup latency | Implementation technology | Best for |
|---|---|---|---|---|---|
| Git worktree | Our ③, Claude Code --worktree |
Files only (independent copy), no process/network isolation | Second-level | git worktree add |
High-risk code changes |
| System-level sandbox | Our ④, Claude Code /sandbox, Cursor |
File system + network (process-level restriction) | Millisecond-level | Seatbelt / bubblewrap / Landlock | Local interaction, restricting bash scope |
| Container sandbox | OpenHands / SWE-agent / Devin | Container namespaces plus explicitly mounted host paths | 30–60 seconds | Docker / Podman | Reproducible isolated Linux environments |
9.1 ③ Worktree — File Isolation#
The agent calls worktree_create to create an independent copy of the working directory; if it works out, worktree_merge; if it goes wrong, worktree_discard.
Limitation: it isolates only files, not processes or the network — bash can still rm -rf /, read ~/.ssh/, and access the network. It protects against breaking the code, not against bash acting outside its intended scope.
9.2 ④ System-level Sandbox — Permission Restriction#
Uses OS kernel mechanisms to restrict what the bash process can do:
- File system: reads are broadly available except configured
deny_readpaths; writes stay inside cwd and configured writable roots - Network: disabled by default
- Implementation: Seatbelt (sandbox-exec) on macOS, bubblewrap on Linux
- Code:
openprogram/sandbox/__init__.py(resolve_policy+wrap_command),backend/local.py(_invocationintegration) - Command:
/sandboxtoggle (CLI_cli_chat/handlers.py+ webuiws_actions/chat.py)
This is the selected OpenProgram runtime boundary because commands use the host's actual Git, Python, Conda, npm, and compiler installations. It is implemented separately for macOS and Linux. Unsupported platforms refuse sandboxed execution by default; Docker is not an automatic fallback.
9.3 The Relationship Between ③ and ④#
The two solve different problems and can be combined:
- Using ③ alone: changes happen in a copy, but bash is unrestricted
- Using ④ alone: changes happen in the original directory, but bash is scope-restricted
- Combined: changes happen in a copy and bash is also restricted by the host-native policy
9.4 Optional Container Backend (Not in the Current Scope)#
OpenProgram does not require Docker for unattended execution. Authority and pairing decide which senders may enter the agent; the sandbox limits side effects made by an admitted agent while preserving the host development environment. Docker may be added later as an explicitly selected second backend when a concrete product requirement needs a separate Linux environment. It is not implemented now and will not replace or automatically back up the native sandbox.
Appendix: Implementation Status#
All four layers — ① Checkpoint, ② Shadow git, ③ Worktree, ④ System-level sandbox — are implemented, along with the read-before-edit guard and the /rewind and /sandbox commands across webui, CLI, and TUI.
Separate work:
| Item | Description |
|---|---|
| UI indication of the current session's "main rollback path" | Backend is ready, frontend pending |
| Optional container backend | Deliberately outside the current sandbox scope; reconsider only for a concrete separate-environment requirement |