spec: Decision Recording System — module boundaries, data models, CLI interfaces (v3.2.0) [AUTO-ARCH-1] #8579

Closed
HAL9000 wants to merge 1 commits from spec/decision-recording-system-v3.2.0 into master
+125
View File
@@ -46852,6 +46852,131 @@ This section defines the ordered milestone plan for CleverAgents v3.x, mapping a
---
## Decision Recording System (v3.2.0)
### Overview
The Decision Recording System captures every strategic decision made during the Strategize and Execute phases of the Plan Lifecycle. Each decision is persisted to the database with full context snapshots, enabling users to inspect the decision tree, understand alternatives considered, and correct decisions retroactively.
### Module Boundaries
- **Module**: `cleveragents.decisions`
- **Layer**: Domain (sits between Application and Infrastructure)
- **Responsibilities**:
- Recording decisions with context snapshots during Strategize phase
- Persisting decision trees to the database
- Providing query interfaces for decision retrieval
- Enforcing decision tree integrity constraints
- **Public Interfaces** (what other modules may call):
- `DecisionRecorder` — records a decision during plan execution
- `DecisionRepository` — CRUD operations for decisions (domain repository interface)
- `DecisionTreeQuery` — read-only queries for tree traversal
- **Forbidden Dependencies**: Must not import from `cleveragents.cli` or `cleveragents.tui`
### Data Models
#### Decision Entity
```python
@dataclass
class Decision:
id: UUID
plan_id: UUID
parent_decision_id: Optional[UUID] # None for root decisions
phase: Literal["strategize", "execute"]
decision_type: str # e.g., "tool_selection", "subtask_decomposition", "resource_allocation"
prompt_snapshot: str # Full prompt sent to LLM at decision point
context_snapshot: dict # Serialized context at decision point
chosen_option: str # The option selected
alternatives_considered: list[AlternativeOption]
rationale: str # LLM-provided reasoning
invariants_applied: list[UUID] # Invariant IDs that constrained this decision
created_at: datetime
metadata: dict # Extensible metadata
```
#### AlternativeOption
```python
@dataclass
class AlternativeOption:
option_text: str
rejection_reason: str
```
#### DecisionTree
```python
@dataclass
class DecisionTree:
plan_id: UUID
root_decisions: list[Decision]
def get_subtree(self, decision_id: UUID) -> list[Decision]: ...
def get_path_to_root(self, decision_id: UUID) -> list[Decision]: ...
def get_children(self, decision_id: UUID) -> list[Decision]: ...
```
### Database Schema
```sql
CREATE TABLE decisions (
id UUID PRIMARY KEY,
plan_id UUID NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
parent_decision_id UUID REFERENCES decisions(id),
phase VARCHAR(20) NOT NULL CHECK (phase IN ('strategize', 'execute')),
decision_type VARCHAR(100) NOT NULL,
prompt_snapshot TEXT NOT NULL,
context_snapshot JSONB NOT NULL,
chosen_option TEXT NOT NULL,
alternatives_considered JSONB NOT NULL DEFAULT '[]',
rationale TEXT NOT NULL,
invariants_applied UUID[] NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
metadata JSONB NOT NULL DEFAULT '{}'
);
CREATE INDEX idx_decisions_plan_id ON decisions(plan_id);
CREATE INDEX idx_decisions_parent_id ON decisions(parent_decision_id);
CREATE INDEX idx_decisions_phase ON decisions(phase);
```
### CLI Interface Specification
#### `agents plan tree <plan-id>`
- Renders the decision tree for a plan as a rich tree view
- Shows decision type, chosen option, and timestamp for each node
- Supports `--depth N` to limit tree depth
- Supports `--phase strategize|execute` to filter by phase
- Output format: Rich tree (default) or JSON (`--format json`)
#### `agents plan explain <decision-id>`
- Shows full details for a single decision
- Displays: prompt snapshot, chosen option, all alternatives considered with rejection reasons, rationale, invariants applied
- Output format: Rich panel (default) or JSON (`--format json`)
### Integration Points
- **Strategize Phase Hook**: `DecisionRecorder.record()` is called by the LangGraph strategize node after each LLM decision
- **Execute Phase Hook**: `DecisionRecorder.record()` is called by the LangGraph execute node for tool selection decisions
- **Invariant System**: `InvariantEnforcer` passes applied invariant IDs to `DecisionRecorder`
- **Plan Correction Engine**: `PlanCorrectionEngine` uses `DecisionTreeQuery` to find the subtree to recompute
### Error Handling
- `DecisionNotFoundError(decision_id)` — raised when a decision ID does not exist
- `DecisionTreeCorruptError(plan_id, reason)` — raised when tree integrity is violated
- All errors are domain exceptions, not infrastructure exceptions
### Cross-Cutting Concerns
- **Logging**: All decision recordings logged at DEBUG level with decision ID and plan ID
- **Observability**: Decision count per plan tracked as a metric
- **Performance**: Decision recording must complete within 50ms (non-blocking to plan execution)
### v3.3.0 — Corrections + Subplans + Checkpoints
**Goal**: Plans can spawn child plans (subplans) during execution. Subplans execute in parallel with configurable concurrency limits. Results are merged back using three-way merge strategies. Checkpointing enables rollback to previous plan states.