Skip to content

Plan-Execute agent

A plan-execute agent runs three explicit phases: ask the LLM to draft a JSON plan, run each step through a ReAct sub-loop with prior step outputs carried as context, then ask the LLM one more time to synthesise the step outputs into one cohesive final answer. Pick this when the task benefits from up-front decomposition — multi-step research, multi-part deliverables, long writing tasks — and ReAct's organic interleaving isn't structured enough.

Type identifier

YAML form:

agents:
  - id: report-writer
    type: plan-execute
    model: { ... }
    tools: [ ... ]

What it does

runPlanExecute in pkg/agent/adk/run_orchestration.go runs three phases:

Phase 1 — Plan

The system prompt is appended with:

Create a step-by-step plan for this task. Respond with JSON array of
steps:
[{"step": 1, "action": "description"}]

A single Generate call asks the model for the plan. extractJSONArray pulls the first [...] block (tolerant of markdown code fences and surrounding prose). If the JSON cannot be parsed or the array is empty, the agent errors with plan-execute: failed to parse plan… / planner returned empty plan — no graceful degradation.

Phase 2 — Execute

For each step, the agent runs a.runReAct(ctx, msg) where msg is:

Execute step <N>: <action>

Context:
<accumulated step log>

The step log accumulates Step N (action):\n<resp.Content>\n\n for every completed step, so later steps see earlier outputs verbatim. Each step is a full ReAct sub-loop — the LLM may call any registered tool the agent has, up to MaxIterations per step. A failure in any step aborts the entire run (plan-execute: step N failed: …).

Phase 3 — Aggregate

A final Generate call asks the LLM to synthesise the step outputs:

You previously planned and executed steps to address the user's
request. Synthesise the step outputs below into a single coherent
final answer that directly addresses the original request. Do not list
steps; produce a unified response. If a step produced a finished
artefact (a report, a draft, structured output), preserve it verbatim
rather than re-summarising.

The aggregated response is what Run returns — not the concatenated step blocks. Without this phase the caller would receive a labelled sequence of step outputs; with it, they get one well-formed answer.

Configuration

Plan-execute uses the standard ReAct configuration plus max_iterations applied PER STEP (not globally):

Field Type Default Description
id, type, model, system_prompt, prompt_vars — — Standard.
tools list — Available for every step's ReAct sub-loop.
memory block conversation Shared across all three phases. The plan and aggregation calls bypass memory directly (they pass synthesized messages to Model.Generate); only the per-step ReAct sub-loops read/write it.
max_iterations int 10 Per-step cap for the ReAct sub-loop.
guardrails block — The chain runs inside each ReAct step but NOT around the plan/aggregate phases — those are direct Model.Generate calls without the guard machinery.
budget, governance, approval_*, tool_retry — — All apply to each step's ReAct sub-loop, since that's where tools get invoked.

Behavior notes

  • The plan and aggregate phases each cost ONE LLM call regardless of step count. Plus N step-loops, each of which can be up to MaxIterations LLM calls plus their tool calls. Budget your token cap accordingly.
  • The planner is given the agent's system prompt verbatim — so the same role / constraints / domain info that drive ReAct also drive the plan. Adjust the prompt if the plan-execute structure needs different framing.
  • A malformed plan response is a hard failure. To make the planner more robust on a cheap model, lean on a model: with reliable JSON output (or wrap the planner step in an output-validator chain upstream and re-prompt).
  • Plan-execute is NOT streaming-capable. RunStream falls back to the non-streaming path and word-chunks the final answer for display.

Example

A writing agent that drafts a multi-section report:

agents:
  - id: report-writer
    type: plan-execute
    model:
      provider: anthropic
      model: claude-sonnet-4-7
    system_prompt: |
      You are a research-driven writer. Each task is decomposed into
      a plan; each step's output is fed forward; the aggregate phase
      produces the final document.
    max_iterations: 6
    tools:
      - { name: web-fetch, type: function, function: web.fetch }
      - { name: read-file, type: function, function: fs.read }
    budget:
      max_tool_calls: 30
      max_input_tokens: 400000

When invoked with "Draft a market overview of EV charging in Europe", the planner might emit [{step:1,"action":"Identify the top 5 markets"}, {step:2,"action":"Gather 2024-2025 charger growth numbers"}, {step:3,"action":"Write a 600-word overview"}]; each is a ReAct sub-loop with tool access; the aggregate call returns the polished 600-word doc.

Source

  • pkg/agent/adk/run_orchestration.go — runPlanExecute, extractJSONArray.
  • pkg/agent/adk/agent.go — Run dispatch on TypePlanExecute.
  • pkg/workflow/definition.go — AgentDefinition (same shape as ReAct).

See also

  • react — the executor each step runs through.
  • deep — recursive decomposition (related but hierarchical).
  • sequential — fixed-list pipelines without an LLM-generated plan.