Files
cleveragents-core/docs/modules/correction-attempts.md
T
HAL9000 44652c216a docs: add sandbox, correction, and invariant module guides
Document lifecycle details, security warnings, and CLI usage for the new module guides so implementors have a single reference.

ISSUES CLOSED: #4848
2026-06-16 18:18:18 -04:00

456 lines
15 KiB
Markdown

# Correction Attempts Module
**Package:** `cleveragents.domain.models.core.correction`
**Introduced:** M4 (v3.3.0); `CorrectionAttemptRecord` and `correction_attempts` table added in M7 (v3.6.0, issue #920)
The correction module provides the domain models and lifecycle management for
plan decision tree corrections. When a plan's execution produces an
undesirable result, a correction allows the user to revert to a prior decision
point or append new guidance without restarting the entire plan.
For the design rationale see
[ADR-007](../adr/ADR-007-decision-tree-and-correction.md) (Decision Tree & Correction),
[ADR-035](../adr/ADR-035-decision-tree-rollback-and-replay.md) (Rollback & Replay).
---
## Purpose
During plan execution, the actor makes a series of decisions (tool calls,
resource writes, sub-plan spawns). These decisions form a tree. If the user
or an automated check determines that a decision was wrong, a correction
provides two recovery paths:
| Mode | Description |
|------|-------------|
| **Revert** | Invalidate the subtree rooted at the target decision; restore checkpoints; re-execute from that point with new guidance |
| **Append** | Add a new child plan at the target decision without invalidating existing decisions |
---
## Core Domain Models
### `CorrectionRequest`
```python
from cleveragents.domain.models.core.correction import CorrectionRequest, CorrectionMode
request = CorrectionRequest(
plan_id="01ARZ3...",
target_decision_id="01BCD4...",
mode=CorrectionMode.REVERT,
guidance="The file path was wrong — use src/api/ not src/app/",
)
```
| Field | Type | Description |
|-------|------|-------------|
| `correction_id` | `str` (ULID) | Unique identifier (auto-generated) |
| `plan_id` | `str` | Plan that owns the targeted decision tree |
| `target_decision_id` | `str` | Decision node to apply correction at |
| `mode` | `CorrectionMode` | `revert` or `append` |
| `guidance` | `str` (max 10,000 chars) | Human-supplied guidance |
| `dry_run` | `bool` | When `True`, only compute impact without executing |
| `status` | `CorrectionStatus` | Current lifecycle status |
| `created_at` | `datetime` (UTC) | When the request was created |
!!! warning "Security: validate untrusted `guidance` input"
The `guidance` field is injected directly into the LLM actor during
re-execution. When accepting guidance from untrusted integrations, validate
and sanitise the input to reduce prompt-injection risk. The 10,000-character
limit is enforced at the model layer but does not prevent malicious
instructions.
---
### `CorrectionMode`
```python
from cleveragents.domain.models.core.correction import CorrectionMode
CorrectionMode.REVERT # "revert" — invalidate subtree and re-execute
CorrectionMode.APPEND # "append" — add new child plan at target
```
---
### `CorrectionStatus`
Lifecycle states for a correction request:
```
PENDING → ANALYZING → EXECUTING → APPLIED
└──► FAILED
└──► CANCELLED
└──► REJECTED
```
| Status | Meaning |
|--------|---------|
| `PENDING` | Request created, not yet processed |
| `ANALYZING` | Impact analysis in progress |
| `EXECUTING` | Correction is being applied |
| `APPLIED` | Correction successfully applied |
| `FAILED` | Correction failed |
| `CANCELLED` | Correction was cancelled |
| `REJECTED` | Correction rejected (e.g. applied child plans block revert) |
---
### `CorrectionImpact`
Result of the impact analysis — computed before executing a correction.
```python
from cleveragents.domain.models.core.correction import CorrectionImpact
impact = CorrectionImpact(
affected_decisions=["01BCD4...", "01BCD5..."],
excluded_decisions=["01BCD6..."],
affected_files=["src/api/handler.py"],
affected_child_plans=["01EFG7..."],
risk_level="medium",
rollback_tier="full",
rollback_tier_depth=2,
)
```
| Field | Type | Description |
|-------|------|-------------|
| `affected_decisions` | `list[str]` | Decision IDs in the affected subtree (BFS order) |
| `excluded_decisions` | `list[str]` | Decision IDs NOT in the affected subtree (preserved) |
| `affected_files` | `list[str]` | Files touched by affected decisions |
| `affected_child_plans` | `list[str]` | Child plan IDs spawned by affected decisions |
| `estimated_cost` | `float \| None` | Estimated recompute cost |
| `risk_level` | `str` | `low`, `medium`, or `high` |
| `rollback_tier` | `str` | `full`, `phase`, or `append_only` |
| `rollback_tier_depth` | `int` | Depth of the targeted decision from the tree root |
| `artifacts_to_archive` | `list[str]` | Artifact paths to archive on revert |
---
### `CorrectionResult`
Outcome of executing a correction. For revert corrections, includes fields
for checkpoint restoration and re-execution signalling.
```python
from cleveragents.domain.models.core.correction import CorrectionResult, CorrectionStatus
result = CorrectionResult(
correction_id="01ARZ3...",
status=CorrectionStatus.APPLIED,
reverted_decisions=["01BCD4...", "01BCD5..."],
checkpoint_restored=True,
actor_state_ref="lc:checkpoint:01XYZ...",
user_intervention_decision_id="01HIJ8...",
phase_transition_target="strategize",
)
```
**Revert re-execution fields** (spec § Correction Flow, Revert Mode):
| Field | Type | Description |
|-------|------|-------------|
| `checkpoint_restored` | `bool` | Whether a sandbox checkpoint was restored |
| `actor_state_ref` | `str` | LangGraph actor checkpoint reference for reasoning rollback |
| `user_intervention_decision_id` | `str` | ULID of the user_intervention decision created to inject guidance |
| `phase_transition_target` | `str` | Target plan phase after revert (e.g. `"strategize"`) |
---
### `CorrectionDryRunReport`
Detailed report showing what a correction *would* do, without modifying state.
```python
from cleveragents.domain.models.core.correction import CorrectionDryRunReport
report = service.generate_dry_run_report(request)
# Access impact data via the nested impact object:
print(report.impact.affected_decisions)
print(report.impact.rollback_tier_depth)
print(report.impact.affected_child_plans)
```
> **Note:** `excluded_decisions`, `rollback_tier_depth`, and
> `child_plans_to_rollback` were previously duplicated at the top level.
> They were removed in M7 (issue #1087) to eliminate divergence risk.
> Access them via `report.impact.*` instead.
---
## Correction Attempt Records
The `correction_attempts` table (added in M7, issue #920) provides a
persistent audit trail of every correction execution attempt.
### `CorrectionAttemptRecord`
```python
from cleveragents.domain.models.core.correction import (
CorrectionAttemptRecord,
CorrectionAttemptState,
CorrectionMode,
)
record = CorrectionAttemptRecord(
plan_id="01ARZ3...",
original_decision_id="01BCD4...",
mode=CorrectionMode.REVERT,
guidance="Use src/api/ not src/app/",
)
# record.state == CorrectionAttemptState.PENDING
# record.correction_attempt_id — auto-generated ULID
```
| Field | Type | Description |
|-------|------|-------------|
| `correction_attempt_id` | `str` (ULID) | Unique identifier (auto-generated) |
| `plan_id` | `str` | Plan that owns the targeted decision tree |
| `original_decision_id` | `str` | Decision node being corrected |
| `new_decision_id` | `str \| None` | New decision created by the correction (if any) |
| `mode` | `CorrectionMode` | `revert` or `append` |
| `guidance` | `str` (max 10,000 chars) | Human-supplied guidance (required, non-empty) |
| `archived_artifacts_path` | `str \| None` | Path to archived artifacts (revert mode) |
| `state` | `CorrectionAttemptState` | Current lifecycle state |
| `created_at` | `datetime` (UTC) | When the record was created |
| `completed_at` | `datetime \| None` | When the attempt completed |
> **Note:** `archived_artifacts_path` is always system-generated within the
> configured artifacts directory. The path is validated to stay inside that
> directory before storage, and no direct user input is accepted for this
> field.
**Validation rules:**
- `plan_id`, `original_decision_id`, and `guidance` must be non-empty (whitespace stripped)
- `new_decision_id` must be non-empty when set
- `created_at` and `completed_at` are normalised to UTC (naive datetimes are coerced)
---
### `CorrectionAttemptState`
Spec-defined lifecycle for correction attempt records:
```
PENDING → EXECUTING → COMPLETE
└──► FAILED
```
| State | Meaning |
|-------|---------|
| `PENDING` | Attempt created, not yet started |
| `EXECUTING` | Correction is actively running |
| `COMPLETE` | Correction completed successfully (terminal) |
| `FAILED` | Correction failed (terminal) |
```python
from cleveragents.domain.models.core.correction import (
CorrectionAttemptState,
CORRECTION_ATTEMPT_VALID_TRANSITIONS,
CORRECTION_ATTEMPT_TERMINAL_STATES,
validate_correction_state_transition,
)
# Check valid transitions
CORRECTION_ATTEMPT_VALID_TRANSITIONS[CorrectionAttemptState.PENDING]
# → frozenset({CorrectionAttemptState.EXECUTING})
# Check terminal states
CorrectionAttemptState.COMPLETE in CORRECTION_ATTEMPT_TERMINAL_STATES
# → True
# Validate a transition (raises ValueError on invalid)
validate_correction_state_transition(
CorrectionAttemptState.PENDING,
CorrectionAttemptState.EXECUTING,
)
```
---
## Cross-Plan Cascade
When a correction targets a decision that spawned child plans, the cascade
mechanism determines what to do with each child plan.
### `ChildPlanState`
Observed state of a child plan for cascade classification:
| State | Meaning |
|-------|---------|
| `NOT_STARTED` | Child plan exists but has not begun executing |
| `IN_PROGRESS` | Child plan is currently executing |
| `COMPLETED_UNAPPLIED` | Child plan finished but changes not yet applied |
| `APPLIED` | Child plan's changes have been applied (blocks revert) |
### `CascadeAction`
```python
from cleveragents.domain.models.core.correction import CascadeAction, ChildPlanState
action = CascadeAction(
child_plan_id="01EFG7...",
child_plan_state=ChildPlanState.IN_PROGRESS,
action="cancel_and_rollback",
sandbox_rolled_back=True,
)
```
| `action` value | Meaning |
|----------------|---------|
| `cancel` | Cancel the child plan (no sandbox rollback needed) |
| `cancel_and_rollback` | Cancel and roll back the child plan's sandbox |
| `reject` | Cannot cascade — child plan is already applied |
### `CorrectionRejection`
Returned when a correction cannot proceed because applied child plans block it:
```python
from cleveragents.domain.models.core.correction import CorrectionRejection
rejection = CorrectionRejection(
correction_id="01ARZ3...",
reason="Child plan 01EFG7... has already been applied and cannot be reverted",
affected_applied_child_plan_ids=["01EFG7..."],
)
```
### `CascadeResult`
Outcome of the full cascade operation:
```python
from cleveragents.domain.models.core.correction import CascadeResult
result = CascadeResult(
correction_id="01ARZ3...",
cascade_actions=[...],
rejected=False,
all_cancelled_plan_ids=["01EFG7..."],
all_rolled_back_plan_ids=["01EFG7..."],
)
```
---
## Using the Correction Service
The `CorrectionService` (in `cleveragents.application.services`) orchestrates
the full correction workflow. Obtain it via the DI container:
```python
service = container.correction_service()
# Dry run — compute impact without executing
report = service.generate_dry_run_report(request)
print(f"Risk: {report.impact.risk_level}")
print(f"Affected decisions: {report.impact.affected_decisions}")
# Execute a revert correction
result = service.execute_revert(request)
if result.status == CorrectionStatus.APPLIED:
print(f"Reverted {len(result.reverted_decisions)} decisions")
print(f"Phase transition target: {result.phase_transition_target}")
# Execute an append correction
result = service.execute_append(request)
if result.spawned_child_plan_id:
print(f"Spawned child plan: {result.spawned_child_plan_id}")
```
---
## CLI Usage
```bash
# Revert to a prior decision point
agents plan correct --plan <PLAN_ID> --decision <DECISION_ID> \
--mode revert \
--guidance "Use src/api/ not src/app/"
# Dry run first to see impact
agents plan correct --plan <PLAN_ID> --decision <DECISION_ID> \
--mode revert --dry-run
# Append new guidance without reverting
agents plan correct --plan <PLAN_ID> --decision <DECISION_ID> \
--mode append \
--guidance "Also add error handling for the 404 case"
```
---
## Database Schema
The `correction_attempts` table stores `CorrectionAttemptRecord` instances:
```sql
CREATE TABLE correction_attempts (
correction_attempt_id TEXT PRIMARY KEY,
plan_id TEXT NOT NULL REFERENCES v3_plans(plan_id) ON DELETE CASCADE,
original_decision_id TEXT NOT NULL REFERENCES decisions(decision_id) ON DELETE RESTRICT,
new_decision_id TEXT REFERENCES decisions(decision_id) ON DELETE RESTRICT,
mode TEXT NOT NULL,
guidance TEXT NOT NULL,
archived_artifacts_path TEXT,
state TEXT NOT NULL DEFAULT 'pending',
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
completed_at TEXT
);
```
**FK semantics:**
- `plan_id``CASCADE` (correction attempts are deleted with the plan)
- `original_decision_id``RESTRICT` (preserves correction audit trail when decisions are cleaned up)
- `new_decision_id``RESTRICT` (consistent with `original_decision_id`)
---
## Testing
The correction module is covered by 53 BDD scenarios in
`features/correction_attempts.feature` and 5 Robot Framework integration
tests in `robot/correction_attempts.robot`.
```python
from cleveragents.domain.models.core.correction import (
CorrectionAttemptRecord,
CorrectionAttemptState,
CorrectionMode,
validate_correction_state_transition,
)
# Create a record
record = CorrectionAttemptRecord(
plan_id="plan-001",
original_decision_id="dec-001",
mode=CorrectionMode.REVERT,
guidance="Fix the path",
)
assert record.state == CorrectionAttemptState.PENDING
# Validate transition
validate_correction_state_transition(
CorrectionAttemptState.PENDING,
CorrectionAttemptState.EXECUTING,
) # OK
validate_correction_state_transition(
CorrectionAttemptState.COMPLETE,
CorrectionAttemptState.EXECUTING,
) # raises ValueError — terminal state
```
---
## Related Documentation
- [ADR-007 Decision Tree & Correction](../adr/ADR-007-decision-tree-and-correction.md)
- [ADR-035 Decision Tree Rollback & Replay](../adr/ADR-035-decision-tree-rollback-and-replay.md)
- [Architecture — Plan Lifecycle](../architecture.md#plan-lifecycle)
- [Sandbox Module](sandbox.md) — checkpoint/rollback used during revert