+210 XP

Multi-agent orchestration with the ADK

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

The three orchestration primitives

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.

Sequential: pipeline stages

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.

Parallel: fan out independent work

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.

Coordinator: dispatch by decision

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 LLM decides the flow per request). Real systems mix all three.

When multi-agent beats a single agent

Splitting has real costs, so justify it. A multi-agent design wins when:

  • Roles need different tools or instructions. A research agent needs Google Search grounding; a drafting agent needs a house style guide and no web access. Separate agents keep each prompt short and each toolset scoped.
  • You want parallelism. One agent cannot truly do two things at once. Fanning out cuts wall-clock time.
  • You want isolation and testability. You can unit-test the "reviewer" agent alone, swap its model from Gemini Pro to Flash, and never touch the rest.
  • The context is getting crowded. Even with Gemini's long context, stuffing research notes, style rules, and draft history into one window degrades focus. Specialists keep each context lean.

It costs you: more moving parts, more places for latency to accumulate, harder debugging (which agent produced the bad output?), and more tokens overall because agents re-establish context when they hand off. If one clear prompt with two tools does the job, use one agent. Reach for orchestration when the seams above start to hurt.

A concrete example: coordinator fans out to research and drafting

Let's build the classic content pipeline. 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.

python
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 flow, 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.
  • The outliner writes to outline, running concurrently inside gather.
  • The 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.

Running it

ADK gives you a local dev loop. From your project directory:

bash
pip install google-adk
adk web

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

Coordinator-as-router: the dynamic variant

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:

python
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 LLM 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.

Build multi-agent systems with the Agent Development Kit

Watch on YouTube

Knowledge check

1. What fundamentally distinguishes a coordinator from a SequentialAgent or ParallelAgent?

2. In which scenario is a SequentialAgent the most appropriate choice?

3. Why does a ParallelAgent complete a set of tasks in the time of the slowest one rather than the sum of all of them?

MULTIPLE CHOICE

4. Select ALL reasons the lesson gives for splitting a workflow into multiple agents instead of using one agent.

Select all the correct answers.

MULTIPLE CHOICE

5. Select ALL statements that correctly describe ADK orchestration primitives.

Select all the correct answers.

Tools, grounding, and where this runs

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.

Practical guardrails

A few things that separate a demo from something you would ship:

  • Name and describe every agent clearly. When a coordinator routes by model decision, it uses each sub-agent's 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.
  • Keep state keys explicit and few. Every output_key is a contract between a producer and a consumer. Fewer, well-named keys mean fewer silent mismatches.
  • Watch token cost at the seams. Each handoff can replay context. If your parallel branches are large, they multiply. Send bulky research to Flash, reserve Pro for the synthesis step, and trim what each agent passes downstream.
  • Test agents in isolation first. Run the researcher alone with a fixed topic until its output is reliable. Only then wire it into the pipeline. Debugging a five-agent system end-to-end is miserable; debugging one agent is easy.
  • Fail loudly. Decide what the writer does if research_notes comes back empty. A production instruction should say "if no research is provided, stop and report that," not quietly hallucinate.

Key Takeaways

  • Use `SequentialAgent` for dependent stages, `ParallelAgent` for independent work, and an `LlmAgent` coordinator for runtime routing. Deterministic flow is more reliable; save model-driven routing for genuinely branching intent.
  • State keys are the wiring. output_key writes to session state and {placeholder} reads it back. Keep keys few, named, and treated as contracts between agents.
  • Mix model tiers per agent. Send high-volume research and outlining to Gemini Flash, reserve Gemini Pro for the quality-critical synthesis step. This per-agent choice is a core reason to split at all.
  • Scope tools to the agent that needs them. Give Google Search grounding only to the researcher; deny it to the drafter so it cannot invent citations.
  • Prototype with `adk web`, deploy the same code to Vertex AI Agent Engine. Split into multiple agents only when roles, tools, parallelism, or context pressure justify the extra debugging surface.