---
adr_number: 35
title: Decision Tree Rollback and Replay
status_history:
- - '2026-02-20'
  - Proposed
  - Jeffrey Phillips Freeman
- - '2026-02-20'
  - Accepted
  - Jeffrey Phillips Freeman
tier: 2
authors:
- Jeffrey Phillips Freeman
superseded_by: null
related_adrs:
- number: 6
  title: Plan Lifecycle
  relationship: Phase reversion interacts with decision correction; corrections may trigger phase changes
- number: 7
  title: Decision Tree and Correction
  relationship: Defines the correction model that this ADR provides rollback mechanics for
- number: 15
  title: Sandbox and Checkpoint
  relationship: Checkpoint infrastructure that decision-aligned checkpointing builds upon
- number: 33
  title: Decision Recording Protocol
  relationship: The `record_decision` tool triggers checkpoint group creation during Execute
- number: 34
  title: Decision Tree Versioning and History
  relationship: Superseded branch mechanics used during correction cascading
acceptance:
  votes_for:
  - voter: Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com>
    comment: Decision-aligned checkpointing with coordinated cross-resource rollback enables fine-grained Execute-phase correction
  votes_against: []
  abstentions: []
---
## Context

ADR-007 defines a high-level 6-step correction flow for reverting decisions, and ADR-034 defines how superseded branches are tracked. However, neither ADR details the mechanics of actually rolling back to an arbitrary decision point — especially during the Execute phase where resources have been modified. The correction model must handle two fundamentally different scenarios:

1. **Mid-Strategize correction**: Strategize is read-only, so there are no resource modifications to roll back. Only the actor's reasoning state needs to be restored to the decision point.

2. **Mid-Execute correction**: Execute modifies resources via tool calls. Rolling back to an arbitrary Execute-phase decision requires restoring both the resource state (sandbox contents) and the actor's reasoning state to the exact moment that decision was made.

The second scenario is significantly harder. Resource state changes continuously throughout Execute as tools are invoked. Without a mechanism to capture resource state at each decision point, the system cannot roll back to arbitrary decisions during Execute — it can only revert the entire Execute phase back to Strategize, which is expensive and discards all valid work.

## Decision Drivers

- Mid-Execute corrections require restoring both resource state (sandbox contents) and actor reasoning state to the exact decision point
- Mid-Strategize corrections are read-only and need only reasoning state restoration, not resource rollback
- Without decision-aligned checkpoints, the only Execute-phase recovery option is reverting the entire phase back to Strategize, discarding all valid work
- Rollback must coordinate across multiple sandbox strategies (git worktree, filesystem copy, database savepoints) within a single correction
- Non-rollbackable resources and irreversible side effects (e.g., external API calls) must be explicitly tracked and warned about rather than silently ignored
- Checkpoint overhead must fit within the plan's checkpoint budget, leveraging the fact that decisions are far less frequent than tool calls

## Decision

Rollback operates on **two independent dimensions**: *resource rollback* (undoing sandbox changes via checkpoint restoration) and *reasoning rollback* (restoring actor state from the decision's `context_snapshot.actor_state_ref`). Execute-phase decisions automatically trigger **coordinated checkpoint groups** across all affected sandbox resources, creating a 1:1 mapping between decisions and resource states that enables fine-grained rollback to any Execute-phase decision point.

## Design

### Two Rollback Dimensions

| Dimension | What It Restores | Mechanism | Applicable Phase |
|---|---|---|---|
| **Resource rollback** | Sandbox contents (files, database state, filesystem) | Checkpoint restoration via sandbox-strategy-specific handlers | Execute only |
| **Reasoning rollback** | Actor's conversation history, intermediate reasoning, graph state | LangGraph checkpoint restoration from `actor_state_ref` | Strategize and Execute |

For Strategize-phase corrections, only reasoning rollback is needed (Strategize is read-only — there are no resource modifications to undo). For Execute-phase corrections, both dimensions are required.

### Mid-Strategize Correction Flow

When `agents plan correct <decision_id> --mode=revert` targets a Strategize-phase decision:

1. **Validate**: Confirm the target decision belongs to the current plan and has `superseded_by IS NULL`.
2. **Load context**: Read the target decision's `context_snapshot`, specifically the `actor_state_ref` (LangGraph checkpoint).
3. **Restore actor state**: Restore the strategy actor's LangGraph state from `actor_state_ref`. This places the actor at the exact reasoning state it was in when the target decision was made — same conversation history, same intermediate reasoning, same loaded context.
4. **Supersede affected subtree**: Mark the target decision and all its descendants as superseded (see ADR-034 for cascade mechanics). The decisions above the target in the tree remain active and unchanged.
5. **Inject guidance**: Create a `user_intervention` decision containing the user's `--guidance` text. This decision becomes part of the actor's context, informing it of the correction.
6. **Resume strategy actor**: The strategy actor continues reasoning from the restored state, now informed by the user's guidance. It creates new decisions (via `record_decision` per ADR-033) to replace the superseded ones.
7. **Record correction**: Create a `correction_attempts` record linking the original decision to the new replacement decision.

No resource rollback occurs — there is nothing to roll back during Strategize.

### Execute-Phase Decision-Aligned Checkpointing

The key mechanism enabling fine-grained Execute-phase rollback is **decision-aligned checkpointing**: every `record_decision` call during the Execute phase automatically triggers creation of a coordinated checkpoint group.

#### Checkpoint Group Creation

When `record_decision` is called during Execute:

1. **Enumerate modified resources**: Identify all sandbox resources that have been written to during this plan's execution up to this point.
2. **Create per-resource checkpoints**: For each modified resource, create a checkpoint using the resource's sandbox-strategy-specific mechanism:

    | Sandbox Strategy | Checkpoint Mechanism | Stored As |
    |---|---|---|
    | `git_worktree` | Git commit in the worktree branch | Commit SHA |
    | `filesystem_copy` | Snapshot of modified files | Compressed archive |
    | `overlay` | Overlay layer state marker | Layer reference |
    | `transaction_rollback` | Database SAVEPOINT | Savepoint name |
    | `none` | Marker only (non-rollbackable) | Marker record |

3. **Link to decision**: Each checkpoint is stored in `checkpoint_metadata` with:
    - `decision_id` = the decision being recorded (this is the group key)
    - `resource_id` = the specific resource this checkpoint covers
    - `checkpoint_type` = `'decision_point'`
    - For non-rollbackable resources: `checkpoint_type` = `'non_rollbackable'`

4. **Unmodified resources**: Resources that have not been written to at the time of the decision have no checkpoint row. This is correct — they are still in their pre-execution baseline state and do not need rollback.

All checkpoints sharing the same `decision_id` form a **checkpoint group** — the complete resource state at that decision point.

#### Why This Works

Since actors record decisions via the `record_decision` tool call (ADR-033), and this tool is treated as a write operation, checkpoint creation integrates naturally with the existing checkpoint system (ADR-015). The sequence during Execute is:

```
Actor reasons about an implementation approach
  │
  ▼
Actor calls record_decision(type=implementation_choice, ...)
  │
  ├── System captures context_snapshot (hot context + LangGraph state)
  ├── System creates checkpoint group (one per modified resource)
  ├── System persists the decision record
  │
  ▼
Actor calls edit_file(...), write_file(...), etc. to implement
  │
  ├── Regular pre-write checkpoints per existing ADR-015 rules
  │
  ▼
Something unexpected happens (error, validation failure, etc.)
  │
  ▼
Actor calls record_decision(type=error_recovery, ...)
  │
  ├── System creates another checkpoint group (captures state including tool effects)
  ├── System persists the decision record
  │
  ▼
Actor implements the recovery...
```

Rolling back to the `implementation_choice` decision restores resource state to the checkpoint taken *before* any of the implementation tool calls. Rolling back to the `error_recovery` decision restores state to the checkpoint taken *after* the failed tool calls but *before* the recovery implementation.

### Mid-Execute Correction Flow

When `agents plan correct <decision_id> --mode=revert` targets an Execute-phase decision:

1. **Validate**: Confirm the target decision belongs to the current plan, has `superseded_by IS NULL`, and was created during the Execute phase.

2. **Load checkpoint group**: Query `checkpoint_metadata WHERE decision_id = <target>` to retrieve all per-resource checkpoints.

3. **Classify checkpoints**:
    - *Rollbackable*: checkpoints with `checkpoint_type = 'decision_point'` (resources with sandboxes that support rollback)
    - *Non-rollbackable*: checkpoints with `checkpoint_type = 'non_rollbackable'` (resources with `sandbox.strategy: none`)
    - *Unaffected*: resources with no checkpoint for this decision (not yet modified at decision time)

4. **Warn about non-rollbackable resources**: If any non-rollbackable checkpoints exist, present a warning to the user:

    ```
    WARNING: The following resources cannot be rolled back (sandbox strategy: none):
      - local/external-api
      - local/notification-service
    
    Resource changes since this decision will persist.
    Proceed with rollback of other resources? [y/N]
    ```

    The user must confirm to proceed. If they abort, the correction is cancelled.

5. **Check for irreversible side effects**: Examine all decisions downstream of the target for `tool_invocation` decisions that used tools with irreversible `side_effects` declarations. If any exist, warn:

    ```
    WARNING: Rolling back past this decision undoes work that triggered
    irreversible external effects:
      - local/deploy-to-staging (1 invocation) — external deployment
      - local/send-notification (2 invocations) — emails sent
    
    Sandbox resources can be rolled back, but external effects cannot be undone.
    Proceed? [y/N]
    ```

6. **Resource rollback**: For each rollbackable checkpoint, dispatch to the strategy-specific handler:

    | Strategy | Rollback Operation |
    |---|---|
    | `git_worktree` | `git reset --hard <commit_sha>` in the worktree |
    | `filesystem_copy` | Restore from compressed archive at `filesystem_path` |
    | `overlay` | Discard overlay writes since the layer reference |
    | `transaction_rollback` | `ROLLBACK TO SAVEPOINT <savepoint_name>` |

    If any individual resource rollback fails, the correction is marked as `'failed'` and the plan enters the `errored` state. The system logs which resources were successfully rolled back and which failed.

7. **Reasoning rollback**: Restore the execution actor's LangGraph state from `context_snapshot.actor_state_ref`.

8. **Supersede affected subtree**: Mark the target decision and all descendants as superseded (ADR-034 cascade mechanics).

9. **Inject guidance**: Create a `user_intervention` decision with the user's `--guidance` text.

10. **Resume execution actor**: The actor continues from the restored state with the correction guidance.

11. **Record correction**: Create a `correction_attempts` record with mode `'revert'` linking the original and new decisions.

### Three Rollback Tiers

The system's rollback capability depends on configuration:

| Tier | Prerequisites | Capability | When to Use |
|---|---|---|---|
| **Full decision-level** | Checkpointing enabled (`sandbox.checkpoint.enabled = true`) AND sandboxable resources | Roll back to any individual Execute-phase decision | Default for most plans; best correction granularity |
| **Phase-level** | Checkpointing disabled OR resources lack sandbox support | Revert entire Execute phase → Strategize; re-execute from scratch | Low-stakes plans where checkpoint overhead is unwarranted |
| **No rollback** | `sandbox.strategy: none` AND checkpointing disabled | Execute-phase decisions are one-way; only `--mode=append` corrections available | External API-only plans, non-sandboxable environments |

The system automatically detects the available tier when a correction is requested and informs the user if the requested correction exceeds the available capability:

```
$ agents plan correct 01HXM... --mode=revert --guidance "Use async instead"

ERROR: Decision 01HXM... was made during Execute, but checkpointing is disabled
       for this plan. Fine-grained rollback is not available.

Available options:
  - Revert to Strategize: agents plan correct <strategize_decision_id> --mode=revert
  - Append correction: agents plan correct 01HXM... --mode=append --guidance "..."
  - Re-run with checkpointing: enable sandbox.checkpoint.enabled and re-execute
```

### Affected Subtree Computation

When correcting a decision, the system must identify all downstream decisions and plans that are affected (the "affected subtree"). This computation uses the influence DAG (not the structural tree):

```python
def compute_affected_subtree(target_decision_id: str) -> tuple[set[str], set[str]]:
    """Returns (affected_decision_ids, affected_plan_ids)."""
    affected_decisions = {target_decision_id}
    affected_plans = set()
    queue = [target_decision_id]
    
    while queue:
        current = queue.pop(0)
        
        # Follow structural tree children
        children = db.query(
            "SELECT decision_id FROM decisions "
            "WHERE parent_decision_id = :current AND superseded_by IS NULL",
            current=current
        )
        
        # Follow influence DAG dependents
        dependents = db.query(
            "SELECT downstream_ref FROM decision_dependencies "
            "WHERE upstream_decision_id = :current AND dependency_type = 'decision'",
            current=current
        )
        
        for decision_id in children | dependents:
            if decision_id not in affected_decisions:
                affected_decisions.add(decision_id)
                queue.append(decision_id)
        
        # Collect affected child plans
        child_plans = db.query(
            "SELECT downstream_ref FROM decision_dependencies "
            "WHERE upstream_decision_id = :current AND dependency_type = 'plan'",
            current=current
        )
        affected_plans.update(child_plans)
    
    return affected_decisions, affected_plans
```

### Cross-Plan Correction Cascading

When the affected subtree includes child plans (via `subplan_spawn` or `subplan_parallel_spawn` decisions):

1. **Child plan not yet started**: Cancel the child plan. It will be re-spawned if the corrected parent decision still calls for it.
2. **Child plan in progress (Strategize or Execute)**: Cancel the child plan. Roll back its sandbox. It will be re-spawned with potentially different parameters after the parent's correction replays.
3. **Child plan completed but not applied**: Cancel the child plan. Roll back its sandbox. Same as above.
4. **Child plan already applied**: **Correction is rejected.** Once a child plan's changes have been committed to real resources, the parent cannot unilaterally roll them back. The user must correct the child plan independently or use `--mode=append` on the parent.

### Phase Boundary Interaction

`agents plan correct` is **orthogonal** to phase reversion (Execute → Strategize). They are different mechanisms triggered by different actors:

| Mechanism | Trigger | Direction | Use Case |
|---|---|---|---|
| Phase reversion | Execution actor (constraints too tight) | Execute → Strategize | Actor-initiated; strategy needs adjustment |
| Decision correction | User (`plan correct`) | Any decision → re-run from that point | User-initiated; decision was wrong |

These can interact:

- **Correcting a Strategize-phase decision while the plan is in Execute**: The plan reverts to Strategize at the corrected decision point. All Execute-phase work is rolled back via checkpoints. The strategy actor resumes from the corrected decision, produces a new strategy, and Execute restarts with the new strategy.

- **Correcting an Execute-phase decision while the plan is in Execute**: The plan remains in Execute. Resources are rolled back to the decision's checkpoint. The execution actor resumes from that point with correction guidance. No phase change occurs.

- **Correcting any decision while the plan is in Apply**: If the plan is in the `constrained` or `errored` Apply state, correction reverts to the appropriate phase. If the plan is already `applied` (terminal success), correction is rejected — applied changes cannot be reverted.

### Checkpoint Budget Considerations

Decision-aligned checkpoints consume from the plan's checkpoint budget (`sandbox.checkpoint.max-per-plan`, default: 50). Since Execute-phase decisions are relatively rare compared to tool calls (typically 5-10 decisions vs. 30-50 tool calls), decision checkpoints fit within the default budget.

When the budget is exceeded, the existing pruning algorithm applies (keep first + most recent, prune oldest intermediate). Decision-linked checkpoints should be given **higher retention priority** than regular pre-write checkpoints during pruning, because losing a decision checkpoint eliminates the ability to roll back to that specific decision.

The pruning priority order is:
1. First checkpoint (always retained)
2. Most recent checkpoint (always retained)
3. Decision-linked checkpoints (`checkpoint_type = 'decision_point'`)
4. Regular pre-write checkpoints (`checkpoint_type = 'pre_write'`)

### Long-Running Database Transaction Constraint

The `transaction_rollback` sandbox strategy uses SAVEPOINTs within a single database transaction spanning the entire Execute phase. Decision-aligned checkpoints add SAVEPOINTs at each decision point, which are lightweight within an existing transaction. However, the underlying long-running transaction itself may be problematic for plans with extended execution times:

- Database transaction timeouts may be exceeded.
- Lock contention prevents other connections from accessing modified rows.
- Undo log / WAL growth accumulates for the duration of the transaction.

**Recommended mitigations** (the choice depends on the plan's expected duration and database characteristics):

1. **Decompose into child plans**: Each child plan gets its own shorter transaction. The parent plan coordinates sequential or parallel execution. This aligns naturally with the hierarchical decomposition model.
2. **Use `none` strategy with append-mode corrections**: Accept that database changes during Execute are not individually rollbackable. Use `--mode=append` corrections to fix issues by adding compensating changes.
3. **Implement a custom resource type with snapshot-based strategy**: A custom `database-snapshot` resource type could use point-in-time snapshots (e.g., `pg_dump`, database cloning, logical replication slots) instead of long-running transactions, trading storage for transaction duration.

Decision-aligned checkpoints (SAVEPOINTs) do not significantly worsen the long-running transaction problem — they are lightweight additions within an already-open transaction. The problem is inherent to the `transaction_rollback` sandbox strategy itself.

## Constraints

- Execute-phase `record_decision` calls must trigger checkpoint group creation. This is mandatory, not optional. The system must not persist an Execute-phase decision without a corresponding checkpoint group.
- Checkpoint groups must be atomic: either all per-resource checkpoints in a group are created, or none are. If checkpoint creation fails for any resource, the `record_decision` call fails and the decision is not persisted.
- Resource rollback during correction must be all-or-nothing for rollbackable resources. If any rollbackable resource fails to roll back, the correction fails and the plan enters `errored` state.
- Correcting a decision whose affected subtree includes an `applied` child plan is rejected. The user must correct the child plan independently.
- Checkpoint pruning must prioritize decision-linked checkpoints over regular pre-write checkpoints.
- The `context_snapshot.actor_state_ref` must be a valid, restorable LangGraph checkpoint at the time of the `record_decision` call.

## Consequences

### Positive
- Fine-grained Execute-phase rollback enables correcting individual decisions without re-executing the entire plan from scratch.
- The three-tier model gracefully degrades: full rollback when possible, phase-level when checkpointing is unavailable, append-only when nothing is sandboxed.
- Coordinated checkpoint groups ensure consistent multi-resource rollback.
- The mechanism reuses existing checkpoint infrastructure (ADR-015) — no new storage or rollback machinery is needed.
- Non-rollbackable resources are explicitly tracked and warned about, preventing silent data loss.

### Negative
- Checkpoint group creation at every Execute-phase decision adds latency and storage overhead.
- Multi-resource rollback coordination adds complexity to the correction engine.
- The three-tier model means correction capability varies by configuration, requiring clear user communication about what is possible.

### Risks
- LangGraph checkpoint restoration may not perfectly reproduce the actor's state if the LangGraph version or model has changed between the original decision and the correction attempt.
- Checkpoint group creation failure (e.g., disk full, database connection lost) prevents the decision from being recorded, potentially blocking plan execution.
- Irreversible side effects from tool invocations cannot be compensated by resource rollback alone. The system can only warn, not prevent.
- Coordinated multi-strategy rollback (e.g., git reset + SAVEPOINT ROLLBACK in the same correction) has no cross-resource atomicity guarantee — one may succeed while the other fails.

## Alternatives Considered

**Nearest-checkpoint rollback with replay** — Instead of checkpointing at every decision, find the nearest existing checkpoint before the target decision, roll back to it, and replay tool calls from the execution log up to the decision point. This reduces checkpoint count but introduces complexity (tool replay may not be deterministic) and latency (replaying many tool calls). Rejected because decision-aligned checkpointing is simpler and the checkpoint budget is adequate.

**Phase-level rollback only (no decision-level Execute rollback)** — Always revert to Strategize when an Execute-phase decision is wrong. Simpler but prohibitively expensive for large plans where only a late Execute-phase decision needs correction. The decision-aligned approach preserves all valid work up to the corrected decision.

**Compensating transactions for cross-resource atomicity** — Implement a two-phase commit protocol across sandbox strategies to guarantee atomic multi-resource rollback. Rejected as over-engineered — the sandbox strategies are heterogeneous (git, filesystem, database) and a distributed transaction coordinator across them would be extremely complex for marginal benefit.

## Compliance

- **Decision-checkpoint linkage tests**: Tests verify that every Execute-phase `record_decision` call creates a checkpoint group with one entry per modified resource.
- **Checkpoint group atomicity tests**: Tests verify that checkpoint group creation is all-or-nothing (if one resource's checkpoint fails, no checkpoints are created and the decision is not recorded).
- **Mid-Strategize correction tests**: BDD scenarios verify the full mid-Strategize correction flow: actor state restoration, subtree superseding, guidance injection, and strategy actor resumption.
- **Mid-Execute correction tests**: BDD scenarios verify the full mid-Execute correction flow: checkpoint group loading, resource rollback, actor state restoration, subtree superseding, and execution actor resumption.
- **Cross-sandbox rollback tests**: Integration tests verify coordinated rollback across multiple resources with different sandbox strategies (e.g., git_worktree + transaction_rollback).
- **Non-rollbackable warning tests**: Tests verify that corrections targeting decisions with non-rollbackable resources produce appropriate warnings.
- **Irreversible side effect warning tests**: Tests verify that corrections passing through tool invocations with irreversible side effects produce appropriate warnings.
- **Tier detection tests**: Tests verify that the system correctly identifies the available rollback tier and produces appropriate error messages when the requested correction exceeds the available tier.
- **Cross-plan cascade tests**: Tests verify that superseding a `subplan_spawn` decision correctly handles child plans in each possible state (not started, in progress, completed, applied).
- **Checkpoint pruning priority tests**: Tests verify that decision-linked checkpoints are retained over regular pre-write checkpoints during pruning.
