ADK workflow agents — sequential, parallel, loop
ADK gives you SequentialAgent, ParallelAgent, and LoopAgent for multi-agent orchestration. Here's a research → write → review pipeline.
What you'll learn
- The three workflow agents — Sequential, Parallel, Loop
- When workflow agents beat a single LlmAgent with tools
- State sharing between sub-agents via session state
- A research → write → review sequential pipeline
Before you start
A team building a competitive-intel agent shoved everything into a
single LlmAgent: scrape three sources, dedupe, summarize, score
confidence, format. The prompt grew to a multi-page spec; outputs
started drifting because the model couldn’t keep all the personas
straight. Splitting it into a SequentialAgent of focused
LlmAgents — one per stage — fixed the regressions overnight and
made each stage debuggable on its own. That’s the win ADK workflow
agents are built for.
When one LlmAgent with tools isn’t enough, ADK gives you
workflow agents — three built-in orchestration classes, each of
which wraps a list of sub-agents and runs them in a fixed pattern.
Unlike MAF’s WorkflowBuilder (which requires explicitly wiring every
edge in a graph), ADK’s approach is declarative: you pick the right
class for the shape you need, pass in the sub-agents, and the
orchestration is implied. This keeps simple pipelines simple while
still composing into arbitrary shapes.
The three you’ll use:
| Class | Runs sub-agents… |
|---|---|
| SequentialAgent | One after another, output of N feeds input of N+1 |
| ParallelAgent | All at once, concurrent |
| LoopAgent | Repeatedly, until a sub-agent signals stop |
You compose them — a SequentialAgent can contain a ParallelAgent
which contains LlmAgents — to build arbitrary shapes.
ADK’s three workflow agent classes. Each runs its sub_agents in a fixed shape; state passes between them via output_key / template substitution.
A research → write → review pipeline
The canonical 3-agent shape: a researcher gathers facts, a writer drafts based on the research, a reviewer critiques. Sequential is the right primitive.
# requires: pip install google-adk
# Shape only — google-adk needs a real Python process and Gemini access; read it as reference code.
from google.adk.agents import LlmAgent, SequentialAgent
# Each sub-agent has its own instructions. Specialize, don't generalize.
researcher = LlmAgent(
name="researcher",
model="gemini-2.5-flash",
instruction=(
"You receive a user topic. Produce 3-5 bullet points of relevant "
"facts. Be specific; cite reasoning, not vibes. "
"Save your output to the session state under key 'research'."
),
output_key="research", # writes the final response into session state
)
writer = LlmAgent(
name="writer",
model="gemini-2.5-flash",
instruction=(
"You receive bullet-point research in session state under "
"{research}. Write a 2-paragraph executive summary based on it. "
"Do not invent facts beyond what's in research."
),
output_key="draft",
)
reviewer = LlmAgent(
name="reviewer",
model="gemini-2.5-flash",
instruction=(
"Review the draft in session state under {draft}. Check it against "
"the research in {research}. Respond with exactly: "
"APPROVED — <one-line reason> OR REVISE — <one-line reason>."
),
output_key="review",
)
# The pipeline. SequentialAgent runs sub_agents in order.
pipeline = SequentialAgent(
name="research_write_review",
sub_agents=[researcher, writer, reviewer],
)
# Run via a Runner (see adk-intro):
# from google.adk.runners import Runner
# from google.adk.sessions import InMemorySessionService
#
# runner = Runner(agent=pipeline, app_name="lessons",
# session_service=InMemorySessionService())
# session = await runner.session_service.create_session(
# app_name="lessons", user_id="u_1"
# )
# events = runner.run_async(
# user_id="u_1", session_id=session.id,
# new_message="Why did Q1 churn drop at our company?",
# )
print("Shape only — needs Gemini API to actually run.")
The new thing here: output_key. When a sub-agent finishes, its
final response is written into session state under that key. Downstream
sub-agents reference it in their instructions with {research} or
{draft} — ADK substitutes the value from state. This is how
information flows between stages in a workflow.
ParallelAgent — fan out and merge
When subtasks are independent, run them concurrently:
from google.adk.agents import ParallelAgent
finance_researcher = LlmAgent(name="finance", model="gemini-2.5-flash",
instruction="...", output_key="finance_notes")
legal_researcher = LlmAgent(name="legal", model="gemini-2.5-flash",
instruction="...", output_key="legal_notes")
market_researcher = LlmAgent(name="market", model="gemini-2.5-flash",
instruction="...", output_key="market_notes")
# All three run concurrently; each writes to its own state key.
researchers = ParallelAgent(
name="researchers",
sub_agents=[finance_researcher, legal_researcher, market_researcher],
)
synthesizer = LlmAgent(
name="synthesizer",
model="gemini-2.5-flash",
instruction=(
"Combine these into a unified brief: "
"Finance: {finance_notes}\\nLegal: {legal_notes}\\nMarket: {market_notes}"
),
)
# Wire the parallel block into a sequence:
brief_pipeline = SequentialAgent(
name="brief",
sub_agents=[researchers, synthesizer],
)
The pattern: parallel for the independent work, then sequential for the merge step. The synthesizer reads all three state keys and produces the combined output.
LoopAgent — refinement until satisfied
LoopAgent re-runs its sub-agents until one of them signals stop. The
canonical use: a generator/critic loop that refines until the critic
says it’s good enough.
from google.adk.agents import LoopAgent
drafter = LlmAgent(
name="drafter",
model="gemini-2.5-flash",
instruction=(
"Draft an answer to {user_question}. If a {critique} exists in "
"state, revise the previous draft to address it."
),
output_key="draft",
)
critic = LlmAgent(
name="critic",
model="gemini-2.5-flash",
instruction=(
"Critique {draft}. If acceptable, respond exactly: STOP. "
"Otherwise return one specific improvement."
),
output_key="critique",
)
# LoopAgent stops when a sub-agent signals escalation (the exact API —
# likely setting escalate=True on an EventActions object — check current
# ADK docs) or when max_iterations is hit. Always cap.
refinement_loop = LoopAgent(
name="refine",
sub_agents=[drafter, critic],
max_iterations=4,
)
The critic can signal stop either by emitting a specific token the drafter inspects, or via ADK’s escalation mechanism — refer to current docs for the exact API.
State is the contract between sub-agents
In ADK workflows, session state is the data bus. Sub-agents read state
via {key} substitution in their instructions and write to state via
output_key. That’s the only way they communicate.
This is a deliberate constraint. It means:
- Each sub-agent has a focused prompt. It doesn’t need to see upstream agents’ full chains-of-thought; it sees the artifacts they produced.
- The state object is your reasoning trace. If the workflow fails, inspecting state shows you exactly what each stage produced.
- You can persist state across runs. Replace
InMemorySessionServicewith a backed one and your workflow becomes resumable.
The shapes compose to fit it: a ParallelAgent runs the three independent checks
at once, a SequentialAgent feeds their merged findings into a verdict step, and
wrapping the whole thing in a LoopAgent lets it revise and re-check until the
verdict is “approved” (capped, of course, by max_iterations). One sentence, three
nested shapes — that compositionality is the point.
When to choose ADK workflows over a single LlmAgent
The test is the same as MAF: don’t build a workflow until a single agent demonstrably fails. Workflows add latency (you make N model calls instead of one), cost (N × tokens), and debugging surface (N prompts to tune). They earn their keep when:
- Roles genuinely differ (a writer needs different temperature/style than a critic).
- You want auditable stages (each
output_keyis a checkpoint). - A loop is the right shape (refinement against a quality bar).
When you can describe the task in one sentence with one tool list, use
one LlmAgent and stop there.
In one breath
- When one
LlmAgentstrains under too many personas, split it into workflow agents — declarative orchestration classes, each wrapping a list ofsub_agents. - Three shapes: SequentialAgent (pipeline, N feeds N+1), ParallelAgent (independent work, run concurrently), LoopAgent (refine until a stop signal) — and they compose into arbitrary pipelines.
- Sub-agents communicate only through session state: write with
output_key, read with{key}substitution in instructions — so each prompt stays focused and the state object is your audit trail. - ParallelAgent is for genuinely independent sub-agents; if one needs another’s
output, it’s sequential. Always cap LoopAgent with
max_iterations. - Don’t build a workflow until a single agent demonstrably fails — they cost N model calls, N× tokens, and N prompts to tune. They earn it when roles truly differ, stages must be auditable, or a refinement loop is the right shape.
Quick check
Quick check
Next
You’ve seen how to build ADK agents and workflows. The last lesson in the ADK series covers deployment — the three paths from local code to production: Agent Engine, Cloud Run, GKE.
Practice this in an interview
All questionsAn agent is an LLM placed in a loop where it reasons, chooses and calls tools or actions, observes the results, and repeats until a goal is met, rather than producing one response and stopping. The key differences are autonomy, tool use, memory and state, and multi-step control flow driven by the model's own decisions.
Use multiple agents when a task decomposes into distinct specialties or parallel subtasks that exceed one agent's context or reliability; avoid it when a single agent suffices, since multi-agent systems add coordination overhead, latency, cost, and error propagation. A supervisor architecture has an orchestrator routing work to specialized sub-agents, while a swarm lets peer agents hand off control to one another without a central coordinator.
ML workflows are multi-step DAGs with dependencies, and an orchestrator gives you dependency management, retries, backfills, caching, observability, and lineage that chained cron jobs cannot. Airflow is a general-purpose task orchestrator defining DAGs in Python, while Kubeflow Pipelines is ML-native, passing typed artifacts between containerized steps on Kubernetes with conditional logic like deploy only if accuracy exceeds a threshold. Choosing depends on whether you need generic scheduling or ML-specific, container-based pipelines.
ReAct interleaves reasoning traces with actions step by step, deciding the next tool call based on the latest observation. Plan-and-Execute first drafts a full multi-step plan and then executes it, which is more efficient and predictable for complex tasks but less adaptive, while Reflexion adds a self-reflection step where the agent critiques past failures and retries with that feedback.