4.7 KiB
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)
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:
from cleveragents.application.services.plan_executor import (
PlanExecutor,
StrategizeResult,
)
executor = PlanExecutor(lifecycle_service=lifecycle, tool_runner=runner)
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:
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 identifiersandbox_refs: List of sandbox reference pathserror_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()andrun_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 stringerror_details: Dict containingexception_typeandtraceback- The plan transitions to
ERROREDprocessing state
Retry Guidance
Plans in ERRORED state can be retried by:
- Resetting the plan's processing state back to
QUEUED - Re-running the failed phase via the executor
Full retry automation is planned for D1b (Phase Reversion & Error Recovery).
Streaming Hooks
Both phases accept an optional stream_callback parameter for real-time
status updates (used by the --stream CLI flag):
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 moduleStrategizeStubActor: Local-only strategize actorExecuteStubActor: Local-only execute actorPlanExecutor: Orchestrator connecting lifecycle service to actorsStrategyDecision: Decision node modelStrategizeResult: Strategize output modelExecuteResult: Execute output modelStreamCallback: Type alias for streaming callbacks