Files
cleveragents-core/docs/modules/git-worktree-sandbox.md
T
HAL9000 984aad4a91 docs: add git worktree sandbox and context tier hydrator module guides
- 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
2026-04-28 09:25:25 +00:00

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:

  1. Creating a temporary git worktree at a new branch (cleveragents/plan-<id>)
  2. Routing all actor file writes into the worktree
  3. Committing changes in the worktree on plan apply
  4. Merging the sandbox branch back to the original branch
  5. 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> from HEAD
  • Creates a temporary directory at /tmp/ca-sandbox-<plan_id>-XXXX
  • Returns a SandboxContext with sandbox_path pointing to the worktree

Raises:

  • ValueError — if plan_id is empty
  • SandboxStateError — if not in PENDING status
  • SandboxCreationError — 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 status
  • ValueError — if resource_path contains .. (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 in CREATED or ACTIVE status
  • SandboxCommitError — 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 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.

Raises:

  • SandboxStateError — if called in an invalid status
  • SandboxRollbackError — if the rollback fails

cleanup() → None

Removes the worktree directory and deletes the sandbox branch. Idempotent — safe to call multiple times.

Internally runs:

  1. git worktree remove --force <worktree_path>
  2. git branch -D cleveragents/plan-<id>
  3. 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