The Sequential Agent Pattern
The previous post walked through a single agent: one model, one tool, one response as well as a parallel agent that does multiple translations in a single with three parallel calls. Fine for atomic tasks. But some jobs have stages, and each stage needs different expertise or different data.
So you break the work into steps. Hand each step to a specialised agent. The output of one feeds the input of the next.
What is the sequential agent pattern?
A pipeline of agents. Each one:
- Has a focused job. Narrow instruction, (optionally) its own tools.
- Receives context from the previous step. Via an output key the next agent can reference.
- Produces output for the next step. Writing results into session state under its own key.
No parallel execution. No branching. No orchestrator making dynamic routing decisions. Agent 1 runs, then Agent 2, then Agent 3. A straight line.
Why not just use one agent?
You could stuff all the tools and every instruction into a single prompt. That falls apart for 3 reasons:
- Prompt dilution. The more instructions you cram into a single prompt, the less reliably the model follows each one.
- Tool ambiguity. With many tools available, the model might call the wrong one or skip one entirely. Give each agent only its own tools and that confusion disappears.
- Debuggability. When something breaks in a 3-step pipeline, you can see exactly which step failed. With a monolithic agent, you’re guessing.
You trade a small amount of latency (multiple API calls instead of one) for significantly better reliability and clarity.
The project
We’ll build Quote Nerd: a CLI that generates a “Daily Inspiration” card in 3 steps:
- Fetch a random quote from the zenquotes.io API.
- Research the author on Wikipedia.
- Write an inspiration card combining the quote with the author’s background.
Each step is a different LlmAgent. ADK’s SequentialAgent runs them in order, passing each agent’s output to the next via output keys.
Click Run to watch all 3 agents work in sequence:
Setup
The package.json:
{
"name": "sequential-agent",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "npx adk run --log_level ERROR agent.ts",
"web": "npx adk web agent.ts"
},
"dependencies": {
"@google/adk": "^0.5.0",
"zod": "^4.3.6"
}
}
Two dependencies: @google/adk (bundles the GenAI SDK under the hood) and zod for tool parameter schemas. adk run fires up the agent from the command line; adk web spins up ADK’s built-in web UI for testing.
The code
Everything lives in one file: agent.ts. Let’s go piece by piece.
Tools
import { FunctionTool, LlmAgent, SequentialAgent } from "@google/adk";
import { z } from "zod";
const getRandomQuote = new FunctionTool({
name: "get_random_quote",
description: "Fetch a random inspirational quote with its author",
parameters: z.object({}),
execute: async () => {
const res = await fetch("https://zenquotes.io/api/random");
const data = (await res.json()) as any;
return { quote: data[0].q, author: data[0].a };
},
});
const searchWikipedia = new FunctionTool({
name: "search_wikipedia",
description:
"Search Wikipedia for biographical information about a person",
parameters: z.object({
query: z
.string()
.describe("The person to search for on Wikipedia"),
}),
execute: async ({ query }) => {
const searchUrl = `https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(query)}&format=json`;
const searchRes = await fetch(searchUrl);
const searchData = (await searchRes.json()) as any;
if (!searchData.query?.search?.length)
return { result: "No results found" };
const title = searchData.query.search[0].title;
const summaryRes = await fetch(
`https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(title)}`
);
const summaryData = (await summaryRes.json()) as any;
return { result: summaryData.extract || "No information found" };
},
});
Tools in ADK are self-contained: schema and implementation live together in a single FunctionTool. parameters defines the shape with zod, execute is the function that runs when the model calls it.
2 tools, one per agent. The quote tool takes no parameters (it fetches a random quote). The Wikipedia tool takes a search query. Agent 3 (the card writer) has no tools at all; it only needs the LLM’s synthesis ability.
Sub-agents
const quoteFetcherAgent = new LlmAgent({
name: "QuoteFetcherAgent",
model: "gemini-2.0-flash",
description:
"Fetches a random inspirational quote using the quote tool.",
instruction: `You fetch random quotes using the provided tool. Call the tool, then return the quote and author in this exact format:
Quote: <the quote>
Author: <the author>`,
tools: [getRandomQuote],
outputKey: "quote_result",
});
Each LlmAgent gets a name, a model, an instruction, the tools it needs, and an outputKey. That key is how ADK passes results between agents: when this agent finishes, its final text response gets stashed in session state under quote_result.
const wikipediaResearcherAgent = new LlmAgent({
name: "WikipediaResearcherAgent",
model: "gemini-2.5-flash",
description:
"Researches a person on Wikipedia and returns a concise bio.",
instruction: `You research people on Wikipedia. The previous agent fetched a quote — here is its output:
{quote_result}
Extract the author's name from the output above and search for them using the Wikipedia tool. Return a concise 2-3 sentence bio about who they are and why they are notable.`,
tools: [searchWikipedia],
outputKey: "author_bio",
});
Notice {quote_result} in the instruction. ADK swaps this for the value stored under that key in session state (the output from Agent 1). No manual parsing, no regex, no glue code.
const inspirationCardAgent = new LlmAgent({
name: "InspirationCardAgent",
model: "gemini-2.5-flash",
description:
"Writes a punchy one-line daily inspiration card.",
instruction: `You write punchy "Daily Inspiration" cards. Combine the quote with the author's background into exactly ONE line that ends with "— <Author Name>". No preamble, no quotes around the output, no extra formatting.
Quote information:
{quote_result}
Author bio:
{author_bio}`,
tools: [],
outputKey: "inspiration_card",
});
Agent 3 pulls in both {quote_result} and {author_bio}. No tools; it only needs the LLM to synthesise a final card from the accumulated context.
The sequential agent
export const rootAgent = new SequentialAgent({
name: "QuoteNerdPipeline",
description:
"Fetches a quote, researches the author, and writes an inspiration card.",
subAgents: [
quoteFetcherAgent,
wikipediaResearcherAgent,
inspirationCardAgent,
],
});
SequentialAgent takes an array of sub-agents and runs them in order. ADK handles the tool-calling loop for each agent, the state passing between them, and the final output. The whole file is about 90 lines.
Without ADK, you’d be writing a runAgent function with a tool-calling loop, manual state management, regex parsing of agent outputs, and a main function wiring it all together. ADK collapses all of that into declarative configuration.
How state flows
outputKey does all the heavy lifting. When an LlmAgent finishes, ADK stashes its final text response in session state under that key. Downstream agents reference it with {key_name} in their instructions.
The flow:
QuoteFetcherAgentruns, output stored asquote_resultWikipediaResearcherAgentruns, reads{quote_result}, output stored asauthor_bioInspirationCardAgentruns, reads{quote_result}and{author_bio}, output stored asinspiration_card
No shared state interface to define. No glue code. outputKey + template syntax handles everything.
Why this works
The task has a natural order, and the sequential pattern makes it explicit:
- Can’t research the author until you know who they are. Agent 2 depends on Agent 1.
- Can’t write the card until you have the quote and the bio. Agent 3 depends on both.
- Each agent is simple. Narrow instructions, minimal tools, clear responsibilities. Each one performs well because it’s not trying to juggle everything.
A single-agent approach would need one massive prompt explaining all 3 steps, all tools available at once, and fingers crossed the model executes them in the right order. Sequential makes the order explicit and declarative.
When to reach for this pattern
- Tasks have a clear order. Step B needs Step A’s output.
- Each step benefits from a focused prompt. Different instructions for different roles.
- Different steps need different tools. Each agent gets only what it needs.
- You want visibility into intermediate results. Each agent’s output lives under its own key, easy to inspect at any point.
Wrong fit when steps can run independently (that’s parallelisation) or when the next step depends on dynamic conditions (that’s routing). We’ll cover both in later posts.
Wrapping up
The sequential pattern takes the single agent and stretches it into a pipeline. SequentialAgent makes the orchestration declarative: define your tools, define your agents, list them in order. The complexity that used to live in hand-rolled runner functions and state management collapses into configuration.