# Plan Model Reference > Source: `src/cleveragents/domain/models/core/plan.py` ## Overview The Plan model is the fundamental unit of orchestration in CleverAgents v3. Plans follow a three-phase lifecycle and are instantiated from Action templates via `agents plan use`. ## NamespacedName All plans, actions, and projects use a `NamespacedName` for scoped identification: ``` [server:][namespace/] ``` | Field | Type | Default | Description | |-------|------|---------|-------------| | `server` | `str \| None` | `None` | Optional server qualifier (e.g., `prod`, `dev`) | | `namespace` | `str` | `"local"` | Namespace scope (e.g., `local`, username, orgname) | | `name` | `str` | *(required)* | The item name (1-255 chars) | ### Parsing Rules - `"my-action"` -> `namespace="local"`, `name="my-action"` - `"local/my-action"` -> `namespace="local"`, `name="my-action"` - `"myorg/my-action"` -> `namespace="myorg"`, `name="my-action"` - `"prod:myorg/my-action"` -> `server="prod"`, `namespace="myorg"`, `name="my-action"` ### Validation - **Namespace**: lowercase alphanumeric with hyphens only. Empty defaults to `"local"`. - **Name**: alphanumeric with hyphens or underscores only. Stored lowercase. ## PlanIdentity Every plan has a unique identity with optional hierarchy links: | Field | Type | Default | Description | |-------|------|---------|-------------| | `plan_id` | `str` | *(required)* | Unique ULID (26-char Crockford base32) | | `parent_plan_id` | `str \| None` | `None` | Parent plan ULID (set for subplans) | | `root_plan_id` | `str \| None` | `None` | Root plan ULID (top-most in tree) | | `attempt` | `int` | `1` | Attempt counter (increments on re-run, >= 1) | ## Lifecycle Phases Plans progress through four phases in strict order: | Phase | Description | Valid Next | |-------|-------------|-----------| | **STRATEGIZE** | LLM generates a strategy / decision tree | EXECUTE | | **EXECUTE** | Tool-based execution in sandboxed environment | APPLY | | **APPLY** | Changes are merged to the target project(s) | APPLIED | | **APPLIED** | Terminal — plan is complete | *(none)* | There is no "Action" phase in the plan lifecycle. Actions are a separate domain model representing reusable plan templates. ### Phase Transitions - `get_next_phase()` returns the next phase in order, or `None` if terminal (APPLIED). - `can_transition_to_next_phase` is `True` only when `processing_state == COMPLETE` and phase is not APPLIED. - Only forward transitions are allowed. Attempting to skip phases (e.g., STRATEGIZE -> APPLY) raises an error. ## Processing States Within each phase, a plan has a processing state: | State | Meaning | |-------|---------| | `QUEUED` | Waiting for compute / worker (default) | | `PROCESSING` | Currently running | | `ERRORED` | Failed; includes error metadata | | `COMPLETE` | Finished successfully | | `CANCELLED` | User/system cancelled (terminal for any phase) | - `processing_state` always has a value (defaults to `QUEUED`). - When `phase` is `APPLIED`, the `processing_state` is automatically corrected to `COMPLETE` by the `validate_phase_state_consistency` model validator. ### Phase/State Consistency Rules The `validate_phase_state_consistency` model validator enforces: 1. **APPLIED phase requires COMPLETE state** — if a plan is constructed with `phase=APPLIED` and any other state, it is auto-corrected to `COMPLETE`. 2. **CANCELLED is terminal** — a plan with `processing_state=CANCELLED` in any phase is considered terminal. ## Computed Properties | Property | Type | Description | |----------|------|-------------| | `state` | `ProcessingState` | Alias for `processing_state` | | `is_terminal` | `bool` | `True` if phase is APPLIED or state is CANCELLED | | `is_errored` | `bool` | `True` if `processing_state == ERRORED` | | `can_transition_to_next_phase` | `bool` | `True` if state is COMPLETE and phase is not APPLIED | | `is_subplan` | `bool` | `True` if `parent_plan_id` is set | | `is_root_plan` | `bool` | `True` if no `root_plan_id` or root == self | | `depth` | `int` | `0` for root, `-1` (placeholder) for non-root | | `has_subplans` | `bool` | `True` if `subplan_statuses` is non-empty | ## Action Linkage Every plan carries an `action_name` field (required, string) that references the namespaced name of the source Action template. This replaces the old `action_id` pattern. ``` action_name: "local/code-coverage" ``` ## Project Links Plans reference projects through `project_links: list[ProjectLink]` instead of a flat `project_ids` list. Each link carries: | Field | Type | Default | Description | |-------|------|---------|-------------| | `project_name` | `str` | *(required)* | Namespaced project name | | `alias` | `str \| None` | `None` | Optional alias for disambiguation | | `read_only` | `bool` | `False` | Whether the project is read-only in this plan | ### Alias Validation Aliases must match the pattern `^[a-z][a-z0-9_-]{0,63}$`: - Lowercase, starts with a letter - Alphanumeric, hyphens, underscores only - Maximum 64 characters Aliases must be unique within a plan. The `validate_project_link_alias_uniqueness` model validator raises `ValueError` on duplicates. ## Automation Profile Plans can carry a resolved automation profile with provenance tracking: ```python automation_profile: AutomationProfileRef | None ``` The `AutomationProfileRef` contains: | Field | Type | Description | |-------|------|-------------| | `profile_name` | `str` | The namespaced profile name (e.g., `"trusted"`) | | `provenance` | `AutomationProfileProvenance` | Where the profile was resolved from | `AutomationProfileProvenance` values: `plan`, `action`, `project`, `global`. Resolution follows plan > action > project > global precedence. Once set at `plan use` time, the profile is locked to the plan. ### Automation Level | Level | Description | |-------|-------------| | `MANUAL` | User must explicitly trigger each phase transition (default) | | `REVIEW_BEFORE_APPLY` | Auto strategize + execute, pause before apply | | `FULL_AUTOMATION` | All phases run automatically without human input | ## Invariants Plans carry invariant constraints with source provenance: ```python invariants: list[PlanInvariant] ``` Each `PlanInvariant` has: | Field | Type | Description | |-------|------|-------------| | `text` | `str` | The constraint text (natural language, min 1 char) | | `source` | `InvariantSource` | Where it originated | `InvariantSource` values: `plan`, `action`, `project`, `global`. Precedence is plan > action > project > global. ## Arguments Plans store resolved argument values and their display order: | Field | Type | Default | Description | |-------|------|---------|-------------| | `arguments` | `dict[str, Any]` | `{}` | Resolved values (name -> value) | | `arguments_order` | `list[str]` | `[]` | Stable ordering for CLI rendering | ## Actor Fields Plans reference actors for each phase: | Field | Type | Default | Description | |-------|------|---------|-------------| | `strategy_actor` | `str \| None` | `None` | Actor for Strategize phase | | `execution_actor` | `str \| None` | `None` | Actor for Execute phase | | `review_actor` | `str \| None` | `None` | Optional actor for code review/QA | | `apply_actor` | `str \| None` | `None` | Optional actor for release/merge | | `estimation_actor` | `str \| None` | `None` | Optional actor for cost/risk estimation | | `invariant_actor` | `str \| None` | `None` | Optional actor for invariant reconciliation | ## PlanTimestamps Lifecycle timestamps are tracked in a dedicated `PlanTimestamps` model: | Field | Type | Default | Description | |-------|------|---------|-------------| | `created_at` | `datetime` | `now()` | When the plan was created | | `updated_at` | `datetime` | `now()` | Last modification time | | `strategize_started_at` | `datetime \| None` | `None` | When strategize phase began | | `strategize_completed_at` | `datetime \| None` | `None` | When strategize phase completed | | `execute_started_at` | `datetime \| None` | `None` | When execute phase began | | `execute_completed_at` | `datetime \| None` | `None` | When execute phase completed | | `apply_started_at` | `datetime \| None` | `None` | When apply phase began | | `applied_at` | `datetime \| None` | `None` | When the plan reached terminal APPLIED state | ## Description and Definition of Done | Field | Type | Default | Description | |-------|------|---------|-------------| | `description` | `str` | *(required)* | Rendered prompt/description defining what the plan does (min 1 char) | | `definition_of_done` | `str \| None` | `None` | Rendered, testable completion criteria | Both are rendered from the action template at plan creation time and stored as plain strings. ## Error Tracking | Field | Type | Default | Description | |-------|------|---------|-------------| | `error_message` | `str \| None` | `None` | Error message if errored | | `error_details` | `dict[str, str] \| None` | `None` | Additional error context (key-value pairs) | ## Execution Placeholders These fields are populated during the plan lifecycle: | Field | Type | Default | Description | |-------|------|---------|-------------| | `changeset_id` | `str \| None` | `None` | ChangeSet ID from Execute phase | | `sandbox_refs` | `list[str]` | `[]` | Active sandbox reference IDs | | `validation_summary` | `dict[str, Any] \| None` | `None` | Validation results before Apply | | `decision_root_id` | `str \| None` | `None` | Root decision node ULID | ## Metadata | Field | Type | Default | Description | |-------|------|---------|-------------| | `created_by` | `str \| None` | `None` | User/session that created the plan | | `tags` | `list[str]` | `[]` | Tags for organization and filtering | | `reusable` | `bool` | `True` | Whether the source action remains available after use | | `read_only` | `bool` | `False` | Whether plan only performs read operations | ## CLI Output `Plan.as_cli_dict()` returns a stable dictionary for CLI rendering in table, JSON, and YAML formats. Fields are ordered consistently: 1. `plan_id`, `name`, `action`, `phase`, `state`, `description` 2. `definition_of_done` (if set) 3. `automation_profile`, `automation_profile_source` (if set) 4. `automation_level` 5. `projects` (if any, with alias/read_only) 6. `invariants` (if any, with source tags) 7. `arguments` (if any, in stable order) 8. `strategy_actor`, `execution_actor` (if set) 9. `error` (if present) 10. `created_at`, `updated_at` 11. `parent_plan_id` (if subplan) 12. `subplan_count` (if has subplans) ## Subplan Hierarchy Plans support a parent/child hierarchy for decomposition: - `identity.parent_plan_id` — Links to parent plan - `identity.root_plan_id` — Links to root of the tree - `subplan_config` — Configuration for child execution - `subplan_statuses` — Status tracking for each child ### ExecutionMode Controls how subplans are scheduled: | Mode | Description | |------|-------------| | `SEQUENTIAL` | One after another, ordered by sequence (default) | | `PARALLEL` | All at once, up to `max_parallel` | ### SubplanMergeStrategy How to merge results from parallel subplans: | Strategy | Description | |----------|-------------| | `GIT_THREE_WAY` | Use git merge-file for code (default) | | `SEQUENTIAL_APPLY` | Apply in completion order | | `FAIL_ON_CONFLICT` | Error if any conflicts | | `LAST_WINS` | Later changes overwrite earlier | ### SubplanConfig Configuration for subplan execution, set on parent plans: | Field | Type | Default | Description | |-------|------|---------|-------------| | `execution_mode` | `ExecutionMode` | `SEQUENTIAL` | How to execute subplans | | `merge_strategy` | `SubplanMergeStrategy` | `GIT_THREE_WAY` | How to merge subplan results | | `max_parallel` | `int` | `5` | Max concurrent subplans in PARALLEL mode (1-50) | | `fail_fast` | `bool` | `False` | Stop all subplans on first failure | | `timeout_per_subplan_seconds` | `int \| None` | `None` | Timeout per subplan in seconds | | `retry_failed` | `bool` | `True` | Automatically retry failed subplans | | `max_retries` | `int` | `2` | Max retry attempts per subplan (0-5) | ### SubplanStatus Stored on the parent plan to track each child: | Field | Type | Default | Description | |-------|------|---------|-------------| | `subplan_id` | `str` | *(required)* | The subplan's ULID | | `action_name` | `str` | *(required)* | Namespaced action name used to create the subplan | | `target_resources` | `list[str]` | `[]` | Resource IDs this subplan operates on | | `status` | `ProcessingState` | `QUEUED` | Current processing state of the subplan | | `started_at` | `datetime \| None` | `None` | When execution started | | `completed_at` | `datetime \| None` | `None` | When execution completed | | `error` | `str \| None` | `None` | Error message if subplan failed | | `changeset_summary` | `str \| None` | `None` | Brief summary of changes produced | | `files_changed` | `int` | `0` | Number of files modified (>= 0) | | `attempt_number` | `int` | `1` | Current attempt number (>= 1) | | `previous_attempts` | `list[SubplanAttempt]` | `[]` | History of prior execution attempts | ### SubplanAttempt Record of a subplan execution attempt for post-mortem analysis: | Field | Type | Default | Description | |-------|------|---------|-------------| | `attempt_number` | `int` | *(required)* | 1-based attempt counter | | `started_at` | `datetime` | *(required)* | When this attempt started | | `completed_at` | `datetime \| None` | `None` | When this attempt ended | | `error` | `str \| None` | `None` | Error message if attempt failed | | `was_retried` | `bool` | `False` | Whether a subsequent attempt was made | ### Computed Properties | Property | Description | |----------|-------------| | `is_subplan` | `True` if `parent_plan_id` is set | | `is_root_plan` | `True` if no root or root == self | | `depth` | `0` for root, `-1` (placeholder) for non-root | | `has_subplans` | `True` if `subplan_statuses` is non-empty | ### Failure Handling `SubplanFailureHandler` makes stateless decisions about: - **should_stop_others**: Halt remaining subplans? (Yes if `fail_fast` or sequential mode) - **should_retry**: Retry the failed subplan? (Yes for retriable errors within retry limits) Retriable errors: `ValidationError`, `TimeoutError`, `TemporaryResourceError`, `MergeConflictError` Non-retriable errors: `ConfigurationError`, `AuthenticationError`, `MissingResourceError`, `CircularDependencyError` Unknown errors are not retried (conservative default).