# Multi-Agent Orchestration With the ADK
One agent is often not enough: the moment a workflow needs a researcher, a writer, and a reviewer, cramming all of it into a single prompt turns into a brittle mess of "first do this, then do that, but only if..." instructions that the model half-follows. The Agent Development Kit (ADK) exists to split that work into specialists and wire them together with explicit control flow.
You already understand what an agent is: a model plus tools plus a loop. This lesson is about composing *several* of those into one system, and knowing when the added machinery pays for itself.
ADK gives you agents as composable objects. A regular LlmAgent reasons and calls tools. But ADK also ships workflow agents: agents whose only job is to run *other* agents in a defined pattern. There are three you need.
SequentialAgent runs its sub-agents one after another. Output flows forward through shared session state. Use it when step B genuinely depends on step A's result: research, then draft, then edit.
ParallelAgent runs its sub-agents concurrently. Each writes to its own key in session state. Use it when tasks are independent and you want them to finish in the time of the slowest one, not the sum of all of them. Think: summarize three documents at once, or query three data sources in parallel.
A coordinator is a normal LlmAgent that has other agents registered as sub_agents. Instead of running everything, the coordinator *reads the request and decides* which specialist should handle it, then transfers control. This is the dynamic pattern: the model routes at runtime rather than following a fixed order.
The key distinction: sequential and parallel are *deterministic* (you decided the flow at build time). A coordinator is *model-driven* (the LLMLLMA Large Language Model is an AI system trained on vast text data to predict and generate language, enabling tasks like writing, summarizing, and answering questions.View full definition → decides the flow per request). Real systems mix all three.
Splitting has real costs, so justify it. A multi-agent design wins when:
It costs you: more moving parts, more places for latency to accumulate, harder debugging (which agent produced the bad output?), and more tokenstokensA token is the basic unit of text that language models process, often a word fragment, whole word, or punctuation mark rather than a single character.View full definition → overall because agents re-establish context when they hand off. If one clear prompt with two tools does the job, use one agent. ReachReachThe number of unique people exposed to your message in a given period. Unlike impressions, reach counts each person once, no matter how often they see it.View full definition → for orchestration when the seams above start to hurt.
Let's build the classic content pipelinepipelineAll active sales opportunities across the stages of the sales process, together with their combined potential value and probability of closing.View full definition →. A coordinator receives a topic. It fans out to a research agent (which uses Google Search grounding) and a drafting agent in parallel, then a final stage stitches the results.
Here the fan-out is genuinely parallel: research and an outline can start simultaneously, then a writer combines them. We compose a ParallelAgent inside a SequentialAgent.
from google.adk.agents import LlmAgent, ParallelAgent, SequentialAgent
from google.adk.tools import google_search
# Specialist 1: gathers grounded facts using Google Search
researcher = LlmAgent(
name="researcher",
model="gemini-2.5-flash",
instruction=(
"Research the given topic. Return 5-7 concise, cited bullet points "
"of current, verifiable facts. Do not write prose."
),
tools=[google_search],
output_key="research_notes",
)
# Specialist 2: proposes structure in parallel, no web access
outliner = LlmAgent(
name="outliner",
model="gemini-2.5-flash",
instruction=(
"Propose a tight 4-section outline for an article on the topic. "
"Headings only, no body text."
),
output_key="outline",
)
# Fan out: research and outline run at the same time
gather = ParallelAgent(
name="gather",
sub_agents=[researcher, outliner],
)
# Final stage: reads both state keys and writes the draft
writer = LlmAgent(
name="writer",
model="gemini-2.5-pro",
instruction=(
"Write a 500-word article. Use the outline in {outline} for "
"structure and only the facts in {research_notes} for claims. "
"House style: plain, direct, no hype."
),
output_key="draft",
)
# Coordinator: gather in parallel, then draft
content_pipeline = SequentialAgent(
name="content_pipeline",
sub_agents=[gather, writer],
)Read the data flowdata flowAn automated sequence of steps that moves data from source to destination: ingestion, transformation, validation, and loading, so it arrives clean and ready to use.View full definition →, because that is where orchestration actually lives:
output_key="research_notes" tells ADK to write the researcher's final output into session state under that key.outliner writes to outline, running concurrently inside gather.writer reads both with the {research_notes} and {outline} template placeholders in its instruction. ADK substitutes state values before the prompt is sent.SequentialAgent guarantees gather fully completes before writer starts, so both keys exist by the time the writer runs.Notice the model choices. Research and outlining go to Gemini Flash: fast, cheap, high volume. The final draft, where quality matters most, goes to Gemini Pro. Mixing tiers per agent is one of the biggest practical wins of multi-agent design, and something a single agent cannot do.
ADK gives you a local dev loop. From your project directory:
pip install google-adk
adk webadk web launches a browser UI where you send input, watch each sub-agent fire, and inspect session state key by key. When the writer produces nonsense, you look at research_notes and outline first: if they were bad, the fault is upstream, not in the writer. That traceability is the practical payoff of explicit orchestration.
The example above hard-codes the flow. Sometimes you want the model to decide. Say some requests need research and others are pure rewrites. Make the coordinator an LlmAgent with specialists as sub_agents:
coordinator = LlmAgent(
name="coordinator",
model="gemini-2.5-pro",
instruction=(
"You route requests. If the user asks for new content about a "
"topic, delegate to 'content_pipeline'. If they paste existing "
"text to improve, delegate to 'editor'. Never answer directly."
),
sub_agents=[content_pipeline, editor],
)Now the LLMLLMA Large Language Model is an AI system trained on vast text data to predict and generate language, enabling tasks like writing, summarizing, and answering questions.View full definition → reads intent and *transfers* to the right sub-agent. This is called agent transfer in ADK: control (and the conversation) moves to the chosen sub-agent, which can transfer back or to a sibling. Powerful, but the tradeoff is real: routing is now a model decision, so it can be wrong. For high-stakes flows, prefer deterministic SequentialAgent/ParallelAgent and keep model-driven routing for genuinely branching intent.
Knowledge check
1. What fundamentally distinguishes a coordinator from the sequential and parallel workflow agents?
2. When is a SequentialAgent the appropriate orchestration choice?
3. Why does ParallelAgent complete faster than running the same tasks sequentially?
4. Select ALL correct answers about why splitting a workflow into multiple specialized agents can be better than one large prompt.
Select all the correct answers.
5. Select ALL correct answers that correctly describe how session state is used by ADK workflow agents.
Select all the correct answers.
Two things make these agents useful rather than academic.
Grounding with Google Search. The google_search tool gives the researcher live, cited results instead of stale training data. That is the difference between "facts as of the model's training cutoff" and "facts as of this morning." Scope it to only the agent that needs it: your drafting agent should not silently invent citations, so give it no search tool at all and force it to rely on state.
Sub-agents can be tools too. Beyond sub_agents (which hand off control), ADK lets you wrap an agent as a callable tool via AgentTool. The difference matters: a sub-agent takes over the turn, while an agent-as-tool returns a result to the caller, like any function. Use AgentTool when the coordinator should stay in charge and just "consult" a specialist mid-reasoning; use sub_agents when you want a clean handoff.
Where it deploys. You prototype locally with adk web or adk run. For production, the same agent definition deploys to Vertex AI Agent Engine, Google's managed runtime that handles sessions, scaling, and tracing, so you are not running the orchestration loop on your own server. You can also containerize and run it on Cloud Run. The point: the code above does not change between your laptop and production. You configure credentials and target, not architecture.
A few things that separate a demo from something you would ship:
name and description to choose. Vague descriptions cause misrouting. Write them like you are writing tool docs for the model, because that is exactly what they are.output_key is a contract between a producer and a consumer. Fewer, well-named keys mean fewer silent mismatches.output_key writes to session state and {placeholder} reads it back. Keep keys few, named, and treated as contracts between agents.research_notes