Building an Agent Swarm with Google ADK: Multi-Agent Systems Done Right
Learn how to build an agent swarm or multi-agent system with Google's Agent Development Kit (ADK) using deterministic workflow wrappers, shared session scratchpads, and Agent-to-Agent (A2A) RPC calls.
Published on • November 14, 2026
AI Assistant

Building an agent swarm or multi-agent system using Google’s Agent Development Kit (ADK) is a powerful way to break down complex tasks into specialized, modular components.
Unlike chat-heavy frameworks that treat a multi-agent system as one long conversation, ADK treats multi-agent systems as a stateless cloud architecture using deterministic workflow wrappers (SequentialAgent, ParallelAgent, LoopAgent), shared session scratchpads (InvocationContext), and Agent-to-Agent (A2A) remote RPC calls.
In this guide we’ll walk through the whole loop: project setup, defining specialist agents, choosing an orchestration pattern, wiring up shared state, and finally running and testing your swarm.
Why a Swarm Instead of One Big Prompt?
A single agent with a mega-prompt tends to fail in predictable ways:
- Instruction collision — research guidelines, writing style, and QA rules all fight for the same context window.
- No guaranteed ordering — the model may edit before it researches, or skip a step entirely.
- No clean handoff — intermediate artifacts get buried in chat history instead of living in a shared workspace.
A swarm fixes all three. Each specialist owns one job, the workflow wrapper guarantees the order of execution, and every agent reads and writes to the same session state.
Step 1: Project Setup & Initialization
Initialize your project environment and install the ADK framework using your terminal:
# Create and activate a Python virtual environment
python3 -m venv venv
source venv/bin/activate
# Install the Google ADK framework and required dependencies
pip install google-adk
Set up your .env file with your Gemini API key (from Google AI Studio or Vertex AI):
GEMINI_API_KEY=your_api_key_here
A clean project layout keeps the swarm readable:
my_swarm/
├── .env
├── agents/
│ ├── research_agent.py
│ ├── writer_agent.py
│ └── editor_agent.py
├── workflows/
│ └── content_pipeline.py
└── run_swarm.py
Step 2: Define Specialized Agents
Instead of a single, bloated prompt handling every task, build focused specialist agents using LlmAgent. Each agent needs a unique name, a model designation, a clear description (which the router uses to assign work), and instructions.
from google.adk.agents import LlmAgent
# 1. Research Agent
research_agent = LlmAgent(
model="gemini-2.5-flash",
name="ResearchAgent",
description="Gathers data, background information, and facts on a given topic.",
instruction="You are a meticulous researcher. Provide factual, concise data points.",
)
# 2. Writer Agent
writer_agent = LlmAgent(
model="gemini-2.5-flash",
name="WriterAgent",
description="Takes research notes and drafts structured content or blog posts.",
instruction="You are a skilled copywriter. Turn raw notes into engaging, well-formatted prose.",
)
# 3. Editor Agent
editor_agent = LlmAgent(
model="gemini-2.5-flash",
name="EditorAgent",
description="Reviews drafts for tone, clarity, and grammatical correctness.",
instruction="You are a strict managing editor. Polish the draft and ensure high quality.",
)
Three details matter more than they look:
namemust be unique — ADK keys session state and traces by agent name.descriptionis routing metadata — a coordinator reads it to decide who should handle a request.instructionis the job contract — keep it short and single-purpose. If you find yourself writing “and also…”, that’s a fourth agent.
Step 3: Choose Your Swarm Orchestration Pattern
ADK lets you orchestrate your agents using code-first patterns depending on how you want them to collaborate.
Pattern A: Deterministic Pipelines (SequentialAgent)
Use a sequential pipeline when tasks must run in a strict assembly-line sequence. State changes are passed cleanly via the shared InvocationContext.
from google.adk.agents import SequentialAgent
# Combines the agents into a guaranteed pipeline
content_pipeline = SequentialAgent(
name="ContentPipeline",
sub_agents=[research_agent, writer_agent, editor_agent],
)
Each sub-agent runs in order, and every one of them sees what the previous agents wrote to the session scratchpad. No prompt gymnastics required — the structure enforces “research first, write second, edit last.”
Pattern B: Dynamic Delegation (sub_agents with a Coordinator)
If you want a root coordinator agent to intelligently route requests to different specialists based on user intent, define sub_agents under a parent router:
root_coordinator = LlmAgent(
model="gemini-2.5-flash",
name="RootCoordinator",
description="Routes user queries to the correct specialized agent.",
instruction="Analyze the user request and delegate work to the appropriate sub-agent.",
sub_agents=[research_agent, writer_agent, editor_agent],
)
Here the model decides who runs and in what order. That flexibility is the trade-off: Pattern A guarantees behavior, Pattern B adapts to intent. A good rule of thumb — use deterministic wrappers for anything that must happen the same way every time (content pipelines, compliance checks, data ETL), and coordinators for open-ended chat surfaces.
Pattern C: Fan-out and Bounded Loops
Two more wrappers round out the toolbox:
from google.adk.agents import ParallelAgent, LoopAgent
# Fan-out: run independent tasks simultaneously, then merge results
fan_out = ParallelAgent(
name="ResearchFanOut",
sub_agents=[market_agent, competitor_agent, trend_agent],
)
# Loop: repeat a step until a condition is met (e.g. quality gate passes)
revision_loop = LoopAgent(
name="RevisionLoop",
sub_agents=[editor_agent],
max_iterations=3,
)
ParallelAgent is ideal when sub-tasks share no dependencies — three researchers fetching different sources at once. LoopAgent gives you a bounded retry: run the editor up to three times instead of trusting a single pass.
Step 4: Share State Through the Session Scratchpad
Agents in a swarm don’t pass messages back and forth like a chat — they read and write a shared session state attached to the InvocationContext. In practice, each agent’s output lands in session.state under its agent name, so the next agent in the pipeline can pick it up.
# Inside an agent's tool or output config, write to the shared scratchpad
from google.adk.tools import ToolContext
async def save_research(topic: str, tool_context: ToolContext) -> dict:
state = tool_context.state
state["research_notes"] = {"topic": topic, "status": "done"}
return {"ok": True}
Because the swarm itself is stateless across invocations, the session store is the single source of truth. Anything you need later — intermediate research, a drafted outline, an approval flag — must be written there explicitly. Anything you don’t persist is gone after the run.
Step 5: Scale Out with Agent-to-Agent (A2A) RPC
When part of your swarm lives somewhere else — another service, another language, another team’s infrastructure — ADK’s A2A support lets agents call each other over the network as remote RPC instead of importing them as local sub-agents.
Local sub-agents run in-process: instant, shared memory, zero network overhead. A2A agents are independent services with a formal contract, which is what you want for microservices boundaries, third-party integrations, or cross-language components. Reach for A2A when you need a deployment boundary; stay local when you just need a code boundary.
Step 6: Run and Test Your Swarm
ADK provides a built-in CLI and web UI to inspect traces and run your swarm locally:
# Launch the interactive ADK local web UI for testing your multi-agent workflow
adk web
The dev UI is the fastest way to watch the swarm work — you can see which sub-agent ran, what it read from state, and what it wrote back.
Alternatively, run it programmatically via a Python script using ADK’s Runner:
import asyncio
import uuid
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
from workflows.content_pipeline import content_pipeline
APP_NAME = "content_swarm"
USER_ID = "developer"
SESSION_ID = uuid.uuid4().hex
async def main():
session_service = InMemorySessionService()
await session_service.create_session(
app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID
)
runner = Runner(
agent=content_pipeline,
app_name=APP_NAME,
session_service=session_service,
)
message = types.Content(
role="user",
parts=[types.Part(text="Write a comprehensive briefing on quantum computing breakthroughs.")],
)
async for event in runner.run_async(
user_id=USER_ID, session_id=SESSION_ID, new_message=message
):
if event.content and event.content.parts:
print(f"[{event.author}] {event.content.parts[0].text}")
asyncio.run(main())
Run it, then inspect the session state to confirm each stage actually wrote its artifact:
python run_swarm.py
Best Practices for a Production Swarm
- Split by failure mode, not by role title. Two agents that fail the same way will produce the same bad output — merge them.
- Make state writes explicit. Every agent should declare what it puts into the scratchpad; hidden coupling is where swarms break.
- Prefer deterministic wrappers for guarantees. If a step must always run, put it in a
SequentialAgent. If it’s optional or adaptive, let a coordinator decide. - Bound your loops. Always set
max_iterationsso a quality gate can’t spin forever. - Trace everything. ADK’s web UI and session traces show which agent ran, when, and why — use them before tuning prompts.
- Start local, scale to A2A later. Begin with in-process sub-agents and introduce remote calls only at real deployment boundaries.
Wrapping Up
An ADK swarm is deliberately boring in the best way: small specialist agents, a workflow wrapper that guarantees ordering, a shared scratchpad for artifacts, and A2A only when you truly need a network hop. That combination turns “one giant prompt that sort of works” into a system you can reason about, trace, and test.
The next time a task feels too big for one agent, don’t add another paragraph to the prompt — add another agent to the pipeline.