# Plan Execute: Strategize & Execute Integration ## Overview The plan executor connects the `PlanLifecycleService` to stub actors that drive plans through the **Strategize** and **Execute** phases. In M1, these actors are local-only stubs (no LLM calls); future milestones will integrate real AI providers. ## Architecture ``` PlanExecutor ├── StrategizeStubActor (read-only, produces decision tree) ├── ExecuteStubActor (sandbox + ToolRunner + ChangeSet capture) ├── PlanLifecycleService (phase transitions, persistence) └── ErrorRecoveryService (optional — error recording + retry logic) ``` ## Phase Lifecycle | Phase | Actor | Mode | Output | |-------------|---------------------|-----------|---------------------------------| | Strategize | StrategizeStubActor | Read-only | Decision tree, invariant records| | Execute | ExecuteStubActor | Sandbox | ChangeSet, execution metadata | ## Strategize Phase The strategize phase is **read-only**: it produces a decision tree from the action's `definition_of_done` without modifying any resources. ### Decision Tree The stub actor parses `definition_of_done` into discrete steps, each represented as a `StrategyDecision` node with a ULID identifier: ```python from cleveragents.application.services.plan_executor import ( PlanExecutor, StrategizeResult, ) executor = PlanExecutor( lifecycle_service=lifecycle, tool_runner=runner, error_recovery_service=er_service, # optional ) result: StrategizeResult = executor.run_strategize(plan_id) # result.decision_root_id -> ULID of root node # result.decisions -> list[StrategyDecision] # result.invariant_records -> list[dict] (stub enforcement records) ``` ### Invariant Propagation Project and action invariants are propagated into the strategize context. In M1, enforcement is stubbed (all invariants are accepted). Full reconciliation via the Invariant Reconciliation Actor lands in D2. ## Execute Phase The execute phase uses sandbox resources with tool calls routed through `ToolRunner` and captured by `ChangeSetCapture`. ### ChangeSet Capture All tool mutations during execute are recorded in a `ChangeSet`: ```python result: ExecuteResult = executor.run_execute(plan_id) # result.changeset_id -> ULID of the changeset # result.changeset -> ChangeSet with entries # result.tool_calls_count -> int # result.sandbox_refs -> list[str] ``` ### Metadata Persistence After execute completes, the following metadata is persisted on the Plan: - `changeset_id`: The ChangeSet identifier - `sandbox_refs`: List of sandbox reference paths - `error_details`: Tool call count and sandbox ref count ## Phase Guards - **Execute requires Strategize COMPLETE**: The executor validates that the plan has completed strategize (has a `decision_root_id`) before allowing execute to proceed. - **Phase validation**: Both `run_strategize()` and `run_execute()` verify the plan is in the correct phase before proceeding. ## Error Handling Failures in either phase are captured with full error context: - `error_message`: The exception message string - `error_details`: Dict containing `exception_type`, `traceback`, and structured error-recovery metadata (category, retry count, recovery hints) when an `ErrorRecoveryService` is attached - The plan transitions to `ERRORED` processing state ### Automatic Retry (D1b) When an `ErrorRecoveryService` is provided to the executor, the **Execute** phase includes an automatic retry loop: 1. On failure the error is classified (transient, validation, etc.) and recorded via `ErrorRecoveryService.record_error()`. 2. If the error is retriable and retry attempts remain, the execute actor is re-invoked automatically. 3. If retries are exhausted or the error is non-retriable, the plan transitions to `ERRORED` with full recovery hints stored in `error_details`. Retry limits default to 3 and are controlled by the automation profile's `auto_retry_transient` threshold. Use `agents plan errors ` to inspect error decisions, retry history, and recovery suggestions for a plan. See [Error Recovery Reference](error_recovery.md) for full details. ### Manual Recovery Plans in `ERRORED` state without automatic retry can be recovered by: 1. Reviewing errors via `agents plan errors ` 2. Resetting the plan's processing state back to `QUEUED` 3. Re-running the failed phase via the executor ## Streaming Hooks Both phases accept an optional `stream_callback` parameter for real-time status updates (used by the `--stream` CLI flag): ```python def my_callback(event_type: str, data: dict) -> None: print(f"[{event_type}] {data}") executor.run_strategize(plan_id, stream_callback=my_callback) ``` ### Event Types | Event | Phase | Description | |------------------------|------------|---------------------------------| | `strategize_started` | Strategize | Phase processing began | | `strategize_decisions` | Strategize | Decisions produced | | `strategize_complete` | Strategize | Phase completed successfully | | `execute_started` | Execute | Phase processing began | | `execute_step` | Execute | Individual decision being executed | | `execute_complete` | Execute | Phase completed successfully | ## Module Reference - **`cleveragents.application.services.plan_executor`**: Core module - `StrategizeStubActor`: Local-only strategize actor - `ExecuteStubActor`: Local-only execute actor - `PlanExecutor`: Orchestrator connecting lifecycle service to actors - `StrategyDecision`: Decision node model - `StrategizeResult`: Strategize output model - `ExecuteResult`: Execute output model - `StreamCallback`: Type alias for streaming callbacks - **`cleveragents.application.services.error_recovery_service`**: Error recovery integration (optional) - See [Error Recovery Reference](error_recovery.md)