# 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 |

## 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

```python
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

1. **Pre-execute checkpoint**: Created before the execute phase starts.
   Captures the sandbox state so that a failed execution can be rolled back.

2. **Post-execute checkpoint**: Created after a successful execute phase.
   Preserves the post-execution state before apply begins.

3. **Pre-apply checkpoint**: Created before the apply phase starts.
   Allows rollback if the apply fails or encounters merge conflicts.

4. **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)
