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:
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:
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
MaxIterationsLLM 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.
RunStreamfalls 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—Rundispatch onTypePlanExecute.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.