diff --git a/CHANGELOG.md b/CHANGELOG.md index ade2d531a..1d505d0af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,34 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Documentation + +- **CLI Reference: Decisions (v3.2.0)**: Added `docs/api/decisions.md` documenting + `agents plan tree` and `agents plan explain` commands, decision recording during + the Strategize phase, the full decision data model (id, plan_id, phase, + context_snapshot, alternatives), and the `DecisionService` Python API. + +- **CLI Reference: Invariants (v3.2.0)**: Added `docs/api/invariants.md` documenting + `agents invariant add`, `agents invariant list`, and `agents invariant remove` + commands, the invariant scope hierarchy (global/project/action/plan), merge + precedence rules, enforcement records, violation model, and the `InvariantService` + Python API. + +- **CLI Reference: Checkpoints (v3.3.0)**: Added `docs/api/checkpoints.md` documenting + `agents plan checkpoint list` and `agents plan rollback ` commands, + automatic checkpoint triggers (`on_tool_write`, `on_tool_write_complete`, + `on_subplan_spawn`, `on_error`), the checkpoint data model, retention policy, and + the `CheckpointService` Python API. + +- **CLI Reference: Plan Corrections & Subplans (v3.3.0)**: Added + `docs/api/plan-corrections.md` documenting `agents plan correct --mode=revert` + and `agents plan correct --mode=append` commands, the correction data model, + `CorrectionAttemptRecord` lifecycle, and a subplan system overview covering + execution modes, merge strategies, and the `SubplanService` Python API. + +- **API Index Update**: Extended `docs/api/index.md` with a new "Plan Intelligence + (v3.2.0 / v3.3.0)" section linking to all four new API reference pages. + ### Fixed - **Automation Profile Silent Fallback** (#8232): `_resolve_profile_for_plan` in diff --git a/docs/api/checkpoints.md b/docs/api/checkpoints.md new file mode 100644 index 000000000..4370e9d96 --- /dev/null +++ b/docs/api/checkpoints.md @@ -0,0 +1,276 @@ +# Checkpoints API Reference + +Checkpoints allow operators to snapshot sandbox state during plan execution and +restore it later. They are used for recovering from mistakes, reverting tool +side-effects, and supporting the decision-correction revert flow. + +> **Version:** Introduced in v3.3.0. Automatic checkpoint triggers added in v3.8.0. +> **See also:** [`docs/reference/checkpointing.md`](../reference/checkpointing.md) + +--- + +## Concept and Purpose + +A **Checkpoint** is an immutable record of sandbox state at a specific point in +time. The execution engine creates checkpoints automatically at key moments +(before and after write-tool executions, before subplan spawning, and on +errors). Operators can also roll back to any checkpoint via the CLI. + +### Automatic Checkpoint Triggers (v3.8.0+) + +| Trigger | When | +|--------------------------|---------------------------------------------------| +| `on_tool_write` | Before each write-tool execution | +| `on_tool_write_complete` | After each write-tool execution | +| `on_subplan_spawn` | Before first subplan execution attempt | +| `on_error` | When the Execute phase fails | + +All four triggers are enabled by default. See +[Configuration](#configuration) to disable specific triggers. + +--- + +## CLI Commands + +### `agents plan checkpoint list` + +Lists all checkpoints available for a plan. + +#### Synopsis + +```bash +agents plan checkpoint list [OPTIONS] +``` + +#### Options + +| Flag | Description | +|-------------------|-------------------------------------------------------| +| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich`| + +#### Examples + +```bash +# List checkpoints for a plan (rich table) +agents plan checkpoint list 01HXYZ1234567890ABCDEFGH + +# JSON output for scripting +agents plan checkpoint list 01HXYZ1234567890ABCDEFGH --format json +``` + +#### Output (rich) + +The rich renderer displays a table with columns: + +| Column | Description | +|-----------------|-------------------------------------------------------| +| ID | Truncated checkpoint ULID | +| Type | `pre_write`, `post_step`, or `manual` | +| Decision | Aligned decision ULID (if set) | +| Size | Checkpoint data size in bytes | +| Created | Creation timestamp (UTC) | +| Phase | Plan phase when checkpoint was created | + +--- + +### `agents plan rollback` + +Rolls back sandbox state to a previously captured checkpoint. + +#### Synopsis + +```bash +agents plan rollback [--yes|-y] [OPTIONS] +``` + +#### Arguments + +| Argument | Description | +|------------------|-------------------------------------------------------| +| `PLAN_ID` | ULID of the plan to roll back | +| `CHECKPOINT_ID` | ULID of the checkpoint to restore | + +#### Options + +| Flag | Description | +|-------------------|-------------------------------------------------------| +| `--yes`, `-y` | Skip the interactive confirmation prompt | +| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich`| + +#### Examples + +```bash +# Interactive rollback (prompts for confirmation) +agents plan rollback 01HPLAN... 01HCHKPT... + +# Skip confirmation (useful in scripts) +agents plan rollback --yes 01HPLAN... 01HCHKPT... + +# JSON output +agents plan rollback --yes 01HPLAN... 01HCHKPT... --format json +``` + +#### JSON Output Envelope + +```json +{ + "rollback_summary": { + "plan_id": "01HPLAN...", + "from_checkpoint_id": "01HCHKPT...", + "restored_files_count": 3 + }, + "changes_reverted": [ + "path/to/file1.py", + "path/to/file2.py", + "path/to/file3.py" + ], + "impact": { + "files_affected": 3 + }, + "post_rollback_state": { + "active_checkpoint": "01HCHKPT...", + "plan_id": "01HPLAN..." + }, + "timing": { + "elapsed_seconds": 0.042 + }, + "messages": ["Rollback completed successfully."] +} +``` + +#### Error Cases + +| Error | Cause | +|----------------------------------------------|----------------------------------------------------| +| `Rollback blocked: plan is already applied` | Plan has reached the terminal `applied` state | +| `Rollback blocked: sandbox is missing` | Sandbox was cleaned up before rollback | +| `Not found: checkpoint` | Checkpoint ID does not exist | +| `Validation Error` | Checkpoint does not belong to the specified plan | + +--- + +## Configuration + +Automatic checkpoint triggers are configured via `core.checkpoints.auto_create_on`: + +```toml +[core.checkpoints] +auto_create_on = ["on_tool_write", "on_tool_write_complete", "on_subplan_spawn", "on_error"] +``` + +To disable a specific trigger, remove it from the list: + +```toml +[core.checkpoints] +# Disable post-write checkpoints to reduce storage usage +auto_create_on = ["on_tool_write", "on_subplan_spawn", "on_error"] +``` + +To disable all automatic checkpoints: + +```toml +[core.checkpoints] +auto_create_on = [] +``` + +--- + +## Checkpoint Data Model + +**Module:** `cleveragents.domain.models.core.checkpoint` + +| Field | Type | Description | +|--------------------|-----------------|-----------------------------------------------------| +| `checkpoint_id` | `str` (ULID) | Unique identifier | +| `plan_id` | `str` (ULID) | The plan that owns this checkpoint | +| `sandbox_ref` | `str` | Git commit hash or patch reference | +| `decision_id` | `str \| None` | Optional decision ULID this checkpoint is aligned to| +| `checkpoint_type` | `CheckpointType`| `pre_write`, `post_step`, or `manual` | +| `resource_id` | `str \| None` | Optional resource ULID association | +| `filesystem_path` | `str` | Relative path within the checkpoint directory | +| `size_bytes` | `int` | Size of the checkpoint data in bytes | +| `created_at` | `datetime` | UTC creation timestamp | +| `metadata` | `CheckpointMetadata` | Audit metadata (reason, source tool, phase) | + +### `CheckpointMetadata` + +| Field | Type | Description | +|---------------|--------------|-----------------------------------------------------| +| `reason` | `str` | Why the checkpoint was created | +| `source_tool` | `str \| None`| Which tool triggered the checkpoint | +| `phase` | `str \| None`| Which plan phase was active | +| `extra` | `dict` | Arbitrary key-value pairs for extension | + +### Retention Policy + +A `CheckpointRetentionPolicy` governs how many checkpoints a plan may keep: + +| Field | Default | Range | +|-------------------|---------|---------| +| `max_checkpoints` | 50 | 1–100 | +| `auto_prune` | `true` | | + +When `auto_prune` is enabled and the checkpoint count exceeds `max_checkpoints`, +the oldest **interior** checkpoints are automatically removed. The first +(earliest) and most recent checkpoints are always preserved. + +--- + +## Python API + +### `CheckpointService` + +**Module:** `cleveragents.application.services.checkpoint_service` + +```python +from cleveragents.application.services.checkpoint_service import CheckpointService +from cleveragents.domain.models.core.checkpoint import CheckpointType + +svc = CheckpointService(repository=repo, settings=settings) + +# Create a manual checkpoint +checkpoint = svc.create_checkpoint( + plan_id="01HPLAN...", + sandbox_ref="abc123def456", + checkpoint_type=CheckpointType.MANUAL, + decision_id=None, + metadata={"reason": "pre-migration snapshot", "phase": "execute"}, +) + +# List checkpoints for a plan +checkpoints = svc.list_checkpoints(plan_id="01HPLAN...") + +# Get a specific checkpoint +cp = svc.get_checkpoint(checkpoint_id="01HCHKPT...") + +# Roll back to a checkpoint +result = svc.rollback_to_checkpoint( + plan_id="01HPLAN...", + checkpoint_id="01HCHKPT...", +) +# result.restored_files_count, result.changed_paths, result.from_checkpoint_id + +# Delete a checkpoint +svc.delete_checkpoint(checkpoint_id="01HCHKPT...") +``` + +### Dependency Injection + +The `CheckpointService` is registered in the DI container and can be obtained +via: + +```python +from cleveragents.di import get_container + +container = get_container() +checkpoint_service = container.checkpoint_service() +``` + +--- + +## Related Documentation + +- [Checkpointing and Rollback Reference](../reference/checkpointing.md) — full reference +- [Decision Correction Reference](../reference/decision_correction.md) — correction-service integration +- [Plan CLI Reference](../reference/plan_cli.md) — plan commands overview +- [Subplan Service](../reference/subplan_service.md) — `on_subplan_spawn` trigger diff --git a/docs/api/decisions.md b/docs/api/decisions.md new file mode 100644 index 000000000..9c6a50e39 --- /dev/null +++ b/docs/api/decisions.md @@ -0,0 +1,259 @@ +# Decisions API Reference + +The decisions subsystem records every choice point in a plan's lifecycle as a +**Decision** node in a persistent tree. Decisions are created during the +Strategize and Execute phases and form the basis for targeted correction and +replay. + +> **Version:** Introduced in v3.2.0. +> **See also:** [`docs/reference/decision_model.md`](../reference/decision_model.md), +> [`docs/reference/decision_service.md`](../reference/decision_service.md), +> [`docs/reference/decision_correction.md`](../reference/decision_correction.md) + +--- + +## CLI Commands + +### `agents plan tree` + +Renders the full decision tree for a plan, showing every recorded choice point +and how they relate to one another. + +#### Synopsis + +```bash +agents plan tree [OPTIONS] +``` + +#### Options + +| Flag | Description | +|----------------------|---------------------------------------------------------------| +| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich` | +| `--show-superseded` | Include superseded (corrected) decisions in the tree | +| `--depth` | Maximum tree depth to display (`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 (useful when reviewing corrections) +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 +``` + +#### Output (rich) + +The rich renderer displays a tree rooted at the `prompt_definition` decision. +Each node shows: + +- Decision ID (truncated ULID) +- Decision type (e.g., `strategy_choice`, `implementation_choice`) +- Chosen option (truncated) +- Confidence score (if set) +- Superseded indicator (`[superseded]`) when `--show-superseded` is active + +--- + +### `agents plan explain` + +Shows full details for a single decision, including the question that was +answered, the chosen option, all alternatives considered, and optional context +snapshot and actor reasoning. + +#### Synopsis + +```bash +agents plan explain [OPTIONS] +``` + +#### Options + +| Flag | Description | +|----------------------|---------------------------------------------------------------| +| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich` | +| `--show-context` | Include context snapshot details (hash, resource refs) | +| `--show-reasoning` | Include rationale and raw actor reasoning trace | + +Alternatives considered are always included in the output. + +#### 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 output with all details +agents plan explain 01HXYZ1234567890ABCDEFGH --format yaml \ + --show-context --show-reasoning +``` + +--- + +## Decision Recording During Strategize + +During the **Strategize** phase, the strategy actor records decisions for every +significant choice it makes. The decision tree is built incrementally: + +1. A `prompt_definition` root decision is created when the plan is initialised. +2. An `invariant_enforced` decision is recorded for each invariant applied by + the Invariant Reconciliation Actor. +3. One or more `strategy_choice` decisions capture the high-level approach. +4. `subplan_spawn` or `subplan_parallel_spawn` decisions are recorded when the + strategy decomposes work into child plans. + +During the **Execute** phase, `implementation_choice`, `resource_selection`, +`tool_invocation`, `error_recovery`, and `validation_response` decisions are +added as children of the relevant strategy decisions. + +--- + +## Decision Data Model + +**Module:** `cleveragents.domain.models.core.decision` + +### Identity Fields + +| Field | Type | Description | +|------------------------|-----------------|-----------------------------------------------------| +| `decision_id` | `str` (ULID) | Auto-generated unique identifier | +| `plan_id` | `str` (ULID) | Parent plan (required) | +| `parent_decision_id` | `str \| None` | Parent decision; `None` for the root | +| `sequence_number` | `int` | Monotonic order within the plan (0-indexed) | + +### Classification + +| Field | Type | Description | +|-----------------|----------------|------------------------------------------------------| +| `decision_type` | `DecisionType` | One of 11 enum values (see table below) | + +#### `DecisionType` Values + +| Value | 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 Fields + +| Field | Type | Description | +|--------------------------|-----------------|-----------------------------------------------------| +| `question` | `str` | What question was being answered (required) | +| `chosen_option` | `str` | The option that was selected (required) | +| `alternatives_considered`| `list[str]` | Other options evaluated | +| `confidence_score` | `float \| None` | 0.0–1.0 confidence, or `None` | + +### Context Snapshot + +Every decision captures a `ContextSnapshot` for replay: + +| Field | Type | Description | +|---------------------|--------------|--------------------------------------------------| +| `hot_context_hash` | `str` | SHA-256 hash of the context window | +| `hot_context_ref` | `str` | Storage pointer to the full serialised context | +| `relevant_resources`| `list[ResourceRef]` | Resources referenced at decision time | +| `actor_state_ref` | `str \| None`| LangGraph actor checkpoint reference | + +### Rationale Fields + +| Field | Type | Description | +|------------------|--------------|-----------------------------------------------------| +| `rationale` | `str` | Human-readable explanation | +| `actor_reasoning`| `str \| None`| Raw LLM reasoning trace, if available | + +### Correction Metadata + +| Field | Type | Description | +|------------------------|-----------------|-------------------------------------------------| +| `is_correction` | `bool` | `True` if this decision corrects another | +| `corrects_decision_id` | `str \| None` | ULID of the original decision | +| `correction_reason` | `str \| None` | Why the correction was made | +| `superseded_by` | `str \| None` | ULID of the decision that replaced this one | + +--- + +## Python API + +### `DecisionService` + +**Module:** `cleveragents.application.services.decision_service` + +```python +from cleveragents.application.services.decision_service import DecisionService +from cleveragents.domain.models.core.decision import DecisionType + +svc = DecisionService(settings=settings, unit_of_work=uow) + +# Record a decision during Strategize +decision = svc.record_decision( + plan_id="01HV...", + decision_type=DecisionType.STRATEGY_CHOICE, + question="Which approach should we use?", + chosen_option="Build a REST API with FastAPI", + alternatives_considered=["GraphQL", "gRPC"], + confidence_score=0.85, + rationale="REST is simpler and better supported by existing tooling.", +) + +# Retrieve the full decision tree (BFS, level by level) +tree = svc.get_tree(plan_id="01HV...") + +# Get a single decision +d = svc.get_decision(decision_id="01HXYZ...") + +# List all decisions for a plan (ordered by sequence number) +decisions = svc.list_decisions(plan_id="01HV...") + +# List decisions of a specific type +strategy_decisions = svc.list_by_type( + plan_id="01HV...", + decision_type=DecisionType.STRATEGY_CHOICE, +) + +# Walk from a decision up to the root +path = svc.get_path_to_root(decision_id="01HXYZ...") +``` + +### Exceptions + +| Exception | When raised | +|-------------------------|--------------------------------------------------| +| `DecisionNotFoundError` | Decision ID does not exist | +| `DuplicateDecisionError`| Decision ID already exists | +| `SequenceConflictError` | Sequence number already used for this plan | +| `ValidationError` | Required fields missing or empty | + +--- + +## Related Documentation + +- [Decision Domain Model](../reference/decision_model.md) — full field reference +- [Decision Service Reference](../reference/decision_service.md) — service API details +- [Decision Correction Reference](../reference/decision_correction.md) — correction modes +- [Plan CLI Reference](../reference/plan_cli.md) — `agents plan tree` / `agents plan explain` +- [Subplan Service](../reference/subplan_service.md) — spawn decisions diff --git a/docs/api/index.md b/docs/api/index.md index 4eee7341e..56e91bd9d 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -19,6 +19,19 @@ classes, functions, and exceptions with signatures and usage examples. | [`cleveragents.providers`](providers.md) | AI provider registry — discovery, selection, LLM factory, and capability metadata | | [`cleveragents.tui`](tui.md) | Interactive Terminal UI — app, persona system, input routing, slash commands, session export/import | +## Plan Intelligence (v3.2.0 / v3.3.0) + +The following pages document the CLI commands and Python APIs introduced in +v3.2.0 (Decisions & Invariants) and v3.3.0 (Corrections, Subplans & +Checkpoints). + +| Page | Description | +|------|-------------| +| [Decisions](decisions.md) | `agents plan tree`, `agents plan explain`, decision recording during Strategize, decision data model and Python API | +| [Invariants](invariants.md) | `agents invariant add/list/remove`, invariant scopes, enforcement, data model and Python API | +| [Checkpoints](checkpoints.md) | `agents plan checkpoint list`, `agents plan rollback`, automatic triggers, data model and Python API | +| [Plan Corrections](plan-corrections.md) | `agents plan correct --mode=revert\|append`, correction data model, subplan system overview and Python API | + > **Note:** Internal modules (prefixed with `_`) and implementation details > not listed in a module's `__all__` are considered private and may change > without notice. diff --git a/docs/api/invariants.md b/docs/api/invariants.md new file mode 100644 index 000000000..4bdeebc82 --- /dev/null +++ b/docs/api/invariants.md @@ -0,0 +1,314 @@ +# Invariants API Reference + +Invariants are natural-language constraints on plan execution. They flow into +the decision tree during the Strategize phase and are reconciled by the +Invariant Reconciliation Actor before any strategy work begins. + +> **Version:** Introduced in v3.2.0. +> **See also:** [`docs/reference/invariants.md`](../reference/invariants.md) + +--- + +## Concept and Purpose + +An **invariant** is a constraint that must hold throughout plan execution. +Examples: + +- `"Never delete production data"` +- `"All API changes need tests"` +- `"Use Python 3.13 only"` + +Invariants are evaluated at the start of the Strategize phase. If any +invariant is violated during execution, an `INVARIANT_VIOLATED` event is +emitted and the phase transition is blocked. + +### Scope Hierarchy + +| Scope | Description | +|-----------|----------------------------------------------------------| +| `GLOBAL` | Applies to every plan in the system | +| `PROJECT` | Applies to plans targeting a specific project | +| `ACTION` | Defined in an action template; promoted on `plan use` | +| `PLAN` | Attached directly to a specific plan | + +### Merge Precedence + +When computing the effective invariant set for a plan: + +``` +plan > project > global +``` + +Action-level invariants are promoted to plan scope when `plan use` is called. +Invariants are de-duplicated by text (case-insensitive); when the same +constraint appears at multiple scopes, only the highest-precedence copy is kept. + +--- + +## CLI Commands + +### `agents invariant add` + +Creates a new invariant at the specified scope. + +#### Synopsis + +```bash +agents invariant add [SCOPE_FLAG] [OPTIONS] +``` + +#### Scope Flags (mutually exclusive) + +| Flag | Description | +|-------------------|-------------------------------------------------------| +| `--global` | Create a global invariant (applies to all plans) | +| `--project NAME` | Create a project-scoped invariant | +| `--plan PLAN_ID` | Create a plan-scoped invariant | +| `--action NAME` | Create an action-scoped invariant | + +#### Options + +| Flag | Description | +|-------------------|-------------------------------------------------------| +| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich`| + +#### Examples + +```bash +# Global invariant (applies to every plan) +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" + +# JSON output +agents invariant add --global "No hardcoded secrets" --format json +``` + +#### Output + +On success, the command prints the created invariant's ID and text: + +``` +✓ OK Invariant 01HABC... created + Scope: global + Text: Never delete production data +``` + +--- + +### `agents invariant list` + +Displays invariants, optionally filtered by scope or matched by regex. + +#### Synopsis + +```bash +agents invariant list [REGEX] [OPTIONS] +``` + +#### Options + +| Flag | Description | +|-------------------------|---------------------------------------------------------------| +| `--global` | Show only global invariants | +| `--project NAME` | Show only invariants for the specified project | +| `--plan PLAN_ID` | Show only invariants for the specified plan | +| `--action NAME` | Show only invariants for the specified action | +| `--effective` | Show the merged effective set (requires `--project`) | +| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich` | + +#### Examples + +```bash +# List all active invariants +agents invariant list + +# Filter by scope +agents invariant list --global +agents invariant list --project myapp + +# Show merged effective set for a project +agents invariant list --effective --project myapp + +# Filter by regex +agents invariant list "data.*safe" + +# JSON output +agents invariant list --format json +``` + +#### Output (rich) + +The rich renderer displays a table with columns: + +| Column | Description | +|-------------|---------------------------------------------------| +| ID | Truncated invariant ULID | +| Scope | `global`, `project`, `action`, or `plan` | +| Scope Target| Project/plan/action name (if applicable) | +| Text | Invariant constraint text | +| Created | Creation timestamp | + +--- + +### `agents invariant remove` + +Removes an invariant by ID. + +#### Synopsis + +```bash +agents invariant remove [--yes|-y] [OPTIONS] +``` + +#### Options + +| Flag | Description | +|-------------------|-------------------------------------------------------| +| `--yes`, `-y` | Skip the interactive confirmation prompt | +| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich`| + +#### Examples + +```bash +# With confirmation prompt +agents invariant remove 01HXYZ... + +# Skip confirmation (useful in scripts) +agents invariant remove --yes 01HXYZ... +``` + +#### Notes + +- Removing a `GLOBAL` or `PROJECT` invariant does not retroactively affect + plans that have already been strategized — the invariant was already + evaluated and recorded in the decision tree. +- Removing a `PLAN`-scoped invariant from a plan that is still in the + Strategize phase will affect the next reconciliation run. + +--- + +## Invariant Data Model + +**Module:** `cleveragents.domain.models.core.invariant` + +| Field | Type | Description | +|---------------|---------------|-----------------------------------------------------| +| `id` | `str` (ULID) | Auto-generated unique identifier | +| `name` | `str` | Short human-readable label (optional) | +| `description` | `str` | The constraint text (required, non-empty) | +| `scope` | `InvariantScope` | `GLOBAL`, `PROJECT`, `ACTION`, or `PLAN` | +| `scope_target`| `str \| None` | Project name, plan ID, or action name (scope-dependent) | +| `created_at` | `datetime` | UTC creation timestamp | +| `is_active` | `bool` | Whether the invariant is currently enforced | + +### `InvariantScope` Enum + +```python +from cleveragents.domain.models.core.invariant import InvariantScope + +InvariantScope.GLOBAL # "global" +InvariantScope.PROJECT # "project" +InvariantScope.ACTION # "action" +InvariantScope.PLAN # "plan" +``` + +### Enforcement Record + +When the Invariant Reconciliation Actor evaluates an invariant, it creates an +`InvariantEnforcementRecord`: + +| Field | Description | +|------------------|--------------------------------------------------| +| `invariant_id` | ULID of the invariant | +| `enforced` | Whether the invariant was successfully enforced | +| `actor_response` | Response text from the reconciliation actor | +| `decision_id` | ULID of the associated `invariant_enforced` decision | + +### Violation Model + +When an invariant is violated during execution, an `InvariantViolation` is +created: + +| Field | Description | +|-----------------|---------------------------------------------------| +| `invariant_id` | ULID of the violated invariant | +| `violated_text` | The invariant text that was violated | +| `severity` | `error`, `warning`, or `info` | +| `details` | Additional violation context | + +--- + +## Python API + +### `InvariantService` + +**Module:** `cleveragents.application.services.invariant_service` + +```python +from cleveragents.application.services.invariant_service import InvariantService +from cleveragents.domain.models.core.invariant import InvariantScope + +svc = InvariantService(unit_of_work=uow) + +# Create a global invariant +inv = svc.create_invariant( + description="Never delete production data", + scope=InvariantScope.GLOBAL, +) + +# Create a project-scoped invariant +inv = svc.create_invariant( + description="All API changes need tests", + scope=InvariantScope.PROJECT, + scope_target="myapp", +) + +# List all invariants +all_invariants = svc.list_invariants() + +# List by scope +global_invariants = svc.list_invariants(scope=InvariantScope.GLOBAL) +project_invariants = svc.list_invariants( + scope=InvariantScope.PROJECT, + scope_target="myapp", +) + +# Get the merged effective set for a project +effective = svc.get_effective_invariants(project_name="myapp") + +# Remove an invariant +svc.remove_invariant(invariant_id="01HXYZ...") +``` + +### `InvariantReconciliationActor` + +The reconciliation actor runs automatically at every plan phase transition. +It is wired into the plan lifecycle and does not need to be invoked manually. + +```python +# The actor is resolved from the DI container: +from cleveragents.di import get_container + +container = get_container() +actor = container.invariant_reconciliation_actor() + +# Manually trigger reconciliation (advanced use): +result = await actor.reconcile(plan=plan, context=ctx) +``` + +--- + +## Related Documentation + +- [Invariants Reference](../reference/invariants.md) — scope hierarchy, merge precedence, enforcement +- [Decision Model](../reference/decision_model.md) — `invariant_enforced` decision type +- [Plan CLI Reference](../reference/plan_cli.md) — `--invariant` flag on `agents plan use` +- [Automation Profiles](../reference/automation_profiles.md) — profile-level invariant interaction diff --git a/docs/api/plan-corrections.md b/docs/api/plan-corrections.md new file mode 100644 index 000000000..866f05a5e --- /dev/null +++ b/docs/api/plan-corrections.md @@ -0,0 +1,354 @@ +# Plan Corrections API Reference + +The plan correction subsystem allows operators to modify a plan's decision tree +after execution by either **reverting** a subtree of decisions or **appending** +new decisions as a child plan. This enables targeted re-execution without +discarding the entire plan. + +> **Version:** Introduced in v3.3.0. Correction attempts tracking added in v3.8.0. +> **See also:** [`docs/reference/decision_correction.md`](../reference/decision_correction.md), +> [`docs/reference/subplans.md`](../reference/subplans.md), +> [`docs/reference/subplan_service.md`](../reference/subplan_service.md) + +--- + +## Concept + +### Correction Modes + +#### Revert Mode + +Invalidates the targeted decision and every descendant reachable via BFS +traversal. Associated artifacts are archived and affected child plans are +rolled back. + +```text + ┌─── D1 (target) ◄── revert starts here + │ │ + │ ┌──┴──┐ + │ D2 D3 ← all invalidated + │ │ + │ D4 ← also invalidated +``` + +Use revert when the original decision was wrong and you want to re-execute +from that point with different guidance. + +#### Append Mode + +Preserves the original decision and spawns a **new child plan** rooted at the +target node. The child plan carries the operator's guidance and produces +additional decisions without disturbing the existing tree. + +```text + D1 (target) + │ + ┌──┴──────────┐ + D2 (original) CP-new ← child plan appended +``` + +Use append when the original decision was correct but you want to add +supplementary work without recomputing. + +--- + +## CLI Commands + +### `agents plan correct --mode=revert` + +Re-executes from a targeted decision point by invalidating the decision subtree +and re-running from that node with new guidance. + +#### Synopsis + +```bash +agents plan correct --mode=revert [OPTIONS] +``` + +#### Arguments + +| Argument | Description | +|---------------|-------------------------------------------------------| +| `PLAN_ID` | ULID of the plan to correct | +| `DECISION_ID` | ULID of the decision to revert from | + +#### Options + +| Flag | Description | +|-----------------------|-------------------------------------------------------| +| `--mode` | Correction mode: `revert` or `append` (required) | +| `--guidance TEXT` | Operator guidance for the re-execution (required) | +| `--dry-run` | Preview impact without making changes | +| `--yes`, `-y` | Skip the interactive confirmation prompt | +| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich`| + +#### Examples + +```bash +# Revert a decision and re-execute with guidance +agents plan correct --mode=revert \ + --guidance "Use a safer migration approach with explicit rollback" \ + 01HPLAN... 01HDECISION... + +# Preview impact before committing +agents plan correct --mode=revert --dry-run \ + --guidance "Use a safer migration approach" \ + 01HPLAN... 01HDECISION... + +# Skip confirmation prompt +agents plan correct --mode=revert --yes \ + --guidance "Use a safer migration approach" \ + 01HPLAN... 01HDECISION... +``` + +#### Dry-Run Output + +A dry-run report includes: + +- **impact** — Full `CorrectionImpact` with affected decisions, files, child + plans, estimated cost, and risk level +- **decisions_to_invalidate** — Decision IDs that *would* be marked invalid +- **child_plans_to_rollback** — Child plans that *would* be rolled back +- **estimated_recompute_time_seconds** — Wall-clock estimate +- **warnings** — Human-readable cautions (e.g., high risk) + +#### Risk Classification + +| Affected Decisions | Risk Level | +|--------------------|------------| +| ≤ 3 | low | +| 4 – 10 | medium | +| > 10 | high | + +--- + +### `agents plan correct --mode=append` + +Adds guidance to a plan by spawning a new child plan rooted at the target +decision, without recomputing the existing decision tree. + +#### Synopsis + +```bash +agents plan correct --mode=append [OPTIONS] +``` + +#### Arguments + +| Argument | Description | +|---------------|-------------------------------------------------------| +| `PLAN_ID` | ULID of the plan to correct | +| `DECISION_ID` | ULID of the decision to append from | + +#### Options + +| Flag | Description | +|-----------------------|-------------------------------------------------------| +| `--mode` | Correction mode: `revert` or `append` (required) | +| `--guidance TEXT` | Operator guidance for the child plan (required) | +| `--dry-run` | Preview impact without making changes | +| `--yes`, `-y` | Skip the interactive confirmation prompt | +| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich`| + +#### Examples + +```bash +# Append a child plan with additional guidance +agents plan correct --mode=append \ + --guidance "Also add integration tests for the new endpoint" \ + 01HPLAN... 01HDECISION... + +# Preview what would be created +agents plan correct --mode=append --dry-run \ + --guidance "Also add integration tests" \ + 01HPLAN... 01HDECISION... +``` + +--- + +## Correction Status Lifecycle + +```text +PENDING → ANALYZING → EXECUTING → APPLIED + → FAILED + PENDING → CANCELLED + ANALYZING → CANCELLED +``` + +--- + +## Correction Data Model + +**Module:** `cleveragents.domain.models.core.correction` + +| Field | Type | Description | +|------------------------|-------------------|-------------------------------------------------| +| `correction_id` | `str` (ULID) | Unique identifier | +| `plan_id` | `str` (ULID) | Plan being corrected | +| `target_decision_id` | `str` (ULID) | Decision being corrected | +| `mode` | `CorrectionMode` | `revert` or `append` | +| `guidance` | `str` | Operator guidance text (1–10,000 chars) | +| `status` | `CorrectionStatus`| Current status (see lifecycle above) | +| `created_at` | `datetime` | UTC creation timestamp | +| `completed_at` | `datetime \| None`| UTC completion timestamp (terminal states only) | + +### `CorrectionAttemptRecord` (v3.8.0+) + +Each execution of a correction is tracked as a `CorrectionAttemptRecord`. +Multiple attempts may exist for a single correction (e.g., if a first attempt +fails and the operator retries). + +| Field | Type | Description | +|--------------------------|-------------------------|-------------------------------------------------| +| `id` | `str` (ULID) | Unique identifier for this attempt | +| `correction_id` | `str` (ULID) | Parent correction ULID | +| `plan_id` | `str` (ULID) | Plan this correction targets | +| `original_decision_id` | `str` (ULID) | Decision being corrected | +| `new_decision_id` | `str \| None` | Replacement decision (set on success) | +| `mode` | `CorrectionMode` | `revert` or `append` | +| `state` | `CorrectionAttemptState`| `pending`, `executing`, `complete`, or `failed` | +| `guidance` | `str` | Operator guidance text | +| `created_at` | `datetime` | UTC creation timestamp | +| `completed_at` | `datetime \| None` | UTC completion timestamp | +| `archived_artifacts_path`| `str \| None` | Path to archived artifacts (revert mode) | + +--- + +## Subplan System Overview + +Subplans allow a parent plan to decompose work into coordinated child plans. +Each child plan runs in its own sandbox, and results are merged back into the +parent plan using a configurable merge strategy. + +### How Subplans Are Spawned + +During the **Strategize** phase, the strategy actor may decide to decompose +work by recording `subplan_spawn` or `subplan_parallel_spawn` decisions. The +`SubplanService` then: + +1. Extracts spawn decisions from the `DecisionService` +2. Builds `SpawnEntry` objects from those decisions +3. Validates the spawn request (resource scopes, merge strategy, parallelism) +4. Creates `SubplanStatus` and `SpawnMetadata` for each entry +5. Returns the result for the caller to persist on the parent plan + +### Execution Modes + +| Mode | Description | +|-----------------------|--------------------------------------------------| +| `sequential` | Execute one at a time in order | +| `parallel` | Execute concurrently (up to `max_parallel`) | +| `dependency_ordered` | Respect DAG dependencies via topological sort | + +### Merge Strategies + +After subplans complete, their sandbox outputs are merged: + +| Strategy | Description | +|-----------------------|--------------------------------------------------| +| `git_three_way` | Three-way merge via `git merge-file` | +| `sequential_apply` | Apply changes in completion order | +| `fail_on_conflict` | Raise `MergeConflictError` on any conflict | +| `last_wins` | Final subplan's output overwrites earlier ones | + +### Subplan Configuration + +```yaml +subplan_config: + execution_mode: parallel # sequential | parallel | dependency_ordered + merge_strategy: git_three_way # git_three_way | sequential_apply | fail_on_conflict | last_wins + max_parallel: 5 # 1-50, for parallel mode + fail_fast: false # stop all on first failure + timeout_per_subplan_seconds: ~ # optional per-subplan timeout + retry_failed: true # auto-retry failed subplans + max_retries: 2 # 0-5, max retry attempts +``` + +### Checkpoint Integration + +Before the first subplan execution attempt, the `SubplanExecutionService` +automatically creates a checkpoint (the `on_subplan_spawn` trigger). This +allows rolling back to the pre-spawn state if subplan execution fails +catastrophically. + +--- + +## Python API + +### `CorrectionService` + +**Module:** `cleveragents.application.services.correction_service` + +```python +from cleveragents.application.services.correction_service import CorrectionService +from cleveragents.domain.models.core.correction import CorrectionMode + +svc = CorrectionService( + decision_service=decision_svc, + plan_service=plan_svc, + checkpoint_service=checkpoint_svc, # optional +) + +# Create a correction request +correction = svc.request_correction( + plan_id="01HPLAN...", + target_decision_id="01HDECISION...", + mode=CorrectionMode.REVERT, + guidance="Use a safer migration approach with explicit rollback", +) + +# Analyze impact (BFS over decision tree) +impact = svc.analyze_impact(correction_id=correction.correction_id) +# impact.affected_decisions, impact.affected_files, impact.risk_level + +# Generate a dry-run report (no side effects) +report = svc.generate_dry_run_report(correction_id=correction.correction_id) + +# Execute the correction +result = svc.execute_correction(correction_id=correction.correction_id) + +# List corrections for a plan +corrections = svc.list_corrections(plan_id="01HPLAN...") + +# Cancel a pending correction +svc.cancel_correction(correction_id=correction.correction_id) +``` + +### `SubplanService` + +**Module:** `cleveragents.application.services.subplan_service` + +```python +from cleveragents.application.services.subplan_service import SubplanService + +svc = SubplanService( + decision_service=decision_svc, + config=plan.subplan_config, +) + +# Get spawn decisions for the parent plan +decisions = svc.get_spawn_decisions(parent_plan_id="01HPLAN...") + +# Build spawn entries from decisions +entries = svc.build_spawn_entries(decisions) + +# Spawn child plans (validates first) +result = svc.spawn( + parent_plan=parent_plan, + config=parent_plan.subplan_config, + spawn_entries=entries, + available_resources=frozenset(known_resource_ids), +) +# result.spawned_statuses, result.metadata, result.total_spawned +``` + +--- + +## Related Documentation + +- [Decision Correction Reference](../reference/decision_correction.md) — full correction reference +- [Subplans: Execution and Merge](../reference/subplans.md) — subplan execution modes and merge strategies +- [Subplan Service Reference](../reference/subplan_service.md) — spawn workflow details +- [Checkpoints API Reference](checkpoints.md) — checkpoint and rollback +- [Decisions API Reference](decisions.md) — decision tree and recording +- [Plan CLI Reference](../reference/plan_cli.md) — plan commands overview