9.5 KiB
adr_number, title, status_history, tier, authors, superseded_by, related_adrs, acceptance
| adr_number | title | status_history | tier | authors | superseded_by | related_adrs | acceptance | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 15 | Sandbox and Checkpoint |
|
3 |
|
null |
|
|
Context
Plans modify project resources — source code, configuration files, databases — during the Execute phase. These modifications must be isolated from the real project until they are reviewed and approved during Apply. If execution fails partway through, the system must be able to roll back to a known-good state. Different resource types require different isolation mechanisms (git worktrees for source code, filesystem copies for non-git directories, transaction rollback for databases).
Decision Drivers
- Execute-phase modifications must be isolated from real project resources until reviewed and approved during Apply
- Different resource types require different isolation mechanisms (git worktrees, filesystem copies, database transactions)
- Fine-grained rollback to individual tool invocations is needed rather than reverting entire phases
- Sandboxing overhead should be avoided for resources the plan never touches (lazy over eager)
- Concurrent plans must not interfere with each other's sandboxed state
Decision
CleverAgents uses per-plan sandboxes with lazy sandboxing (resources sandboxed only when first accessed) and five isolation strategies selected per resource type. Checkpointing is integrated at the tool level — each tool declares whether it supports checkpoints, and the system creates checkpoints before write operations to enable fine-grained rollback.
Design
Sandbox Principles
- Lazy sandboxing: Resources are sandboxed only when first accessed during execution, not eagerly at plan start. This avoids unnecessary copies for resources that the plan may not touch.
- Per-plan sandboxes: Each plan (including child plans) has its own sandbox. No sandbox sharing between plans.
- Resource-defined strategy: The sandbox strategy is defined by the resource type, with a global default override via
sandbox.strategy(default:git_worktree). - Cleanup behavior: Cleanup before exit; abandoned sandboxes cleaned on next run; completed sandboxes based on retention policy (
sandbox.cleanup:on_apply,on_terminal, ormanual).
Five Isolation Strategies
git_worktree — Creates a git worktree with a plan-specific branch. Rollback via git reset/checkout. The default strategy for git-checkout resources. Provides full isolation while sharing the repository's object store.
filesystem_copy (copy_on_write) — Copies the target directory. Rollback via restoring from the original snapshot. Used for fs-directory resources.
overlay — Overlay filesystem mount. Writes go to an overlay layer; rollback by discarding the overlay. Used for fs-mount resources. More efficient than full copy for large directories.
transaction_rollback — Database transaction with rollback capability. Used for custom database resource types. Changes within the transaction are invisible to other connections until committed.
none — No isolation. Used for read-only resources or resources that cannot be sandboxed (e.g., external APIs). Requires require_sandbox: false in the automation profile.
Multi-Resource Sandboxing
When a plan operates on multiple resources:
- Each resource gets its own sandbox with its own strategy.
- Apply commits each resource's sandbox separately.
- Partial apply is possible — some resources may be committed while others are not.
- Shared resources (linked to multiple projects in a multi-project plan) get a single sandbox.
Checkpointing
Checkpointing operates at the tool level. Each tool declares:
checkpointable: Boolean — whether the tool supports checkpointing.checkpoint_scope: The granularity of the checkpoint —file,transaction,commit, orsnapshot.rollback_mechanism: How rollback is performed.
Per-source checkpoint mechanisms:
| Source | Mechanism |
|---|---|
| Built-in file ops | Git commit in worktree |
| Built-in git ops | Git reflog |
| MCP tools | MCP checkpoint protocol (if supported) |
| Agent Skills | Snapshot-based |
| Custom tools | Tool-declared mechanism |
Checkpoint creation is automatic before write operations when sandbox.checkpoint.enabled is true (default). Maximum checkpoints per plan: sandbox.checkpoint.max-per-plan (default: 50). When exceeded, oldest checkpoints are pruned (keeping the first and most recent).
Execution as Transactions
Each execution step is treated as a transaction: on success, a checkpoint is committed; on failure, the step is rolled back to the previous checkpoint. This enables fine-grained recovery without re-executing the entire plan.
Checkpoint Configuration
| Key | Default | Description |
|---|---|---|
sandbox.checkpoint.enabled |
true |
Global checkpoint enable/disable |
sandbox.checkpoint.dir |
<data-dir>/checkpoints |
Storage directory (subdirectory per plan ULID) |
sandbox.checkpoint.max-per-plan |
50 |
Maximum checkpoints retained |
Plans can require checkpoints via the require_checkpoints flag. If enabled, only tools with checkpointable: true are permitted.
Constraints
- All resource modifications during Execute must happen in a sandbox. Direct modification of real resources is prohibited (unless
sandbox.strategy: noneis explicitly configured and the automation profile allows it). - Lazy sandboxing means sandbox creation is deferred until first access. The system must handle the case where sandbox creation fails at access time.
- Checkpoint pruning must keep the first and most recent checkpoints. Intermediate checkpoints are pruned oldest-first.
- Each plan's sandbox is independent. Cross-plan sandbox access is not permitted.
- The
require_checkpointsplan flag, when enabled, rejects tools that declarecheckpointable: false.
Consequences
Positive
- Sandboxed execution ensures that incomplete or failed work never contaminates real project resources.
- Lazy sandboxing avoids unnecessary resource copying for resources the plan does not touch.
- Tool-level checkpointing enables fine-grained rollback without re-executing entire phases.
- Five strategies cover the range of resource types from git repos to databases to APIs.
- Per-plan sandboxes prevent interference between concurrent plans.
Negative
- Sandbox management adds complexity and resource overhead (disk space for copies/worktrees, checkpoint storage).
- Lazy sandboxing introduces a potential failure point mid-execution when the sandbox is first created.
- The
nonestrategy creates a gap in isolation guarantees for resources that cannot be sandboxed.
Risks
- Disk space exhaustion from sandbox copies and checkpoints, especially for large repositories or many concurrent plans.
- Git worktree creation may fail if the repository is in a conflicting state (e.g., another worktree exists for the same branch).
- Overlay filesystem may not be available on all platforms (requires kernel support).
Alternatives Considered
Eager sandboxing (sandbox all resources at plan start) — Simpler but wastes resources. A plan targeting a project with 10 linked resources may only modify 2 of them. Lazy sandboxing avoids copying the other 8.
No checkpointing (full rollback only) — Simpler but coarse-grained. If step 47 of 50 fails, the entire sandbox must be reverted instead of just step 47.
Compliance
- Isolation tests: Integration tests verify that sandbox modifications are invisible to the real project until Apply.
- Strategy tests: Each of the five strategies has tests for creation, modification, rollback, and cleanup.
- Checkpoint tests: Tests verify checkpoint creation, rollback to specific checkpoints, and pruning behavior.
- Lazy sandbox tests: Tests verify that sandbox creation is deferred until first resource access and handles creation failures gracefully.
- Cleanup tests: Tests verify that sandbox cleanup occurs at the correct lifecycle point (on_apply, on_terminal, manual).