Developer’s guide to multi-agent patterns in ADK
Source: Google Developers Blog
Source: Google Developers Blog
Date: Dec 16, 2025
The world of software development has already learned this lesson: monolithic applications don’t scale. Whether you’re building a massive e‑commerce platform or a complex AI application, relying on a single, all‑in‑one entity creates bottlenecks, increases debugging costs, and limits specialized performance.
The same principle applies to an AI agent. A single agent tasked with too many responsibilities becomes a “Jack of all trades, master of none.” As the complexity of instructions increases, adherence to specific rules degrades, and error rates compound, leading to more and more “hallucinations.” If your agent fails, you shouldn’t have to tear down the entire prompt to find the bug.
Reliability comes from decentralization and specialization. Multi‑Agent Systems (MAS) let you build the AI equivalent of a micro‑services architecture. By assigning specific roles (a Parser, a Critic, a Dispatcher) to individual agents, you build systems that are inherently more modular, testable, and reliable.
In this guide we’ll use the Google Agent Development Kit (ADK) to illustrate eight essential design patterns—from the Sequential Pipeline to the Human‑in‑the‑loop pattern—providing concrete patterns and pseudocode you need to build production‑grade agent teams.
1. Sequential Pipeline Pattern (aka the assembly line)
Think of this pattern as a classic assembly line where Agent A finishes a task and hands the baton directly to Agent B. It is linear, deterministic, and refreshingly easy to debug because you always know exactly where the data came from.
Typical use‑case: processing raw documents.
Parser → Extractor → Summarizer

In ADK, the SequentialAgent primitive handles the orchestration for you. The secret sauce is state management: simply use output_key to write to the shared session.state so the next agent in the chain knows exactly where to pick up the work.
# ADK Pseudocode
# Step 1: Parse the PDF
parser = LlmAgent(
name="ParserAgent",
instruction="Parse raw PDF and extract text.",
tools=[PDFParser],
output_key="raw_text"
)
# Step 2: Extract structured data
extractor = LlmAgent(
name="ExtractorAgent",
instruction="Extract structured data from {raw_text}.",
tools=[RegexExtractor],
output_key="structured_data"
)
# Step 3: Summarize
summarizer = LlmAgent(
name="SummarizerAgent",
instruction="Generate summary from {structured_data}.",
tools=[SummaryEngine]
)
# Orchestrate the Assembly Line
pipeline = SequentialAgent(
name="PDFProcessingPipeline",
sub_agents=[parser, extractor, summarizer]
)
2. Coordinator / Dispatcher Pattern (aka the concierge)
Sometimes you don’t need a chain; you need a decision‑maker. In this pattern, a central, intelligent agent acts as a dispatcher. It analyzes the user’s intent and routes the request to the specialist agent best suited for the job.
Typical use‑case: complex customer‑service bots that need to route a user to a “Billing” specialist or a “Tech Support” specialist.

This relies on LLM‑driven delegation. Define a parent CoordinatorAgent and provide a list of specialist sub_agents. ADK’s AutoFlow mechanism transfers execution based on the descriptions you provide for the children.
# ADK Pseudocode
billing_specialist = LlmAgent(
name="BillingSpecialist",
description="Handles billing inquiries and invoices.",
tools=[BillingSystemDB]
)
tech_support = LlmAgent(
name="TechSupportSpecialist",
description="Troubleshoots technical issues.",
tools=[DiagnosticTool]
)
# The Coordinator (Dispatcher)
coordinator = LlmAgent(
name="CoordinatorAgent",
instruction=(
"Analyze user intent. Route billing issues to BillingSpecialist "
"and bugs to TechSupportSpecialist."
),
sub_agents=[billing_specialist, tech_support]
)
3. Parallel Fan‑Out / Gather Pattern (aka the octopus)
Speed matters. If you have tasks that don’t depend on each other, why run them one by one? In this pattern, multiple agents execute tasks simultaneously to reduce latency or gain diverse perspectives. Their outputs are then aggregated by a final “synthesizer” agent.
Typical use‑case: automated code review. Spawn a Security Auditor, a Style Enforcer, and a Performance Analyst in parallel, then combine their feedback with a Synthesizer agent.

The ParallelAgent in ADK runs sub_agents concurrently. Although these agents operate in separate execution threads, they share the same session state, so be mindful of race conditions when writing to shared keys.
# ADK Pseudocode
security_auditor = LlmAgent(
name="SecurityAuditor",
instruction="Check the PR for security vulnerabilities.",
tools=[StaticAnalyzer]
)
style_enforcer = LlmAgent(
name="StyleEnforcer",
instruction="Enforce coding style guidelines.",
tools=[Linter]
)
performance_analyst = LlmAgent(
name="PerformanceAnalyst",
instruction="Identify performance bottlenecks.",
tools=[PerfProfiler]
)
# Synthesizer that gathers the three reports
synthesizer = LlmAgent(
name="ReviewSynthesizer",
instruction=(
"Combine the outputs from SecurityAuditor, StyleEnforcer, and "
"PerformanceAnalyst into a single, cohesive review comment."
),
tools=[TextCombiner]
)
# Parallel orchestration
parallel_review = ParallelAgent(
name="ParallelCodeReview",
sub_agents=[security_auditor, style_enforcer, performance_analyst],
gather_agent=synthesizer
)
The remaining five patterns (Human‑in‑the‑loop, Critic‑Reviewer, Adaptive Routing, Stateful Conversation, and Self‑Healing) follow similar principles and are detailed in the full ADK documentation.
1. Parallel fan‑out (aka the “Swarm”)
When a task can be broken into independent sub‑tasks, you can run several agents in parallel and then combine their results.
Each sub‑agent writes its output to a unique key in the session state to avoid race conditions.
# ADK Pseudo
# Define parallel workers
security_scanner = LlmAgent(
name="SecurityAuditor",
instruction="Check for vulnerabilities like injection attacks.",
output_key="security_report"
)
style_checker = LlmAgent(
name="StyleEnforcer",
instruction="Check for PEP8 compliance and formatting issues.",
output_key="style_report"
)
complexity_analyzer = LlmAgent(
name="PerformanceAnalyst",
instruction="Analyze time complexity and resource usage.",
output_key="performance_report"
)
# Fan‑out (the Swarm)
parallel_reviews = ParallelAgent(
name="CodeReviewSwarm",
sub_agents=[security_scanner, style_checker, complexity_analyzer]
)
# Gather / Synthesize
pr_summarizer = LlmAgent(
name="PRSummarizer",
instruction=(
"Create a consolidated Pull Request review using "
"{security_report}, {style_report}, and {performance_report}."
)
)
# Wrap in a sequence
workflow = SequentialAgent(sub_agents=[parallel_reviews, pr_summarizer])
2. Hierarchical decomposition (aka the Russian doll)
Large tasks may exceed a single agent’s context window. A high‑level agent can break the goal into sub‑tasks and delegate them to lower‑level agents. Unlike simple routing, the parent may wait for a sub‑task’s result before continuing its own reasoning.

You can treat a sub‑agent as a tool by wrapping it in AgentTool. The parent agent then calls the whole sub‑workflow as a single function.
# ADK Pseudocode
# Level 3: Tool agents
web_searcher = LlmAgent(
name="WebSearchAgent",
description="Searches the web for facts."
)
summarizer = LlmAgent(
name="SummarizerAgent",
description="Condenses text."
)
# Level 2: Coordinator agent
research_assistant = LlmAgent(
name="ResearchAssistant",
description="Finds and summarizes info.",
# Coordinator manages the tool agents
sub_agents=[web_searcher, summarizer]
)
# Level 1: Top‑level agent
report_writer = LlmAgent(
name="ReportWriter",
instruction=(
"Write a comprehensive report on AI trends. "
"Use the ResearchAssistant to gather info."
),
# Expose the sub‑agent hierarchy as a tool for the parent
tools=[AgentTool(research_assistant)]
)
3. Generator & Critic (aka the editor’s desk)
Separate creation from validation. The Generator produces a draft; the Critic checks it against hard‑coded criteria. If the draft fails, feedback is sent back to the Generator for revision. This loop continues until the output passes the quality gate.

Implementation uses a SequentialAgent for the draft‑review interaction and a LoopAgent to enforce the exit condition.
# ADK Pseudocode
# Generator
generator = LlmAgent(
name="Generator",
instruction=(
"Generate a SQL query. If you receive {feedback}, "
"fix the errors and generate again."
),
output_key="draft"
)
# Critic
critic = LlmAgent(
name="Critic",
instruction=(
"Check if {draft} is valid SQL. If correct, output 'PASS'. "
"If not, output error details."
),
output_key="feedback"
)
# Loop that repeats until the critic returns PASS
validation_loop = LoopAgent(
name="ValidationLoop",
sub_agents=[generator, critic],
condition_key="feedback",
exit_condition="PASS"
)
4. Iterative refinement (aka the sculptor)
For tasks that benefit from progressive improvement rather than a simple pass/fail, use a loop of Generator → Critic → Refinement agents. The loop terminates when a quality threshold is met, either after a fixed number of iterations or when an agent signals early completion (escalate=True).

# ADK Pseudocode
# Generator
generator = LlmAgent(
name="Generator",
instruction="Create a first draft of the document.",
output_key="draft"
)
# Critic (provides improvement notes)
critic = LlmAgent(
name="Critic",
instruction="Review {draft} and return a list of improvement suggestions.",
output_key="feedback"
)
# Refinement (applies suggestions)
refiner = LlmAgent(
name="Refiner",
instruction=(
"Take {draft} and the suggestions in {feedback}, "
"and produce an improved version."
),
output_key="refined"
)
# Loop that continues until the critic signals quality or max_iterations is hit
refinement_loop = LoopAgent(
name="IterativeRefinement",
sub_agents=[generator, critic, refiner],
condition_key="feedback",
exit_condition="QUALITY_OK", # can be set by the critic
max_iterations=5
)
Summary of patterns
| Pattern | When to use | Core primitives |
|---|---|---|
| Parallel fan‑out | Independent sub‑tasks that can run concurrently | ParallelAgent, SequentialAgent |
| Hierarchical decomposition | Tasks too large for a single context window | Nested LlmAgents wrapped with AgentTool |
| Generator & Critic | Need strict correctness or compliance checks | SequentialAgent + LoopAgent |
| Iterative refinement | Quality improves over multiple passes | LoopAgent with custom exit logic |
These patterns can be mixed and matched to build sophisticated, reliable workflows with the ADK framework.
Cleaned‑up Markdown
generator = LlmAgent(
name="Generator",
instruction="Generate an initial rough draft.",
output_key="current_draft"
)
# Critique Agent
critic = LlmAgent(
name="Critic",
instruction="Review {current_draft}. List ways to optimize it for performance.",
output_key="critique_notes"
)
# Refiner Agent
refiner = LlmAgent(
name="Refiner",
instruction="Read {current_draft} and {critique_notes}. Rewrite the draft to be more efficient.",
output_key="current_draft" # Overwrites the draft with a better version
)
# The Loop (Critique → Refine)
loop = LoopAgent(
name="RefinementLoop",
max_iterations=3,
sub_agents=[critic, refiner]
)
# Complete Workflow
workflow = SequentialAgent(sub_agents=[generator, loop])
7. Human‑in‑the‑loop (the human safety net)
AI agents are powerful, but sometimes you need a human in the driver’s seat for critical decision‑making. In this model, agents handle the groundwork, but a human must authorize high‑stakes actions—especially those that are irreversible or carry significant consequences (e.g., executing financial transactions, deploying code to production, or acting on sensitive data). This ensures safety and accountability.
The diagram shows a Transaction Agent processing routine work. When a high‑stakes check is needed, it calls an ApprovalTool Agent, which pauses execution and waits for a Human Reviewer to say “Yes” or “No”.

ADK allows you to implement this via custom tools. An agent can call an approval_tool which pauses execution or triggers an external system to request human intervention.
# ADK Pseudocode
transaction_agent = LlmAgent(
name="TransactionAgent",
instruction="Handle routine processing. If high stakes, call ApprovalTool.",
tools=[ApprovalTool]
)
approval_agent = LlmAgent(
name="ApprovalToolAgent",
instruction="Pause execution and request human input."
)
workflow = SequentialAgent(sub_agents=[transaction_agent, approval_agent])
8. Composite patterns (the mix‑and‑match)
Real‑world enterprise applications rarely fit into a single box. You will likely combine these patterns to build production‑grade systems.
Example: A robust Customer‑Support system might use a Coordinator to route requests. If the user has a technical issue, that branch could trigger a Parallel search of documentation and user history. The final answer might then go through a Generator → Critic loop to ensure tone consistency before being sent to the user.

A few pro‑tips before you start
- State management is vital: In ADK,
session.stateis your whiteboard. Use descriptiveoutput_keynames so downstream agents know exactly what they are reading. - Clear descriptions: When using routing, the
descriptionfield of your sub‑agents acts as API documentation for the LLM. Be precise. - Start simple: Don’t build a nested loop system on day one. Begin with a sequential chain, debug it, then add complexity.
Get Started Now
Ready to build your team of agents? Check out the ADK Documentation to get started. We can’t wait to see what you build.