- Add docs/modules/git-worktree-sandbox.md: full lifecycle guide for GitWorktreeSandbox including state machine, method reference, error table, and CLI Apply Summary panel documentation (PR #5998) - Add docs/modules/context-tier-hydrator.md: module guide for ContextTierHydrator covering public API, limits/filters, fragment structure, and structured log events (PR #4219, bug #1028) - Update mkdocs.yml nav to include both new module pages - Update README.md highlights with git worktree sandbox, ACMS context tier hydration, and container/devcontainer resource stop features - Update CHANGELOG.md [Unreleased] section with entries for PRs #5998, #4219, #3250, #4197, #4175, #3837
9.5 KiB
Git Worktree Sandbox Module
Package: cleveragents.infrastructure.sandbox.git_worktree
Introduced: v3.9.0 (PR #5998)
The Git Worktree Sandbox provides fully isolated, git-native sandboxing for
git-checkout resources during plan execution and apply phases. Changes are
staged on a dedicated branch in a temporary git worktree and merged back to the
original branch only on a successful plan apply. If the plan is rolled back,
the merge is undone atomically.
For the sandbox protocol and other sandbox strategies, see
docs/development/custom_sandbox_strategy.md.
For the plan lifecycle that invokes sandboxes, see
ADR-006 and
ADR-015.
Purpose
When an actor executes a plan against a git-checkout resource, it must not
write changes directly to the working tree — doing so would corrupt the
repository state if the plan fails or is rolled back. The Git Worktree Sandbox
solves this by:
- Creating a temporary git worktree at a new branch (
cleveragents/plan-<id>) - Routing all actor file writes into the worktree
- Committing changes in the worktree on
plan apply - Merging the sandbox branch back to the original branch
- Cleaning up the worktree and branch after the merge
Non-git resources (e.g. fs-directory) continue to use the copy-on-write
sandbox strategy.
Lifecycle
GitWorktreeSandbox(resource_id, original_path)
│
▼
sandbox.create(plan_id) # PENDING → CREATED
│ Creates worktree at /tmp/ca-sandbox-<plan_id>-XXXX
│ Creates branch cleveragents/plan-<plan_id>
│
▼
sandbox.get_path("src/foo.py") # CREATED/ACTIVE → ACTIVE
│ Returns /tmp/ca-sandbox-.../src/foo.py
│ Actor writes to this path
│
▼
sandbox.commit("msg") # ACTIVE → COMMITTED
│ git add -A (in worktree)
│ git commit (in worktree)
│ git merge cleveragents/plan-<id> (in original repo)
│
▼
sandbox.cleanup() # → CLEANED_UP
git worktree remove --force
git branch -D cleveragents/plan-<id>
git worktree prune
Rollback is possible from ACTIVE or COMMITTED:
sandbox.rollback() # ACTIVE/COMMITTED → ROLLED_BACK
│ If COMMITTED: git reset --hard <pre_merge_commit> (original repo)
│ git reset --hard <base_commit> (worktree)
│ git clean -fd (worktree)
Key Class: GitWorktreeSandbox
from cleveragents.infrastructure.sandbox.git_worktree import GitWorktreeSandbox
sandbox = GitWorktreeSandbox(
resource_id="01HXYZ...",
original_path="/home/user/my-project",
git_timeout=30, # optional, default 30 s
)
Constructor Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
resource_id |
str |
required | ULID of the resource being sandboxed |
original_path |
str |
required | Absolute path to the git repository root |
git_timeout |
int |
30 |
Timeout in seconds for each git command |
Methods
create(plan_id: str) → SandboxContext
Creates the worktree and sandbox branch. Must be called before any other method.
- Creates branch
cleveragents/plan-<sanitised_plan_id>fromHEAD - Creates a temporary directory at
/tmp/ca-sandbox-<plan_id>-XXXX - Returns a
SandboxContextwithsandbox_pathpointing to the worktree
Raises:
ValueError— ifplan_idis emptySandboxStateError— if not inPENDINGstatusSandboxCreationError— if git worktree creation fails (e.g. not a git repo, git command timeout)
get_path(resource_path: str) → str
Translates a resource-relative path to its absolute path inside the worktree.
worktree_path = sandbox.get_path("src/cleveragents/cli/main.py")
# → "/tmp/ca-sandbox-abc123-XXXX/src/cleveragents/cli/main.py"
Raises:
SandboxStateError— if sandbox is not in a usable statusValueError— ifresource_pathcontains..(directory traversal)
commit(message: str | None = None) → CommitResult
Stages all changes in the worktree, commits them, and merges the sandbox branch into the original branch.
result = sandbox.commit("feat: add new feature")
print(result.commit_ref) # git SHA of the sandbox commit
print(result.changed_files) # list of modified files
print(result.added_files) # list of new files
print(result.deleted_files) # list of deleted files
If there are no staged changes, returns a CommitResult with success=True
and empty file lists (no-op commit).
Raises:
SandboxStateError— if not inCREATEDorACTIVEstatusSandboxCommitError— if the git commit or merge fails
rollback() → None
Discards all worktree changes.
- From
ACTIVE: resets the worktree branch to the base commit - From
COMMITTED: additionally resets the original branch to the pre-merge commit, fully undoing the merge
Warning: Rolling back from
COMMITTEDexecutesgit reset --hardon the original branch. If other git worktrees or external processes are tracking the same branch, they will be affected.
Raises:
SandboxStateError— if called in an invalid statusSandboxRollbackError— if the rollback fails
cleanup() → None
Removes the worktree directory and deletes the sandbox branch. Idempotent — safe to call multiple times.
Internally runs:
git worktree remove --force <worktree_path>git branch -D cleveragents/plan-<id>git worktree prune
Falls back to shutil.rmtree if git worktree remove fails.
SandboxContext Metadata
After create(), the sandbox.context.metadata dict contains:
| Key | Description |
|---|---|
strategy |
Always "git_worktree" |
branch |
Sandbox branch name, e.g. cleveragents/plan-abc123 |
original_branch |
Branch that was active before sandboxing |
base_commit |
SHA of the HEAD commit at sandbox creation time |
worktree_path |
Absolute path to the temporary worktree directory |
Status State Machine
PENDING
│ create()
▼
CREATED ──── get_path() ──► ACTIVE
│ │
│ commit() │ commit()
▼ ▼
COMMITTED ◄─────────────── COMMITTED
│ │
│ rollback() │ rollback()
▼ ▼
ROLLED_BACK ROLLED_BACK
│ │
│ cleanup() │ cleanup()
▼ ▼
CLEANED_UP CLEANED_UP
Any state ──► ERRORED (on git command failure)
Error Reference
| Exception | When raised |
|---|---|
SandboxCreationError |
create() fails (not a git repo, git timeout, etc.) |
SandboxCommitError |
commit() fails (git commit or merge error) |
SandboxRollbackError |
rollback() fails (git reset error) |
SandboxStateError |
Method called in wrong lifecycle state |
All exceptions are importable from cleveragents.infrastructure.sandbox.protocol.
Integration with Plan Lifecycle
The SandboxFactory selects GitWorktreeSandbox automatically when the
resource type is git-checkout and the sandbox strategy is git_worktree.
The PlanApplyService calls sandbox.commit() and then sandbox.cleanup() on
success, or sandbox.rollback() followed by sandbox.cleanup() on failure.
The CLI plan apply command displays a spec-aligned Apply Summary panel:
╭─ Apply Summary ──────────────────────────────────────────────────╮
│ Plan ID : 01HXYZ... │
│ Project : local/my-project │
│ Artifacts : 3 changed, 1 added, 0 deleted │
│ Timestamp : 2026-04-09T12:34:56 │
╰──────────────────────────────────────────────────────────────────╯
╭─ Sandbox Cleanup ────────────────────────────────────────────────╮
│ Worktree removed · Branch deleted · Prune complete │
╰──────────────────────────────────────────────────────────────────╯
✓ OK Changes applied
Configuration
The git command timeout defaults to 30 seconds. Override it by passing
git_timeout to the constructor, or configure it via the sandbox strategy
registry if using the factory.
sandbox = GitWorktreeSandbox(
resource_id=resource.id,
original_path=resource.location,
git_timeout=60, # increase for large repos on slow storage
)
See Also
docs/development/custom_sandbox_strategy.md— How to implement a custom sandbox strategy- ADR-015 Sandbox & Checkpoint — Design rationale
- ADR-038 Cross-Mechanism Sandbox Coordination — Multi-sandbox coordination