Files
cleveragents-core/docs/reference/plan_execute.md
T

5.9 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)
└── 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:

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:

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 <plan_id> to inspect error decisions, retry history, and recovery suggestions for a plan.

See Error Recovery Reference for full details.

Manual Recovery

Plans in ERRORED state without automatic retry can be recovered by:

  1. Reviewing errors via agents plan errors <plan_id>
  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):

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)