11 KiB
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.
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:
# 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.
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:
# 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.0–1.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:
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:
# 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— Full domain model referencedocs/reference/decision_service.md—DecisionServiceAPI referencedocs/api/plan-corrections.md— Correction modes and subplan system- ADR-007: Decision Tree & Correction
- ADR-033: Decision Recording Protocol
- ADR-034: Decision Tree Versioning & History 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.
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:
# 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.
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 |