---
adr_number: 34
title: Decision Tree Versioning and History
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: Decision trees are built during lifecycle phases; corrections may trigger phase reversion
- number: 7
  title: Decision Tree and Correction
  relationship: Defines the decision record structure and `superseded_by` field that this ADR builds upon
- number: 19
  title: Storage and Persistence
  relationship: Decision records are stored in SQLite/SQLAlchemy with the repository pattern
- number: 33
  title: Decision Recording Protocol
  relationship: Defines how decisions are created; this ADR defines how they are versioned and organized
- number: 35
  title: Decision Tree Rollback and Replay
  relationship: Uses the versioned tree structure for rollback target identification and affected subtree computation
acceptance:
  votes_for:
  - voter: Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com>
    comment: Implicit versioning via superseded_by provides full audit history without schema overhead
  votes_against: []
  abstentions: []
---
## Context

ADR-007 defines the decision record structure including a `superseded_by` field for marking corrected decisions and a `correction_attempts` table for tracking correction history. However, it does not describe how the full decision tree — including old superseded branches that coexist with current active branches — is structured, navigated, or historically reconstructed. The specification refers to `--show-superseded` rendering and decision tree visualization, but the data model for distinguishing "current tree" from "historical tree at a point in time" is implicit rather than explicit.

Without a defined versioning model, the system cannot reliably answer questions like: "What did the tree look like before correction N?", "Which decisions were replaced and which survived?", or "How has this plan's strategy evolved across multiple corrections?"

## Decision Drivers

- Must reconstruct historical states of the decision tree for auditing and debugging ("what did the tree look like before correction N?")
- Version identifiers must be derivable without a central authority, using existing `superseded_by` pointers and `correction_attempts` records
- Superseded decisions must never be deleted — append-only history is required for audit trails and correction comparison
- The structural tree (rendering/navigation) and influence DAG (impact analysis) serve different purposes and must be maintained as separate but overlapping structures
- Cross-plan tree relationships via child plan references must preserve child plan decision records when a parent's `subplan_spawn` is superseded
- Historical reconstruction must be deterministic and reproducible given the same correction attempt identifier

## Decision

The decision tree uses **implicit versioning through `superseded_by` pointers**. The "current tree" is defined as all decisions where `superseded_by IS NULL`. Historical tree states are reconstructable by walking `superseded_by` chains backward from `correction_attempts` records. Old superseded branches are never deleted — they remain in the database alongside current decisions, creating a persistent, append-only history of the tree's evolution.

## Design

### Dual Structure: Structural Tree and Influence DAG

A plan's decision data forms two overlapping structures:

**Structural Tree** (`parent_decision_id`): Defines the hierarchical rendering order of decisions. Each decision has at most one parent. The `prompt_definition` decision is the root (no parent). This tree determines how `agents plan tree` renders the decision hierarchy.

**Influence DAG** (`decision_dependencies` table): Captures which decisions constrained or influenced which other decisions. A single decision may be influenced by multiple upstream decisions. For example, an `implementation_choice` decision might be influenced by both a `strategy_choice` (which set the approach) and a `resource_selection` (which determined the files to modify). This DAG is the structure used to compute the "affected subtree" during correction.

The two structures serve different purposes:

| Structure | Primary Key | Purpose | Used For |
|---|---|---|---|
| Structural tree | `parent_decision_id` | Rendering, navigation, tree visualization | `agents plan tree`, `agents plan explain` |
| Influence DAG | `decision_dependencies` | Dependency tracking, impact analysis | `agents plan correct` (affected subtree computation) |

A decision's parent in the structural tree may differ from its dependencies in the influence DAG. For example, a `tool_invocation` decision's tree parent is the `implementation_choice` that spawned it, but its influence DAG dependencies include the `strategy_choice` and `resource_selection` that constrained it.

### Current Tree

The **current tree** for a plan is the set of active, non-superseded decisions:

```sql
SELECT * FROM decisions
WHERE plan_id = :plan_id
  AND superseded_by IS NULL
ORDER BY sequence_number;
```

This is what `agents plan tree <plan_id>` renders by default. The current tree satisfies these structural invariants:

1. **Single root**: Exactly one decision with `decision_type = 'prompt_definition'` and `parent_decision_id IS NULL`.
2. **Reachability**: Every non-root decision in the current tree is reachable from the root via `parent_decision_id` links (all of which also have `superseded_by IS NULL`).
3. **Acyclicity**: No circular references in `parent_decision_id` chains.
4. **Monotonic ordering**: `sequence_number` is monotonically increasing per plan across all decisions (including superseded ones). New decisions created during correction receive higher sequence numbers than the decisions they replace.

### Superseded Branches

When a decision is corrected via `agents plan correct`:

1. The original decision's `superseded_by` is set to the new replacement decision's ID.
2. All descendants of the original decision in the structural tree are also superseded (cascading). Each descendant's `superseded_by` is set to a sentinel value indicating it was invalidated as part of a cascade (the correction attempt's new decision ID).
3. New replacement decisions are created with `is_correction = true` and `corrects_decision_id` pointing to the original.
4. The replacement decisions (and any new descendants) have `superseded_by IS NULL`, making them part of the current tree.

**Superseded decisions are never deleted.** They remain in the database indefinitely, subject to configurable retention policies that may archive them after the plan reaches the `applied` terminal state.

### Superseded Chain Structure

A decision that has been corrected multiple times forms a chain:

```
D_original (superseded_by → D_correction1)
    └── D_correction1 (superseded_by → D_correction2)
        └── D_correction2 (superseded_by → NULL)  ← current
```

Each link in the chain corresponds to a `correction_attempts` record that provides the correction metadata (mode, guidance, archived artifacts).

### Rendering Superseded Branches

`agents plan tree --show-superseded` renders both current and superseded decisions:

```
Plan: 01KH29QDEE...
├── [prompt_definition] "Increase test coverage to 85%"
├── [invariant_enforced] "All API calls over TCP must be mocked"
├── [strategy_choice] "Prioritize auth and payment modules"
│   └── (superseded) [strategy_choice] "Prioritize all modules equally"
│       └── Superseded by: 01KH2A... on 2026-02-19
│           Reason: "Prioritize auth and payment modules due to deadline"
├── [subplan_spawn] "Write tests for auth module"
│   └── Plan: 01KH29R8WP...
└── [subplan_spawn] "Write tests for payment module"
    └── Plan: 01KH29RN2Y...
```

Superseded decisions are displayed with a `(superseded)` prefix, visually dimmed, and indented under the current decision that replaced them. The superseding decision ID and correction reason are shown as metadata.

### Historical Tree Reconstruction

Given a `correction_attempt_id`, the system can reconstruct the tree "as it was before that correction":

```
Algorithm: reconstruct_tree_at_correction(plan_id, correction_attempt_id)

1. Load the correction_attempt record:
   - original_decision_id: the decision that was corrected
   - new_decision_id: the replacement decision

2. Start from the current tree (all decisions WHERE superseded_by IS NULL)

3. Remove the new_decision_id and all its descendants from the result set

4. Add the original_decision_id and all its descendants back into the result set
   (these are the decisions whose superseded_by points to the new_decision_id
    or was set during this correction's cascade)

5. Result: the tree as it existed before this specific correction was applied
```

For reconstructing the tree at an arbitrary point in the correction history (before the Nth correction), the algorithm applies steps 3-4 in reverse for each correction from the most recent back to N+1.

### Multiple Corrections: Linear Correction History

Each correction creates a `correction_attempts` record. The sequence of corrections for a plan forms a linear history ordered by `created_at`:

```
Correction 1: replaced D_strategy with D_strategy'
Correction 2: replaced D_resource with D_resource'
Correction 3: replaced D_strategy' with D_strategy''
```

The tree at any version is computable by applying or un-applying corrections in sequence. This is analogous to a version control log — each correction is a "commit" that transforms the tree from one state to another.

### Cross-Plan Tree Relationships

Decision trees span plan boundaries through child plan references:

- A parent plan's `subplan_spawn` decision includes `downstream_plan_ids` listing the child plans spawned from that decision.
- Each child plan has its own independent decision tree with its own `prompt_definition` root.
- The child plan's prompt is created by the parent plan during Execute (the parent's execution actor authored the child's description).

When a parent plan's `subplan_spawn` decision is superseded:
- The corresponding child plan is invalidated (its decisions are not individually superseded — the entire child plan is marked for re-execution or cancellation).
- If the correction replaces the `subplan_spawn` with a new spawn decision, a new child plan is created with a fresh decision tree.
- The old child plan's decision tree is preserved (for audit and comparison via `agents plan diff --correction`).

### Sequence Number Semantics

The `sequence_number` field serves as a global ordering of all decisions within a plan, including superseded ones:

- Original decisions: assigned sequentially during initial Strategize/Execute (1, 2, 3, ...).
- Correction decisions: assigned higher sequence numbers than the decisions they replace (e.g., if decision 5 is corrected, the replacement might be decision 12).
- This means the current tree's sequence numbers may have gaps (e.g., 1, 2, 4, 12, 13) where superseded decisions occupied the missing numbers.
- Sequence numbers are never reused or reassigned.

## Constraints

- Decision trees are **append-only**. Decisions are never deleted from the database. Superseded decisions have their `superseded_by` set but remain as permanent records.
- `superseded_by` chains must be acyclic. A decision cannot be superseded by one of its own ancestors in the superseded chain.
- Every plan has exactly one `prompt_definition` root in its current tree. Correcting the root replaces it with a new root (the old root is superseded, not deleted).
- Historical reconstruction must be deterministic: given the same `correction_attempt_id`, the algorithm always produces the same tree.
- Cross-plan references (via `downstream_plan_ids`) are informational pointers, not ownership. Superseding a parent decision does not modify the child plan's decision records — it marks the child plan for re-execution at the plan level.

## Consequences

### Positive
- The append-only model provides complete audit history of every decision and correction ever made.
- Implicit versioning via `superseded_by` requires no additional schema — the existing `decisions` and `correction_attempts` tables are sufficient.
- Historical reconstruction enables debugging ("what was the strategy before the last correction?") and comparison (`agents plan diff --correction`).
- The dual structure (tree + DAG) separates concerns: rendering is clean (tree), impact analysis is precise (DAG).

### Negative
- Append-only storage means decision tables grow monotonically. Long-lived plans with many corrections accumulate significant data.
- Historical reconstruction requires iterating through correction_attempts in reverse, which is O(N) in the number of corrections. For plans with many corrections, this could be slow.
- The dual structure (tree + DAG) creates a maintenance burden: both must be kept consistent when decisions are created or superseded.

### Risks
- Storage growth for heavily-corrected plans could become problematic without retention policies.
- The "cascade supersede" operation (superseding all descendants of a corrected decision) must be atomic — partial cascading would leave the tree in an inconsistent state.
- Sequence number gaps in the current tree could confuse users or tooling that expects contiguous numbering.

## Alternatives Considered

**Explicit tree version snapshots (a `tree_versions` table capturing active decision sets)** — Would enable O(1) historical tree lookups but adds schema complexity, storage overhead for snapshot maintenance, and a synchronization concern (snapshot must be updated atomically with every decision creation and correction). The implicit model is simpler and sufficient for the expected correction frequency.

**Mutable tree (delete superseded decisions, keep only current)** — Simpler storage model but loses all audit history. The user cannot see what the tree looked like before a correction, cannot compare old vs. new strategies, and cannot debug why a correction was made. Rejected because auditability is a core requirement.

## Compliance

- **Current tree invariant tests**: Tests verify that the current tree (superseded_by IS NULL) always has exactly one root, all decisions are reachable from root, and no cycles exist.
- **Supersede cascade tests**: Tests verify that correcting a decision supersedes all descendants atomically and that the cascade is complete (no orphaned active descendants of a superseded decision).
- **Historical reconstruction tests**: Tests verify that `reconstruct_tree_at_correction` produces the correct tree for each correction point, matching the actual tree state at that point in time.
- **Sequence number tests**: Tests verify monotonic ordering, no reuse, and correct gap behavior after corrections.
- **Cross-plan reference tests**: Tests verify that superseding a `subplan_spawn` decision correctly flags the child plan for re-execution without modifying the child's decision records.
- **Rendering tests**: Tests verify that `plan tree` and `plan tree --show-superseded` produce correct output for trees with zero, one, and multiple corrections.
