Developer’s guide to multi-agent patterns in ADK

Published: (December 16, 2025 at 08:40 PM EST)
9 min read

Source: Google Developers Blog

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) allow you to build the AI equivalent of a microservices 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.

Use case: processing raw documents—Parser → Extractor → Summarizer.

Sequential pattern

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.

Use case: complex customer‑service bots that need to route a user to a “Billing” specialist or a “Tech Support” specialist.

Coordinator‑dispatcher pattern

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

Use case: Automated Code Review—run a “Security Auditor,” a “Style Enforcer,” and a “Performance Analyst” in parallel, then combine their feedback.

Parallel fan‑out

The ParallelAgent in ADK runs sub‑agents simultaneously. Although these agents operate in separate execution threads, they share the session state. To prevent race conditions, ensure each agent writes its data to a unique key.

# ADK Pseudocode

security_auditor = LlmAgent(
    name="SecurityAuditor",
    instruction="Identify security vulnerabilities in the code.",
    tools=[StaticAnalysisTool],
    output_key="security_report"
)

style_enforcer = LlmAgent(
    name="StyleEnforcer",
    instruction="Check code style compliance.",
    tools=[Linter],
    output_key="style_report"
)

performance_analyst = LlmAgent(
    name="PerformanceAnalyst",
    instruction="Analyze performance bottlenecks.",
    tools=[Profiler],
    output_key="performance_report"
)

# Synthesizer that gathers the reports
synthesizer = LlmAgent(
    name="ReviewSynthesizer",
    instruction=(
        "Combine {security_report}, {style_report}, and {performance_report} "
        "into a single cohesive review comment."
    ),
    tools=[Summarizer],
    output_key="final_review"
)

# Parallel orchestration
parallel_review = ParallelAgent(
    name="ParallelCodeReview",
    sub_agents=[security_auditor, style_enforcer, performance_analyst],
    gather_agent=synthesizer
)

Cleaned Markdown Content

# 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])

4. Hierarchical Decomposition (aka the Russian doll)

Sometimes a task is too big for a single agent’s context window. High‑level agents can break down complex goals into sub‑tasks and delegate them. Unlike the routing pattern, the parent agent might delegate only part of a task and wait for the result before continuing its own reasoning.

In the diagram below, a ReportWriter doesn’t do the research itself. It delegates to a ResearchAssistant, which in turn manages WebSearch and Summarizer tools.

hierarchical task decomposition agent

You can treat a sub‑agent as a tool. By wrapping an agent in AgentTool, the parent agent can call it explicitly, effectively treating the sub‑agent’s entire workflow as a single function call.

# ADK Pseudocode

# Level 3: Tool Agents
web_searcher = LlmAgent(
    name="WebSearchAgent",
    description="Searches 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."
    ),
    # Wrap the sub‑agent hierarchy as a tool for the parent
    tools=[AgentTool(research_assistant)]
)

5. Generator and Critic (aka the editor’s desk)

Generating high‑quality, reliable output often requires a second pair of eyes. In this pattern, you separate the creation of content from its validation. One agent acts as the Generator, producing a draft, while a second agent acts as the Critic, reviewing it against specific, hard‑coded criteria or logical checks.

The architecture includes conditional looping: if the review passes, the loop breaks and the content is finalized; if it fails, feedback is routed back to the Generator to produce a compliant draft. This is especially useful for code generation that needs syntax checking or content creation that requires compliance review.

generator critic agent pattern

Implementation in ADK uses two primitives—a SequentialAgent that manages the draft‑and‑review interaction, and a parent LoopAgent that enforces the quality gate and exit condition.

# ADK Pseudocode

# The Generator
generator = LlmAgent(
    name="Generator",
    instruction=(
        "Generate a SQL query. If you receive {feedback}, "
        "fix the errors and generate again."
    ),
    output_key="draft"
)

# The Critic
critic = LlmAgent(
    name="Critic",
    instruction=(
        "Check if {draft} is valid SQL. If correct, output 'PASS'. "
        "If not, output error details."
    ),
    output_key="feedback"
)

# The Loop
loop = LoopAgent(
    name="ValidationLoop",
    sub_agents=[generator, critic],
    condition_key="feedback",
    exit_condition="PASS"
)

6. Iterative Refinement (aka the sculptor)

Great work rarely happens in a single draft. Just as a human writer revises, polishes, and edits, agents sometimes need several attempts to get an answer exactly right. In this pattern, agents enter a cycle of generating, critiquing, and refining until the output meets a specific quality threshold.

Unlike the Generator‑and‑Critic pattern, which focuses on a binary pass/fail, this pattern emphasizes qualitative improvement. A Generator creates a rough draft, a Critique Agent provides optimization notes, and a Refinement Agent polishes the output based on those notes.

iterative refinement agent pattern (1)

The pattern is implemented using LoopAgent. A critical component is the exit mechanism. While you can set a hard limit with max_iterations, ADK also allows agents to signal early completion. An agent can trigger an exit by setting escalate=True within its EventActions if the quality threshold is met before the maximum iterations are reached.

# ADK Pseudocode

# Generator
generator = LlmAgent(
    name="Generator",
    instruction="Generate an initial rough draft.",
    output_key="current_draft"
)

# Critique Agent
critic = LlmAgent(
    name="Critic",
    instruction=(
        "Review {current_draft} and provide improvement suggestions. "
        "If the draft meets quality standards, set {feedback} to 'PASS'."
    ),
    output_key="feedback"
)

# Refinement Agent
refiner = LlmAgent(
    name="Refiner",
    instruction=(
        "Incorporate the suggestions from {feedback} into the draft "
        "and output the refined version."
    ),
    output_key="refined_draft"
)

# Loop Agent
refinement_loop = LoopAgent(
    name="IterativeRefinementLoop",
    sub_agents=[generator, critic, refiner],
    condition_key="feedback",
    exit_condition="PASS",
    max_iterations=5   # optional hard limit
)

LLM Agent Example

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—specifically 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.”

human‑in‑the‑loop pattern

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.

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

composite agent pattern

A Few Pro‑Tips Before You Start

  • State management is vital: In ADK, session.state is your whiteboard. Use descriptive output_key values so downstream agents know exactly what they are reading.
  • Clear descriptions: When using routing, the description field of your sub‑agents serves as API documentation for the LLM. Be precise.
Back to Blog

Related posts

Read more »