Give an LlmAgent a handful of tools and it will already do something that looks like planning: reason, call a tool, read the result, reason again. For a great many tasks that reason–act loop is enough, and reaching for anything more is waste. But some tasks have structure — dependencies, phases, verification points, work that must not be redone or skipped — and a loop that only ever chooses a plausible next step cannot hold that structure in its head. This article is about the machinery that does: planning inside a single agent. Not delegation across agents, but how one agent turns a goal into ordered steps, tracks which are done, revises the plan when a step fails, critiques its own output, shows the plan to a human before spending money, and still terminates.

What planning actually buys you

Take a concrete goal: migrate this service’s database and update all callers. It has dependencies (schema before data before cutover), parallelizable parts, verification points, and failure branches. An agent that only ever picks a locally-plausible next action produces locally-plausible nonsense: it updates a caller before the schema exists, skips verification because nothing in the immediate context demanded it, or loses track of which callers it already touched.

Planning makes that structure explicit and therefore inspectable. The decomposition names the phases and their order; a progress record says what is done; the agent reasons about the task as a whole rather than as a sequence of disconnected reactions. The second, underrated payoff is debuggability. A reactive trace is a pile of tool calls you must reconstruct a narrative from; a plan is a data structure, so when the output is wrong you can point at the step that produced the wrong thing and replay it. Planning does not only make an agent more capable — it makes its failures legible, which in production is often the larger win.

Advertisement

ReAct: deliberation smeared through the loop

The default behaviour of a tool-using LlmAgent is ReAct: the model reasons about what it needs, emits a tool call, the runtime executes it and feeds the result back, and the model reasons again with that result in context, repeating until it answers with no further calls. Deliberation is not a separate phase — it is embedded in every step. There is no artifact called ‘the plan’ anywhere in the system.

That is a genuine strength. ReAct is maximally adaptive: every decision is made with the freshest possible information, so a surprising tool result is absorbed immediately instead of colliding with a commitment made five steps ago. It has no plan to become stale, no replanning machinery to build, and no planning tokens to pay for. For search, lookup, question-answering, and most tool-routing, it is simply the right answer.

Its failure mode is equally characteristic. Because the agent optimises one step at a time, it drifts on long horizons: it pursues a promising thread until the original goal has quietly fallen out of the context window, revisits work it already did, or converges on a locally sensible answer that never satisfies the actual request.

Plan-then-execute: commit to a shape before you act

The alternative is to make the plan a first-class artifact. The agent reads the goal, produces an explicit ordered list of steps before touching a tool, then executes them. The decomposition is generated once with the whole goal in view and no tool noise in the context — exactly the condition under which models produce their most coherent structure.

Three things follow. The plan is inspectable before it is expensive: you can log it, show it to the user, or gate execution behind an approval, none of which is possible when the plan exists only implicitly. Execution gets cheaper per step, because each step runs with a tight context rather than the whole accumulated conversation. And progress becomes measurable: three of seven steps done is a real number you can display and resume from.

The cost is brittleness. A plan is a set of assumptions written down before any of them were tested, and reality routinely violates them — a tool errors, a dataset is not shaped as assumed, an API returns an empty list. An agent that marches through a stale plan produces confident garbage. Plan-then-execute is therefore never complete without replanning; that pair, not the plan alone, is the actual pattern.

Picking a mode — and the hybrid that usually wins

The honest framing is not ‘which is better’ but ‘how much structure does this task have, and how early can I know it.’

SignalPrefer ReActPrefer plan-then-execute
Step countA handfulMany, long horizon
DependenciesOrder barely mattersReal prerequisites
KnowabilityNext step depends on the last resultShape derivable from the goal
Cost per stepCheap, reversibleExpensive or side-effecting
OversightNone neededA human should see it first
ResumabilityOne turn, doneMust survive restarts

Most real systems land in the middle, and the middle has a name: plan the coarse structure, react within each step. The agent generates five phases, then executes phase two with an ordinary reason–act loop free to call three tools in whatever order the results suggest. You get the global coherence of a plan and the local adaptivity of ReAct, spending planning tokens only where the decomposition is genuinely uncertain. And a third option beats both when it applies: if the steps are the same every time, do not plan at all — encode them as a SequentialAgent or LoopAgent and let the composition be the plan, free, deterministic, and impossible for a model to skip a step in.

The planner slot: what ADK actually injects

ADK exposes planning as a planner field on LlmAgent, populated from google.adk.planners. A planner is not an orchestrator running beside the agent; it is a pair of hooks around the model call. Before the request goes out the planner contributes planning instructions to it; when the response returns the planner post-processes the parts, separating deliberation from the content that should be surfaced or acted upon.

Two implementations ship with the kit. BuiltInPlanner delegates to the model’s native thinking capability by passing a ThinkingConfig — you are not prompting the model into a planning format, you are switching on reasoning it already knows how to do and paying for it in thinking tokens. PlanReActPlanner takes the prompt-engineering route for models without native thinking: it instructs the model to emit its plan, its reasoning, and its actions as separately delimited blocks, then parses those blocks apart so the runtime acts on the actions and treats the rest as deliberation.

from google.adk.agents import LlmAgent
from google.adk.planners import BuiltInPlanner
from google.genai import types

agent = LlmAgent(
    name="analyst",
    model="gemini-2.5-pro",
    instruction="Answer multi-step analysis questions using the tools.",
    planner=BuiltInPlanner(
        thinking_config=types.ThinkingConfig(include_thoughts=True),
    ),
    tools=[query_tickets, cluster_themes],
)

If neither fits, BasePlanner is subclassable: implement the instruction-building and response-processing halves yourself and you have a domain-specific planning strategy in the same slot.

Writing a decomposition that survives contact with tools

Plan quality is mostly step quality, and models produce far better steps when the instruction says what a good step looks like. Three properties do nearly all the work.

A step must be verifiable. ‘Analyse the data’ has no done-condition; ‘produce a per-theme volume count for Q3 and write it to state['theme_counts']’ can be checked. Make every step name its output. A step must be executable with the tools that exist. Models cheerfully plan steps requiring capabilities the agent lacks, so put the tool inventory in the planning context and require each step to map to a tool or to plain reasoning. A step must be bounded — a step that is really a project needs either splitting or a genuine sub-plan with its own depth limit.

Two anti-patterns follow. Over-decomposition — fifteen steps for something a reason–act loop does in three — multiplies model calls without adding coherence. And speculative branching, where the plan hedges on facts execution has not established yet, means you are planning too far ahead: stop at the unknown and let replanning resume once the answer is in hand.

The plan lives in state, not in the transcript

A plan that exists only as text the model once emitted is a plan you will lose. Long tasks compact their context, sessions resume hours later, processes restart, and a summarised conversation silently drops the ordering detail that made the plan a plan. Treat the plan instead as data in session state: steps, their status, and the artifacts each produced, written through output_key or tool_context.state.

def record_step(step_id: str, status: str, result_key: str, tool_context):
    """Mark a plan step done/failed and where its output landed."""
    plan = tool_context.state.get("plan", [])
    for s in plan:
        if s["id"] == step_id:
            s["status"] = status
            s["result_key"] = result_key
    tool_context.state["plan"] = plan   # reassign so the delta is persisted
    return {"remaining": [s["id"] for s in plan if s["status"] == "pending"]}

Note the reassignment: ADK persists state through the event deltas a tool or callback produces, so mutating a nested object in place and never writing the key back is a classic way to lose progress. Once the plan is in state, the agent answers ‘what is next’ by reading rather than re-deriving it from a possibly-summarised history — and resumability, an accurate progress indicator, and a durable debugging record all fall out of the same structure.

Advertisement

Replanning when execution diverges from the plan

Replanning converts a brittle plan into a robust one, and the first design decision is what counts as divergence. Three triggers cover most cases: a step failed outright; a step succeeded but returned something that invalidates a later step’s premise (the tickets were not categorised the way the clustering step assumed); or new information makes the remaining plan the wrong plan.

The response should be graded, not all-or-nothing. A transient tool error deserves a retry. A single bad step deserves repair — rewrite that step, keep the rest. Only a broken premise deserves a full replan of the remaining steps. Collapsing all three into ‘regenerate everything’ is the most common way replanning becomes a cost centre.

Two invariants keep replanning honest. The goal is immutable — replanning revises the route, never the destination. An agent that quietly relaxes the objective until the plan succeeds is the worst failure mode here, because it reports success; pin the original goal in state and re-inject it verbatim on every replan. And completed work is preserved: replan the remaining steps from current state. Finally, count replans — two or three on a hard task is healthy, ten means thrashing, and the right move then is to stop and escalate.

Reflection: a critic loop that improves the output

Replanning fixes the route; reflection fixes the product. After the agent produces work, a separate evaluation step judges it against explicit criteria and either accepts it or sends it back for revision. In ADK the natural shape is a LoopAgent holding a producer and a critic, with max_iterations as the hard ceiling and escalation as the early exit.

from google.adk.agents import LoopAgent

def accept_draft(tool_context):
    """Called by the critic when the draft meets every criterion."""
    tool_context.actions.escalate = True   # break out of the LoopAgent
    return {"verdict": "accepted"}

review_loop = LoopAgent(
    name="draft_and_review",
    sub_agents=[reviser, critic],
    max_iterations=3,
)

Reflection lives or dies on the criteria. A critic told to ‘check the quality’ always finds something and loops to the ceiling every time, burning three times the tokens for cosmetic edits. A critic given a checklist — does every theme have a recommendation, is every quantitative claim traceable to a retrieved number — returns an actionable verdict and terminates the moment the checklist passes. Keep the critic’s context narrow, the artifact and the criteria rather than the whole conversation, so it judges the work instead of sympathising with the reasoning that produced it.

Plan visibility: showing the user the plan before it runs

An implicit plan is invisible; an explicit one is a product feature. Because ADK streams everything the runner does as events, the plan reaches your UI the moment it is generated, at three distinguishable levels.

Show the reasoning. With include_thoughts enabled on a ThinkingConfig, the model’s thought content is surfaced in the event stream marked as thought rather than as answer, so a client can render it in a collapsible panel. Show the steps. If the plan is in state as steps with statuses, a checklist that ticks off in real time falls out free — and on a two-minute task, visible progress is the difference between patience and a reload. Gate the execution. Strongest of all: render the plan and require approval before any tool runs. A before_tool_callback that checks an approval flag in state and refuses otherwise turns the plan into a genuine consent boundary.

One caution: thought content is deliberation, not commitment. Exposing raw reasoning invites users to argue with discarded hypotheses and leaks internal detail. Show a rendered plan to everyone; show raw thoughts to developers.

Bounding depth, iterations, and cost

Every mechanism here multiplies model calls. Planning adds a call before the work; replanning adds one per divergence; reflection multiplies the producing calls by the iteration count; sub-plans multiply everything again. An unbounded planning agent is not merely expensive — it is the one configuration strictly worse than plain ReAct, because it can fail to terminate.

BoundWhere it livesWhat it stops
Loop ceilingmax_iterations on LoopAgentA critic that is never satisfied
Early exitescalate from a tool or callbackPaying for iterations after success
Call ceilingThe runner’s run configurationA runaway reason–act loop
Replan counterA counter in session statePlan/execute/replan thrashing
Plan depthA depth field carried in stateSub-plans spawning sub-plans
Token or cost budgetbefore_model_callbackThe bill, before it arrives

The callback row is the one people skip and then regret. A before_model_callback can read a running token tally from state and short-circuit the call with a canned ‘budget exhausted, here is what I have so far’ response — graceful degradation rather than a timeout. Pair every bound with an explicit completion criterion: a plan should say what ‘done’ means, so the agent stops because it succeeded rather than because it ran out of budget.

When an explicit planner beats a plain tool loop

It comes down to a few honest questions. Does the task run long enough that the goal will fall out of context? Do the steps have real prerequisites? Are the actions expensive or irreversible enough that someone should see them first? Must the work survive a restart? Does anyone need to watch progress? Two or more yeses and an explicit plan earns its tokens. All noes and you should let the model call tools in a loop, because you would be paying for a plan nobody reads.

When you do plan, evaluate the plan itself and not only the final answer. A report can come out acceptable from a bad plan, and a good plan can be wrecked by one broken tool; scoring only the output never tells you which happened. Build a small eval set of goals with reference decompositions, score generated plans on coverage, ordering, and executability, and trace each step’s events so a bad run is attributable to a step rather than to ‘the agent.’ The end state to aim for is an agent that plans enough to stay coherent, replans enough to stay honest, reflects enough to stay correct, and is bounded enough to always stop — four properties in permanent tension, which is exactly why planning is an architecture decision and not a flag you turn on.

Planning inside an ADK agent is a spectrum, not a switch. At one end, a plain reason–act loop is maximally adaptive and costs nothing extra — the right default for short, loosely-ordered tasks. At the other, plan-then-execute makes the decomposition a first-class artifact you can inspect, approve, resume, and debug — what long-horizon, dependency-heavy work needs. ADK gives you the planner slot for this, and workflow agents for structure that never varies. Three rules make it work: keep the plan in session state so it survives compaction and restarts; replan the route, never the goal, grading your response from retry to repair to full replan; and bound everything — iterations, replans, depth, tokens — because an unbounded planning agent is strictly worse than not planning at all.