- Add docs/modules/context-hydration.md: documents the ACMS context hydration pipeline (context_tier_hydrator) that fixes the empty ContextTierService bug (#1028). Covers hydrate_tiers_from_project, hydrate_tiers_for_plan, file listing strategies, limits, and fragment metadata. - Add docs/modules/git-worktree-sandbox.md: documents the GitWorktreeSandbox class that isolates plan apply changes in a dedicated git branch/worktree and merges back on commit (#4454). Covers full lifecycle, error types, branch naming, and fallback for non-git projects. - Update docs/architecture.md: add Git Worktree Sandbox Apply section and extend ACMS section with context hydration note. - Update mkdocs.yml: add both new module pages to the Modules nav. ISSUES CLOSED: #6841
6.6 KiB
Git Worktree Sandbox
Package: cleveragents.infrastructure.sandbox.git_worktree
Introduced: Unreleased (issue #4454)
The git worktree sandbox isolates plan modifications in a dedicated git
branch and worktree. On commit, changes are merged back to the original
branch via git merge. On rollback, the worktree is discarded entirely.
This replaces the previous flat shutil.copy2 apply strategy for
git-checkout resources.
Purpose
When a plan applies changes to a git-checkout resource, the changes must be isolated from the working tree until the user approves them. The git worktree sandbox achieves this by:
- Creating a new branch
cleveragents/plan-<plan_id>from the current HEAD. - Creating a temporary git worktree at a system temp directory.
- Letting the actor write changes inside the worktree.
- On commit: staging all changes, committing them in the worktree, then merging the sandbox branch back into the original branch.
- On rollback: resetting the worktree (and optionally the original branch) to the base commit and discarding the worktree.
Non-git projects fall back to the original flat file copy strategy.
GitWorktreeSandbox
from cleveragents.infrastructure.sandbox.git_worktree import GitWorktreeSandbox
sandbox = GitWorktreeSandbox(
resource_id="01HZ...",
original_path="/path/to/repo",
)
Implements the Sandbox protocol.
Constructor parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
resource_id |
str |
— | Identifier of the resource being sandboxed |
original_path |
str |
— | Path to the git repository root |
git_timeout |
int |
30 |
Timeout in seconds for git commands |
Lifecycle
PENDING ──► create() ──► CREATED ──► get_path() ──► ACTIVE
│
commit() ─┤─► COMMITTED ──► cleanup() ──► CLEANED_UP
│
rollback() ─┴─► ROLLED_BACK ──► cleanup() ──► CLEANED_UP
create(plan_id) → SandboxContext
Creates the worktree and branch.
ctx = sandbox.create(plan_id="plan-01HZ...")
# ctx.sandbox_path — path to the worktree
# ctx.metadata["branch"] — e.g. "cleveragents/plan-plan-01HZ..."
# ctx.metadata["base_commit"] — HEAD SHA at creation time
Raises SandboxCreationError if the path is not a git repository root or
if git worktree add fails.
get_path(resource_path) → str
Translates a resource-relative path to an absolute path inside the
worktree. Rejects .. path traversal attempts.
worktree_file = sandbox.get_path("src/main.py")
# → "/tmp/ca-sandbox-plan-01HZ.../src/main.py"
commit(message=None) → CommitResult
Stages all changes in the worktree, commits them, then merges the sandbox branch into the original branch.
result = sandbox.commit("feat: implement new feature")
# result.success — True on success
# result.commit_ref — SHA of the sandbox commit
# result.changed_files — list of modified file paths
# result.added_files — list of newly added file paths
# result.deleted_files — list of deleted file paths
If there are no staged changes, returns a CommitResult with empty file
lists and no new commit.
Raises SandboxCommitError if the commit or merge fails.
rollback()
Discards all worktree changes.
- From
ACTIVE: resets the worktree branch to the base commit. - From
COMMITTED: also resets the original branch to the pre-merge commit (fully undoes the merge).
!!! warning "Multi-worktree safety"
Rolling back from COMMITTED executes git reset --hard on the
original branch. If other git worktrees or external processes are
tracking the same branch, they will be affected.
cleanup()
Removes the worktree directory and deletes the sandbox branch. Idempotent — safe to call multiple times.
Apply Summary
When plan apply uses the git worktree strategy, the CLI displays a
spec-aligned Apply Summary:
╭─ Apply Summary ──────────────────────────────────────╮
│ Plan ID: plan-01HZ... │
│ Artifacts: 3 files changed │
│ Insertions: +142 │
│ Deletions: -17 │
│ Project: local/my-project │
│ Timestamp: 2026-04-09 17:30:00 │
╰──────────────────────────────────────────────────────╯
╭─ Sandbox Cleanup ────────────────────────────────────╮
│ ✓ Worktree removed │
│ ✓ Branch deleted │
╰──────────────────────────────────────────────────────╯
✓ OK Changes applied
Error Types
| Exception | When Raised |
|---|---|
SandboxCreationError |
git worktree add fails or path is not a git root |
SandboxCommitError |
git commit or git merge fails |
SandboxRollbackError |
git reset --hard fails during rollback |
SandboxStateError |
Method called in an invalid lifecycle state |
All exceptions are importable from
cleveragents.infrastructure.sandbox.protocol.
Branch Naming
Branch names are sanitised from the plan ID:
- Only alphanumeric characters, hyphens, underscores, slashes, and dots are kept.
- Runs of disallowed characters are collapsed into a single hyphen.
- The final branch name has the form
cleveragents/plan-<sanitised_plan_id>.
Fallback for Non-Git Projects
PlanLifecycleService detects whether the resource is a git-checkout type.
If not, it falls back to the original flat file copy strategy
(shutil.copy2). The git worktree sandbox is only used for resources
where resource_type_name is git-checkout or git.