This blog is part of the ADK Masterclass - Hands-On Series. In the previous modules, we explored individual LLM Agents and deterministic Workflow Agents. Now, we step into the most advanced paradigm in ADK: Multi-Agent Systems.
Multi-Agent Systems (MAS) are architectures where multiple autonomous agents collaborate to solve problems that are too complex for a single agent. This mimics human organizations where specialized roles (managers, researchers, writers) work together towards a common goal.
View Code on GitHubTable of Contents
1. What is Multi-Agent Systems?
A Multi-Agent System (MAS) is an architecture where multiple specialized agents collaborate to achieve complex goals. Instead of a single monolithic agent, we structure our application as a team of agents, often in a hierarchy, where coordinator agents route tasks to specialized worker agents, similar to how managers delegate to departments in a company.
In ADK, we build multi-agent systems by composing agents derived from BaseAgent:
- LLM Agents: Intelligent agents powered by language models that reason and make decisions.
- Workflow Agents: Orchestrators (
SequentialAgent,ParallelAgent,LoopAgent) that manage execution flow of sub-agents. - Custom Agents: Specialized agents with deterministic logic for specific tasks or external integrations.
This approach offers key advantages:
- Modularity: Break complex problems into focused, manageable agents.
- Specialization: Optimize each agent for its domain with custom instructions, tools, and models.
- Reusability: Build once, reuse across multiple applications.
- Maintainability: Easier debugging when each agent has a clear responsibility.
- Context Management: Distribute work to avoid overwhelming a single agent.
2. ADK Primitives for Agent Composition
ADK provides core building blocks for structuring multi-agent systems. Understanding these primitives is essential for building effective architectures.
Agent Hierarchy
Multi-agent systems use parent-child relationships defined in BaseAgent, creating a tree structure where agents can have sub-agents. When we pass agent instances to sub_agents, ADK automatically establishes the hierarchy and sets each child's parent_agent attribute.
Constraint: An agent can only have one parent. We can navigate the hierarchy using agent.parent_agent to move up, or agent.find_agent(name) to locate specific agents.
Workflow Agents as Orchestrators
Workflow Agents (SequentialAgent, ParallelAgent, LoopAgent) coordinate sub-agents without performing the work themselves. They follow deterministic, code-defined patterns while allowing LLM-powered sub-agents to handle intelligent reasoning, giving us structured control with adaptive intelligence.
Interaction & Communication Mechanisms
ADK provides three mechanisms for agent collaboration:
Shared Session State
All agents share access to session.state, a persistent dictionary. When an agent uses output_key, it stores output in shared state. Other agents access this via state injection ({key_name} placeholders) or direct state access, making data flow explicit and traceable.
LLM-Driven Delegation
LLM agents can transfer control to another agent using transfer_to_agent, intelligently recognizing when a task is better suited for a different specialist based on query content and available agents.
Explicit Invocation (AgentTool)
We can create explicit tools that allow agents to call other agents on demand, providing programmatic control over agent interactions and complementing automatic delegation.
3. Common Multi-Agent Patterns
Based on the ADK primitives, several common patterns have emerged for structuring multi-agent systems. These patterns provide proven architectures for different use cases:
Coordinator/Dispatcher Pattern
This is the hierarchical pattern we'll implement in our example. A coordinator agent (often an LLM agent) receives requests and dispatches them to specialized worker agents. The coordinator makes intelligent routing decisions, while workers focus on their specific domains.
Sequential Pipeline Pattern
We've already seen this with SequentialAgent. Agents execute in a strict order, with each agent's output feeding into the next. This is ideal for multi-stage processing where each step depends on the previous one.
Parallel Fan-Out/Gather Pattern
Using ParallelAgent, we can fan out a single input to multiple agents simultaneously, then gather their results. This is perfect for independent analysis tasks or getting multiple perspectives on the same problem.
Hierarchical Task Decomposition
A manager agent breaks down a complex task into subtasks, delegates them to worker agents, and then synthesizes the results. This mimics how human teams tackle large projects by dividing work among specialists.
Review/Critique Pattern (Generator-Critic)
One agent generates content (code, documents, designs), and another agent critiques it. The generator refines based on feedback, creating an iterative improvement cycle. This is similar to the Loop Agent pattern we explored earlier.
Iterative Refinement Pattern
Using LoopAgent, we can create systems that repeatedly refine their output until quality criteria are met. This is useful for tasks that benefit from multiple passes of improvement.
4. Tutorial
Let's build a Hierarchical system for customer support. We'll have a "Triage Agent" that routes queries to either "Sales" or "Tech Support".
Prerequisites
- Google AI Studio API Key
- Python 3.9+ installed
Step 1: Setup Environment
python3 -m venv .venv
source .venv/bin/activate
pip install google-adk python-dotenv
# Set your API Key
export GEMINI_API_KEY=your_api_key_here
Step 2: Initialize Project
Create a new agent project using the ADK CLI:
adk create customer_support
cd customer_support
Step 3: Define the Worker Agents
We'll create two specialized worker agents that handle different types of customer inquiries.
from google.adk.agents import Agent
import os
MODEL = os.getenv("GEMINI_MODEL", "gemini-2.5-flash")
# Sales Agent: Handles pricing and feature inquiries
sales_agent = Agent(
name="SalesAgent",
model=MODEL,
instruction="""You are a friendly sales representative for a financial services platform.
Your role is to:
- Answer questions about pricing plans and subscription tiers
- Explain product features and benefits
- Help customers understand which plan suits their needs
- Provide clear, professional responses about purchasing options
Be helpful, enthusiastic, and focused on helping customers make informed decisions.""",
description="Handles sales inquiries about pricing and product features."
)
# Tech Support Agent: Handles technical issues
tech_support_agent = Agent(
name="TechSupportAgent",
model=MODEL,
instruction="""You are a technical support engineer for a financial services platform.
Your role is to:
- Help users debug technical issues step-by-step
- Troubleshoot errors and system problems
- Guide users through technical procedures
- Provide clear, methodical solutions
Be patient, thorough, and focused on resolving technical problems efficiently.""",
description="Handles technical support inquiries and debugging assistance."
)
Step 4: Define the Router Agent
We'll create a router agent that analyzes incoming queries and routes them to the appropriate worker agent.
# Router Agent: Analyzes queries and routes to appropriate worker
router_agent = Agent(
name="TriageAgent",
model=MODEL,
instruction="""You are a customer support triage manager.
Analyze the user's query and determine which department should handle it.
Routing Rules:
- If the query is about buying, pricing, plans, subscriptions, or product features → Route to SALES
- If the query is about bugs, errors, technical issues, or system problems → Route to TECH
Respond with ONLY one of these exact phrases:
- "ROUTE: SALES" (for sales-related queries)
- "ROUTE: TECH" (for technical support queries)
Do not provide any other text or explanation.""",
description="Routes customer queries to the appropriate support agent.",
output_key="routing_decision"
)
Step 5: Create the Multi-Agent System
We'll create a control function that uses the router to direct queries to the appropriate worker agent.
import asyncio
from google.genai import types
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
async def handle_customer_query(user_query: str):
"""Orchestrates the multi-agent system to handle customer queries."""
session_service = InMemorySessionService()
# Step 1: Get routing decision from router agent
router_runner = Runner(
app_name="support_system",
agent=router_agent,
session_service=session_service
)
session = await session_service.create_session(
state={},
app_name="support_system",
user_id="customer_01"
)
router_content = types.Content(
role="user",
parts=[types.Part(text=user_query)]
)
# Get routing decision
routing_events = router_runner.run_async(
session_id=session.id,
user_id=session.user_id,
new_message=router_content
)
routing_decision = ""
async for event in routing_events:
if event.content and event.content.parts:
for part in event.content.parts:
if part.text:
routing_decision += part.text
# Step 2: Route to appropriate worker agent
if "SALES" in routing_decision:
worker_agent = sales_agent
elif "TECH" in routing_decision:
worker_agent = tech_support_agent
else:
return "Unable to route query. Please try rephrasing."
# Step 3: Get response from worker agent
worker_runner = Runner(
app_name="support_system",
agent=worker_agent,
session_service=session_service
)
worker_events = worker_runner.run_async(
session_id=session.id,
user_id=session.user_id,
new_message=router_content
)
response = ""
async for event in worker_events:
if event.content and event.content.parts:
for part in event.content.parts:
if part.text:
response += part.text
return response
# Example usage
if __name__ == "__main__":
query = "How much does the premium plan cost?"
result = asyncio.run(handle_customer_query(query))
print(result)
Step 6: Run the System
We can run this system using the ADK web interface. The router will automatically direct queries to the appropriate agent.
# Set API key and run
export GOOGLE_API_KEY=your_api_key_here
adk web
How It Works
When we run this multi-agent system, ADK orchestrates the hierarchical routing pattern:
- Input: A customer query enters the system (e.g., "How much does the premium plan cost?" or "I'm getting an error when trying to log in").
- Routing: The TriageAgent analyzes the query and determines which department should handle it. It outputs either "ROUTE: SALES" or "ROUTE: TECH".
- Delegation: Based on the routing decision, the system delegates the query to the appropriate worker agent (SalesAgent or TechSupportAgent).
- Processing: The worker agent processes the query using its specialized knowledge and instructions, providing a tailored response.
- Output: The final response is returned to the customer, addressing their specific inquiry through the appropriate specialist.
This hierarchical pattern allows us to scale our support system indefinitely. We can add a "Billing Agent", a "Legal Agent", or a "Feature Request Agent" without changing the core logic of the other workers. Each agent maintains its own specialized expertise while the router ensures queries reach the right specialist.
5. Conclusion
Multi-Agent Systems represent the future of AI application development. By decomposing complex problems into smaller, manageable interaction loops between specialized agents, you can build systems that are robust, understandable, and incredibly capable.
In the next section, we will explore Tools, the mechanism that allows these agents to interact with the outside world (APIs, databases, and more).
Next Steps
We've covered the fundamentals of building LLM agents, workflow patterns, and multi-agent systems. In the next section, we'll explore tools that give our agents the ability to interact with the outside world.