# Checkpointing and Rollback ## Overview CleverAgents supports checkpointing and rollback to allow operators to snapshot sandbox state during plan execution and restore it later. This is useful for recovering from mistakes, reverting tool side-effects, and supporting the decision-correction revert flow. ## Concepts ### Checkpoint A **Checkpoint** is an immutable record of sandbox state at a point in time. Each checkpoint stores: - `checkpoint_id` — Unique ULID identifier - `plan_id` — The plan that owns this checkpoint - `sandbox_ref` — A reference to the sandbox state (e.g. git commit hash) - `decision_id` — Optional decision ULID this checkpoint is aligned to - `checkpoint_type` — One of `pre_write`, `post_step`, or `manual` - `resource_id` — Optional resource ULID association - `filesystem_path` — Relative path within the checkpoint directory - `size_bytes` — Size of the checkpoint data in bytes - `created_at` — Timestamp of creation (UTC) - `metadata` — Audit metadata including reason, source tool, and phase ### Retention Policy A **CheckpointRetentionPolicy** governs how many checkpoints a plan may keep. When `auto_prune` is enabled and the number of checkpoints exceeds `max_checkpoints`, the oldest **interior** checkpoints are automatically removed when a new one is created. The first (earliest) and most recent checkpoints are always preserved during pruning. **Defaults:** | Field | Default | Range | |-------------------|---------|---------| | `max_checkpoints` | 50 | 1–100 | | `auto_prune` | true | | ### Rollback A **rollback** restores sandbox state to a previously captured checkpoint. The `RollbackResult` describes what changed: - `restored_files_count` — Number of files restored - `changed_paths` — Paths that were modified during rollback - `from_checkpoint_id` — The checkpoint that was restored ## Guards The rollback operation enforces two guards: 1. **Plan is applied** — Once a plan reaches the `applied` terminal state, rollback is rejected with a `BusinessRuleViolation`. 2. **Sandbox is missing** — If the sandbox has been cleaned up, rollback is rejected because there is nothing to restore. ## Automatic Checkpoint Triggers (v3.8.0+) The execution engine now supports four automatic checkpoint triggers, all configurable via `checkpoints.auto_create_on` (default: all enabled): | Trigger | When | Component | |---------|------|-----------| | `before_tool_execute` | Before each write-tool execution | `ToolRunner` | | `after_tool_execute` | After each write-tool execution | `ToolRunner` | | `on_subplan_spawn` | Before first subplan execution attempt | `SubplanExecutionService` | | `on_error` | When the Execute phase fails | `PlanExecutor` | ### Configuration ```toml [checkpoints] auto_create_on = "before_tool_execute,after_tool_execute,on_subplan_spawn,on_error" ``` To disable a specific trigger, remove it from the list. To disable all automatic checkpoints: ```toml [checkpoints] auto_create_on = "" ``` ### Wiring The `CheckpointService` is injected as an optional parameter into: - **`ToolRunner`** — `checkpoint_service` and `auto_checkpoint_triggers` parameters - **`SubplanExecutionService`** — `checkpoint_service`, `auto_checkpoint_triggers`, and `parent_plan_id` parameters - **`PlanExecutor`** — uses `_is_auto_trigger_active()` helper to check trigger config When `checkpoint_service` is `None`, all automatic checkpointing is skipped (backward-compatible no-op). ## CLI Usage ### Create a checkpoint (programmatic) Checkpoints are created automatically by the execution engine via the four triggers above, or manually via `CheckpointService.create_checkpoint()`. The `CheckpointScope` on each tool's capability declaration controls which tools trigger `before_tool_execute` / `after_tool_execute` checkpoints. ### Rollback to a checkpoint ```bash agents plan rollback [--yes|-y] ``` The `--yes` / `-y` flag skips the interactive confirmation prompt. **JSON output envelope** (when using `--format json`): ```json { "rollback_summary": { "plan_id": "...", "from_checkpoint_id": "...", "restored_files_count": 3 }, "changes_reverted": ["path/to/file1.py", "path/to/file2.py"], "impact": { "files_affected": 3 }, "post_rollback_state": { "active_checkpoint": "...", "plan_id": "..." }, "timing": { "elapsed_seconds": 0.042 }, "messages": ["Rollback completed successfully."] } ``` ### Error Cases | Error | Cause | |-------|-------| | `Rollback blocked: plan is already applied` | Plan has reached terminal applied state | | `Rollback blocked: sandbox is missing` | Sandbox was cleaned up before rollback | | `Not found: checkpoint` | Checkpoint ID does not exist | | `Validation Error` | Checkpoint does not belong to the specified plan | ## Correction Service Integration The `CorrectionService` accepts an optional `checkpoint_service` parameter (typed as `CheckpointService | None`). This is an **integration point** for a future milestone: once wired, the correction revert flow will delegate sandbox restoration to the checkpoint rollback mechanism, enabling reuse of the same rollback logic for both explicit CLI rollback and decision-correction reverts. The delegation is **not yet active** in the current implementation; `execute_revert()` performs BFS impact analysis and marks decisions as reverted but does not invoke `checkpoint_service.rollback_to_checkpoint()`. ## Database Schema The `checkpoint_metadata` table stores checkpoint metadata: | Column | Type | Description | |-------------------|-------------|-------------------------------------| | `checkpoint_id` | STRING(26) PK | ULID primary key | | `plan_id` | STRING(26) FK | References `v3_plans.plan_id` | | `decision_id` | STRING(26) | Optional decision alignment | | `checkpoint_type` | TEXT | `pre_write`, `post_step`, `manual` | | `resource_id` | STRING(26) | Optional resource association | | `sandbox_ref` | TEXT | Git commit hash or patch reference | | `filesystem_path` | TEXT | Relative path within checkpoint dir | | `size_bytes` | INTEGER | Size of checkpoint data in bytes | | `created_at` | STRING(30) | ISO-8601 timestamp | | `metadata_json` | TEXT | JSON: reason, source_tool, phase | Indexes: `idx_checkpoints_plan`, `ix_checkpoint_metadata_created_at`. ## Dependency Injection The `CheckpointService` is registered in the DI container (`container.py`) and backed by a `CheckpointRepository` that uses the session-factory pattern (ADR-007). The CLI `rollback` command obtains the service from the container via `get_container().checkpoint_service()`. The `UnitOfWorkContext` also exposes a `checkpoints` property for cross-repository atomicity within a single transaction. ## Metadata for Auditability Each checkpoint stores structured metadata to support audit trails: - **reason** — Why the checkpoint was created (e.g. "pre-execution") - **source_tool** — Which tool triggered the checkpoint (e.g. "lint-check") - **phase** — Which plan phase was active (e.g. "execute") - **extra** — Arbitrary key-value pairs for extension