Files
cleveragents-core/docs/api/decisions.md

349 lines
11 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.
# Decision Recording and Tree API (v3.2.0)
The decision subsystem records every choice point during a plan's Strategize and Execute
phases as a persistent tree of **Decision** nodes. This page documents the CLI commands
and Python API for inspecting and navigating that tree.
---
## Overview
During the **Strategize** phase, the strategy actor records each significant choice as a
`Decision` node. Decisions are linked parent-to-child to form a tree rooted at the
`prompt_definition` node (the original plan prompt). The tree is persisted to SQLite via
`DecisionRepository` and can be queried at any time — even after the plan has completed.
Key capabilities introduced in **v3.2.0**:
- Automatic decision recording for every strategy and execution choice.
- Context snapshots (SHA-256 hash + storage pointer) captured alongside each decision.
- Alternatives-considered tracking for every decision node.
- Full tree rendering via `agents plan tree`.
- Per-decision explanation via `agents plan explain`.
---
## CLI Reference
### `agents plan tree`
Display the decision tree for a plan.
```bash
agents plan tree <PLAN_ID> [OPTIONS]
```
**Options:**
| Flag | Description |
|------|-------------|
| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich` (default: `rich`) |
| `--show-superseded` | Include superseded decisions in the tree |
| `--depth` | Maximum tree depth to render (0 = unlimited, default: 0) |
**Examples:**
```bash
# Default rich tree view
agents plan tree 01HXYZ1234567890ABCDEFGH
# Table format
agents plan tree 01HXYZ1234567890ABCDEFGH --format table
# Include superseded decisions (e.g. after a correction)
agents plan tree 01HXYZ1234567890ABCDEFGH --show-superseded
# Limit depth to 2 levels
agents plan tree 01HXYZ1234567890ABCDEFGH --depth 2
# JSON output for scripting
agents plan tree 01HXYZ1234567890ABCDEFGH --format json
```
**Rich output** renders an indented tree with decision type labels, sequence numbers,
and full 26-character ULIDs so that IDs can be copied directly into follow-up commands
such as `agents plan explain` or `agents plan correct`.
---
### `agents plan explain`
Show full details for a single decision node, including alternatives considered,
context snapshot, and actor reasoning.
```bash
agents plan explain <DECISION_ID> [OPTIONS]
```
**Options:**
| Flag | Description |
|------|-------------|
| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich` |
| `--show-context` | Include context snapshot details (hash, storage ref, resources) |
| `--show-reasoning` | Include rationale and raw actor reasoning trace |
Alternatives considered are **always** included in the output regardless of flags.
**Examples:**
```bash
# Default rich output
agents plan explain 01HXYZ1234567890ABCDEFGH
# JSON with full context snapshot
agents plan explain 01HXYZ1234567890ABCDEFGH --format json --show-context
# Show actor reasoning trace
agents plan explain 01HXYZ1234567890ABCDEFGH --show-reasoning
# YAML with all details
agents plan explain 01HXYZ1234567890ABCDEFGH --format yaml \
--show-context --show-reasoning
```
---
## Decision Data Model
Each decision node stores the following fields.
### Identity
| Field | Type | Description |
|-------|------|-------------|
| `decision_id` | ULID | Auto-generated unique identifier |
| `plan_id` | ULID | Parent plan |
| `parent_decision_id` | ULID \| None | Parent node; `None` for the root |
| `sequence_number` | int | Monotonic order within the plan (0-indexed, never reused) |
### Classification
| Field | Type | Description |
|-------|------|-------------|
| `decision_type` | `DecisionType` | One of 11 enum values (see table below) |
**Decision types:**
| Type | Phase | Description |
|------|-------|-------------|
| `prompt_definition` | Strategize | Root decision — the plan prompt |
| `invariant_enforced` | Strategize | An invariant constraint was applied |
| `strategy_choice` | Strategize | High-level approach chosen |
| `implementation_choice` | Execute | How to implement a specific task |
| `resource_selection` | Execute | Which resources to read / modify |
| `subplan_spawn` | Strategize | Decision to create a child plan |
| `subplan_parallel_spawn` | Strategize | Spawn a group of child plans in parallel |
| `tool_invocation` | Execute | Which skill / tool to use |
| `error_recovery` | Execute | How to handle a failure |
| `validation_response` | Execute | Response to a validation failure |
| `user_intervention` | Any | User-provided guidance / correction |
### Content
| Field | Type | Description |
|-------|------|-------------|
| `question` | str | What question was being answered |
| `chosen_option` | str | The option that was selected |
| `alternatives_considered` | list[str] | Other options that were evaluated |
| `confidence_score` | float \| None | 0.01.0 confidence, or `None` |
### Context Snapshot
Every decision captures a `ContextSnapshot` for replay and correction:
| Field | Type | Description |
|-------|------|-------------|
| `hot_context_hash` | str | SHA-256 hash of the context window at decision time |
| `hot_context_ref` | str | Storage pointer to the full serialised context |
| `relevant_resources` | list[ResourceRef] | Resources in scope at decision time |
| `actor_state_ref` | str | LangGraph actor checkpoint reference |
### Rationale
| Field | Type | Description |
|-------|------|-------------|
| `rationale` | str | Human-readable explanation |
| `actor_reasoning` | str \| None | Raw LLM reasoning trace |
### Downstream Impact
| Field | Type | Description |
|-------|------|-------------|
| `downstream_decision_ids` | list[ULID] | Decisions that depend on this one |
| `downstream_plan_ids` | list[ULID] | Child plans spawned from this decision |
| `artifacts_produced` | list[ArtifactRef] | Artifacts created by this decision |
### Correction Metadata
| Field | Type | Description |
|-------|------|-------------|
| `is_correction` | bool | `True` if this decision corrects another |
| `corrects_decision_id` | ULID \| None | Original decision being corrected |
| `correction_reason` | str \| None | Why the correction was made |
| `superseded_by` | ULID \| None | Decision that replaced this one |
---
## Tree Structure
Decisions form a tree via `parent_decision_id`:
```
prompt_definition (root, parent=None)
├── invariant_enforced
├── strategy_choice
│ ├── implementation_choice
│ │ ├── resource_selection
│ │ └── tool_invocation
│ └── subplan_spawn
└── strategy_choice
```
The `prompt_definition` type is always the root and must have
`parent_decision_id = None`. The **current tree** consists of all decisions where
`superseded_by IS NULL`. Superseded decisions are hidden by default in `agents plan tree`
but can be shown with `--show-superseded`.
---
## Decision Persistence
Decisions are persisted to SQLite via `DecisionRepository` in the
`cleveragents.infrastructure.database` package. The service layer
(`DecisionService`) supports two modes:
| Mode | UnitOfWork | Storage |
|------|------------|---------|
| In-memory | `None` | Internal dicts |
| Persisted | provided | SQLite via `DecisionRepository` + in-memory write-through cache |
When a `UnitOfWork` is wired, mutations are written to the database first, then the
in-memory cache is updated (write-through).
**Python API — recording a decision:**
```python
from cleveragents.application.services.decision_service import DecisionService
from cleveragents.domain.models.core.decision import DecisionType
svc = DecisionService(unit_of_work=uow)
decision = svc.record_decision(
plan_id="01HV...",
decision_type=DecisionType.STRATEGY_CHOICE,
question="Which approach should we take?",
chosen_option="Build a REST API",
alternatives_considered=["GraphQL API", "gRPC service"],
confidence_score=0.85,
rationale="REST is simpler and better supported by existing tooling.",
)
```
**Python API — retrieving the tree:**
```python
# BFS from root(s), level by level
tree = svc.get_tree(plan_id="01HV...")
# Walk from a decision up to the root
path = svc.get_path_to_root(decision_id="01HXYZ...")
# List all decisions for a plan, ordered by sequence number
decisions = svc.list_decisions(plan_id="01HV...")
```
---
## See Also
- [`docs/reference/decision_model.md`](../reference/decision_model.md) — Full domain model reference
- [`docs/reference/decision_service.md`](../reference/decision_service.md) — `DecisionService` API reference
- [`docs/api/plan-corrections.md`](plan-corrections.md) — Correction modes and subplan system
- [ADR-007: Decision Tree & Correction](../adr/ADR-007-decision-tree-and-correction.md)
- [ADR-033: Decision Recording Protocol](../adr/ADR-033-decision-recording-protocol.md)
- [ADR-034: Decision Tree Versioning & History](../adr/ADR-034-decision-tree-versioning-and-history.md)
diff --git a/docs/api/invariants.md b/docs/api/invariants.md
new file mode 100644
index 0000000..2222222
--- /dev/null
+++ b/docs/api/invariants.md
@@ -0,0 +1,274 @@
# Invariant Management API (v3.2.0)
Invariants are natural-language constraints that govern plan execution. They are
evaluated at the start of the Strategize phase by the **Invariant Reconciliation Actor**
and recorded in the decision tree as `invariant_enforced` nodes.
---
## Overview
Invariants introduced in **v3.2.0** provide a declarative way to constrain what a plan
may do. They are scoped (global, project, action, or plan), merged by precedence, and
de-duplicated before enforcement. Violations block the phase transition and emit
`INVARIANT_VIOLATED` events.
Key capabilities:
- Four scope levels: `GLOBAL`, `PROJECT`, `ACTION`, `PLAN`.
- Merge precedence: plan > project > global (action invariants are promoted to plan scope).
- Automatic enforcement at every phase transition via `InvariantReconciliationActor`.
- CLI commands for adding, listing, and removing invariants.
---
## CLI Reference
### `agents invariant add`
Create a new invariant constraint.
```bash
agents invariant add <NAME> --description <DESC> [SCOPE_FLAG]
```
**Scope flags:**
| Flag | Scope | Description |
|------|-------|-------------|
| `--global` | `GLOBAL` | Applies to every plan in the system |
| `--project <NAME>` | `PROJECT` | Applies to plans targeting the named project |
| `--plan <PLAN_ID>` | `PLAN` | Attached directly to a specific plan |
| `--action <ACTION>` | `ACTION` | Defined in an action template; promoted on `plan use` |
**Examples:**
```bash
# Global invariant
agents invariant add --global "Never delete production data"
# Project-scoped invariant
agents invariant add --project myapp "All API changes need tests"
# Plan-specific invariant
agents invariant add --plan 01HXYZ... "Use Python 3.13 only"
# Action-scoped invariant
agents invariant add --action local/code-coverage "Minimum 80% coverage"
```
---
### `agents invariant list`
Display invariants, optionally filtered by scope or project.
```bash
agents invariant list [PATTERN] [OPTIONS]
```
**Options:**
| Flag | Description |
|------|-------------|
| `--global` | Show only global invariants |
| `--project <NAME>` | Show only invariants for the named project |
| `--effective --project <NAME>` | Show the merged effective set for a project |
| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich` |