Files
CoreRasurae 86e245c585
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / build (pull_request) Successful in 16s
CI / quality (pull_request) Successful in 19s
CI / security (pull_request) Successful in 35s
CI / typecheck (pull_request) Successful in 37s
CI / unit_tests (pull_request) Successful in 2m22s
CI / integration_tests (pull_request) Successful in 2m51s
CI / docker (pull_request) Successful in 38s
CI / coverage (pull_request) Successful in 3m34s
CI / benchmark-regression (pull_request) Successful in 22m10s
CI / lint (push) Successful in 12s
CI / build (push) Successful in 14s
CI / quality (push) Successful in 18s
CI / security (push) Successful in 32s
CI / typecheck (push) Successful in 34s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 1m55s
CI / docker (push) Successful in 38s
CI / integration_tests (push) Successful in 2m52s
CI / coverage (push) Successful in 4m4s
CI / benchmark-publish (push) Successful in 13m13s
feat(checkpoint): add checkpointing and rollback
fix Alembic migration down_revision to chain after
  m4_002_skill_flattened_tools (was pointing to stale
  c3_001_actor_registry)

BDD coverage: 33 scenarios (7 new), 129 steps, all passing.

ISSUES CLOSED: #206
2026-03-02 10:18:14 +00:00

159 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 | 1100 |
| `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.
## CLI Usage
### Create a checkpoint (programmatic)
Checkpoints are typically created automatically by the execution engine
before and after tool invocations, controlled by the `CheckpointScope`
on each tool's capability declaration.
### Rollback to a checkpoint
```bash
agents plan rollback [--yes|-y] <plan_id> <checkpoint_id>
```
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