Files
cleveragents-core/docs/reference/plan_execute.md
T
2026-02-25 10:05:10 +00:00

11 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.

When a PlanExecutionContext is provided, the execute phase delegates to RuntimeExecuteActor for full tool-calling runtime integration with changeset capture through ChangeSetStore.

Architecture

PlanExecutor
├── StrategizeStubActor     (read-only, produces decision tree)
├── ExecuteStubActor         (legacy stub: sandbox + ChangeSetCapture)
├── RuntimeExecuteActor      (runtime: ToolRunner + ChangeSetStore)
├── PlanExecutionContext      (plan metadata + resource bindings)
├── PlanLifecycleService      (phase transitions, persistence)
└── ErrorRecoveryService      (optional — error recording + retry logic)

Execution Modes

Mode Actor Trigger Output
Stub ExecuteStubActor No execution_context ExecuteResult
Runtime RuntimeExecuteActor execution_context is provided RuntimeExecuteResult

PlanExecutionContext

The PlanExecutionContext bridges plan metadata into the tool runtime:

from cleveragents.application.services.plan_execution_context import (
    PlanExecutionContext,
)
from cleveragents.domain.models.core.change import InMemoryChangeSetStore

ctx = PlanExecutionContext(
    plan_id="01HGZ...",
    decision_root_id="01HGZ...",
    sandbox_root="/tmp/sandbox",
    automation_profile="trusted",
    project_resources={"repo": {"path": "/code"}},
    changeset_store=InMemoryChangeSetStore(),
)

# Start a changeset for tracking mutations
changeset_id = ctx.start_changeset()

# Record changes during execution
ctx.record_change(entry)

# Retrieve changeset
cs = ctx.get_changeset(changeset_id)

# Summarize context state
summary = ctx.summarize()

Fields

Field Type Required Description
plan_id str Yes ULID of the plan
decision_root_id str | None No Root decision from strategize
sandbox_root str | None No Sandbox filesystem path
automation_profile str | None No Automation profile name
project_resources dict[str, Any] No Project resource metadata
resource_bindings dict[str, BoundResource] No Resolved resource bindings
changeset_store ChangeSetStore No Defaults to InMemoryChangeSetStore

RuntimeExecuteActor

Wraps ToolRunner to execute strategy decisions with changeset capture:

from cleveragents.application.services.plan_execution_context import (
    RuntimeExecuteActor,
    RuntimeExecuteResult,
)

actor = RuntimeExecuteActor(
    tool_runner=runner,
    execution_context=ctx,
)
result: RuntimeExecuteResult = actor.execute(decisions)

RuntimeExecuteResult Fields

Field Type Description
changeset_id str ULID of the produced changeset
tool_call_count int Number of tool calls made
sandbox_refs list[str] Sandbox reference paths
decision_ids_processed list[str] Processed decision node IDs
execution_duration_ms float Wall-clock execution time (ms)

PlanExecutor Runtime Mode

The PlanExecutor auto-selects runtime vs stub mode:

from cleveragents.application.services.plan_executor import PlanExecutor

# Stub mode (no execution_context)
executor = PlanExecutor(lifecycle_service=lifecycle, tool_runner=runner)
assert not executor.has_runtime

# Runtime mode (with execution_context)
executor = PlanExecutor(
    lifecycle_service=lifecycle,
    tool_runner=runner,
    execution_context=ctx,
)
assert executor.has_runtime
assert executor.changeset_store is not None

# Execute auto-dispatches to RuntimeExecuteActor
result = executor.run_execute(plan_id)

Properties

Property Type Description
has_runtime bool True if execution_context is set
changeset_store ChangeSetStore | None Store from execution context
execution_context PlanExecutionContext | None The execution context

ChangeSetStore Wiring

The ChangeSetStore protocol defines the interface for changeset persistence:

class ChangeSetStore(Protocol):
    def start(self, plan_id: str) -> str: ...
    def record(self, changeset_id: str, entry: ChangeEntry) -> None: ...
    def get(self, changeset_id: str) -> SpecChangeSet | None: ...
    def get_for_plan(self, plan_id: str) -> list[SpecChangeSet]: ...
    def summarize(self, changeset_id: str) -> dict[str, Any]: ...

InMemoryChangeSetStore is the default for M1. Database-backed implementations will be added in D1 milestone.

Phase Lifecycle

Phase Actor Mode Output
Strategize StrategizeStubActor Read-only Decision tree, invariant records
Execute RuntimeExecuteActor Runtime ChangeSet via ChangeSetStore
Execute ExecuteStubActor Stub ChangeSet via ChangeSetCapture

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,
    execution_context=ctx,               # optional — enables runtime mode
    error_recovery_service=er_service,    # optional — enables retry logic
)
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 with exception_type, traceback, mode, 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 (stub mode) 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:

Event Types

Event Phase Actor Description
strategize_started Strat. Stub Phase processing began
strategize_decisions Strat. Stub Decisions produced
strategize_complete Strat. Stub Phase completed
execute_started Execute Stub Stub execute began
execute_step Execute Stub Stub decision step
execute_complete Execute Stub Stub execute completed
runtime_execute_started Execute Runtime Runtime execute began
runtime_execute_step Execute Runtime Runtime decision step
runtime_execute_complete Execute Runtime Runtime execute completed

Module Reference

  • cleveragents.application.services.plan_execution_context
    • PlanExecutionContext: Execution context bridging plan to runtime
    • RuntimeExecuteActor: Tool-calling execute actor
    • RuntimeExecuteResult: Runtime execution output model
  • cleveragents.application.services.plan_executor
    • PlanExecutor: Orchestrator with runtime/stub mode selection
    • StrategizeStubActor: Local-only strategize actor
    • ExecuteStubActor: Local-only execute actor
    • StrategyDecision, StrategizeResult, ExecuteResult: Data models
    • StreamCallback: Type alias for streaming callbacks
  • cleveragents.application.services.error_recovery_service: Error recovery integration (optional)