- docs/modules/git-worktree-sandbox.md: new module guide for GitWorktreeSandbox (PR #5998) covering execute/apply phases, non-git fallback, conflict handling, and context hydration integration - docs/reference/sandbox.md: add Strategy Selection section and Git Worktree Sandbox section documenting execute/apply phases and non-git fallback - docs/reference/plan_apply.md: add Git Worktree Merge-Based Apply section with CLI output panels and fallback behaviour; update intro to reference PR #5998 - docs/reference/context_tiers.md: add Context Tier Hydration section documenting ContextTierHydrator (PR #4219), hydration algorithm, exclusion rules, configuration parameters, and LLMExecuteActor integration - mkdocs.yml: add Git Worktree Sandbox to Modules nav
6.0 KiB
Sandbox Infrastructure
Overview
The sandbox infrastructure provides resource isolation during plan execution. Each sandbox creates an isolated environment where a plan can read and write to a resource without affecting the original until changes are explicitly committed.
Sandbox Strategies
| Strategy | Class | Description |
|---|---|---|
none |
NoSandbox |
No isolation; writes go directly to the original |
copy_on_write |
CopyOnWriteSandbox |
Filesystem copy for isolation |
git_worktree |
GitWorktreeSandbox |
Git worktree for git repositories |
Strategy Selection
The sandbox strategy is chosen automatically based on the resource type:
- Git repositories (
git-checkoutresources):git_worktreestrategy is used. The plan's changes are committed to an isolated branchcleveragents/plan-<plan_id>inside a git worktree, keeping the working tree clean until apply. - Non-git directories:
copy_on_writestrategy is used. The resource directory is copied to a temporary location; changes are written there and copied back on apply. - No isolation needed:
nonestrategy writes directly to the resource.
Git Worktree Sandbox
The GitWorktreeSandbox provides full git-native isolation for plan execution
on git-checkout resources. It was introduced in v3.5.0 (PR #5998) to replace
flat shutil.copy2 apply with a proper git merge workflow.
Execute Phase
- A git worktree is created at a temporary path for the plan's linked git-checkout resource.
- A new branch
cleveragents/plan-<plan_id>is checked out inside the worktree. - LLM-generated file output is written to the worktree directory.
- Changes are committed to the worktree branch — no merge yet.
- The worktree path is stored as a sandbox reference for the apply phase.
Apply Phase
- The worktree branch is merged into the project's current branch via
git merge --no-ff. - The CLI displays spec-aligned output panels:
- Apply Summary: Plan ID, artifact count, insertions/deletions, project name, applied-at timestamp.
- Sandbox Cleanup: worktree removed, branch merged to main.
- Next Steps: review
git diff, commit changes. - Footer:
✓ OK Changes applied
- The worktree is removed after a successful merge.
Non-Git Fallback
If the linked resource is not a git repository, the sandbox falls back to
the copy_on_write strategy: files are copied from the sandbox directory
to the project directory using shutil.copy2. Path traversal guards
prevent writes outside the project root. Protected directories (.git,
.cleveragents, node_modules) are skipped.
Sandbox Lifecycle
PENDING -> CREATED -> ACTIVE -> COMMITTED -> CLEANED_UP
| |
| +-> CLEANED_UP
|
+-> ROLLED_BACK -> ACTIVE
|
+-> ERRORED -> CLEANED_UP
Checkpoint and Rollback Hooks
Purpose
Checkpoint hooks preserve sandbox state at key points during plan
execute/apply flows. When a failure occurs, the CheckpointManager
can restore a sandbox to a previously captured checkpoint.
SandboxCheckpoint Model
A frozen Pydantic model capturing a point-in-time snapshot:
| Field | Type | Description |
|---|---|---|
checkpoint_id |
str |
ULID identifier |
sandbox_id |
str |
Sandbox that was checkpointed |
plan_id |
str |
Plan owning the sandbox |
phase |
str |
Lifecycle phase (pre_execute, post_execute, pre_apply) |
created_at |
datetime |
When captured |
metadata |
dict[str, str] |
Key-value metadata (status, reason, etc.) |
snapshot_path |
str |
Path to the snapshot directory |
CheckpointManager API
mgr = CheckpointManager()
# Create a snapshot before execute
cp = mgr.create_checkpoint(sandbox, plan_id, "pre_execute", {})
# Create a snapshot after successful execute
cp2 = mgr.create_checkpoint(sandbox, plan_id, "post_execute", {"status": "success"})
# Rollback on failure
success = mgr.rollback_to(cp)
# List all checkpoints for a sandbox
checkpoints = mgr.list_checkpoints(sandbox.sandbox_id)
# Delete a checkpoint
mgr.delete_checkpoint(cp.checkpoint_id)
Checkpoint Lifecycle
-
Pre-execute checkpoint: Created before the execute phase starts. Captures the sandbox state so that a failed execution can be rolled back.
-
Post-execute checkpoint: Created after a successful execute phase. Preserves the post-execution state before apply begins.
-
Pre-apply checkpoint: Created before the apply phase starts. Allows rollback if the apply fails or encounters merge conflicts.
-
Rollback on failure: When execute or apply fails, the system attempts to restore the sandbox to the most recent checkpoint.
Integration with Plan Executor
The PlanExecutor accepts an optional checkpoint_manager parameter.
When provided, checkpoint hooks are automatically invoked:
- Before
run_execute:create_checkpoint(sandbox, plan_id, "pre_execute") - After successful execute:
create_checkpoint(sandbox, plan_id, "post_execute") - On execute failure:
rollback_to(last_checkpoint)
When no CheckpointManager is injected, all hooks are silently skipped.
Integration with Plan Apply Service
The PlanApplyService also accepts an optional checkpoint_manager:
- Before apply:
create_checkpoint(sandbox, plan_id, "pre_apply") - On apply failure:
rollback_to(last_checkpoint)
Thread Safety
The CheckpointManager is thread-safe. All mutable state is protected
by a reentrant lock (threading.RLock).
Snapshot Storage
Snapshots are stored in temporary directories under the system temp folder. Each snapshot is a full copy of the sandbox working directory at the time of checkpoint creation. Snapshots are cleaned up when:
- A checkpoint is explicitly deleted via
delete_checkpoint() - The checkpoint manager goes out of scope (manual cleanup recommended)