In a Google ADK SequentialAgent, how does one agent's output reach the next agent?
answer
- agents do not return values to each other
- the channel is the session, not a call stack
- one field on the producer, a placeholder on the consumer
- flat namespace, so keys collide
- optional placeholder form for maybe-missing values
basics
~20 sThrough session state, not through a return value. Set output_key on the producing LlmAgent and ADK stores its final response under that key; the next agent reads it with a {key} placeholder in its instruction, or a tool reads it from state.
solid answer
~50 sADK agents do not return values to each other — a `SequentialAgent` just runs its `sub_agents` in order over one shared session. The wiring is `output_key`: give the producing `LlmAgent` `output_key="draft"` and ADK writes that agent's final response into session state under `draft` when it finishes. Downstream agents consume it either by templating it into their prompt (`instruction="Critique this draft: {draft}"`, with `{draft?}` if it may be absent) or from inside a tool that reads state. If the producer also declares an `output_schema`, the parsed structured object is stored rather than raw text. The consequences are worth stating: the channel is a flat namespace, so two agents sharing an `output_key` overwrite each other, and an agent that never sets `output_key` leaves nothing behind for the next step even though its text is in the event history.
code
python · 17 linesfrom google.adk.agents import LlmAgent, SequentialAgent
writer = LlmAgent(
name="writer",
model="gemini-2.0-flash",
instruction="Write a short release note for the feature described by the user.",
output_key="draft",
)
editor = LlmAgent(
name="editor",
model="gemini-2.0-flash",
instruction="Tighten this release note and remove marketing language.\n\nDRAFT:\n{draft?}",
output_key="final_note",
)
pipeline = SequentialAgent(name="release_notes", sub_agents=[writer, editor])go deeper
Remember the two halves: set output_key on the agent that produces the value, and reference it as {key} in the next agent's instruction. Say plainly that data moves through session state.
Explain that agents never return values to each other, that state is one flat per-session namespace, and that {key?} is the optional form for values that may not have been produced.
Bring the failure modes: colliding keys under ParallelAgent, stale values read from a previous turn when a producer was skipped, and prompt bloat from chaining full outputs — plus reading state in a tool when the payload is large.
Treat the key set as a pipeline contract — named centrally, owned, versioned — and set the convention for what may be templated versus passed by reference, since that choice determines token cost across every agent downstream.
## There is no return value The first thing to internalise about ADK composition is that agents do not call each other and collect results. A `SequentialAgent` runs sub-agent A to completion, then sub-agent B, over the same invocation context and the same session. Anything A wants B to see has to be left somewhere B can look. That somewhere is **session state** — a dict-like store attached to the session that survives across agents and across turns. ## output_key: the write side `output_key` is a field on `LlmAgent`. When set, ADK takes that agent's final response and stores it in session state under that key as the agent finishes. One line of configuration replaces what would otherwise be a custom callback or a tool whose only job is "save my answer". Two details matter: - It captures the agent's **final** response for the invocation, not intermediate tool calls or reasoning turns. - If the agent also declares an `output_schema`, the structured, parsed object is what lands in state, so downstream consumers get a dict rather than a string they must re-parse. Note the trade: declaring `output_schema` on an `LlmAgent` constrains it to producing that JSON — such an agent is not the place to also do tool calling or hand off to another agent. Without `output_key`, the agent's text still appears in the event stream, and a human watching a trace sees it — which is exactly why people are surprised when the next agent behaves as if nothing happened. Events are the history; state is the channel. ## Templating: the read side An `instruction` string is not static. ADK substitutes session-state values into `{}` placeholders before the prompt is sent, so a consumer agent is written as: `instruction="You are an editor. Critique the following draft and list concrete fixes.\n\nDRAFT:\n{draft}"` A missing key is a hard problem in a prompt template, so ADK supports an optional form: `{draft?}` resolves to nothing when the key is absent instead of failing. Use the optional form for anything produced by a branch that may not have run — a conditional step, a parallel branch that errored, the first pass of a loop before any draft exists. The alternative read path is a tool. Tools receive a context object that exposes session state, so a tool can pull `draft` out of state and act on it without the value ever being pasted into a prompt. That matters when the payload is large: templating a 50KB document into every downstream agent's instruction pays for those tokens on every turn of every agent that references it, whereas a tool reads it once, in code, for free. ## The failure modes **Key collisions.** State is one flat namespace per session. Two agents configured with the same `output_key` overwrite each other silently, and in a `ParallelAgent` that race is genuinely concurrent. Give each producer a distinct key — `research_summary`, `legal_review`, `market_analysis` — and treat the key set as part of your pipeline's contract. **Stale reads.** Because state persists, a placeholder can resolve to a *previous* turn's value if the current turn's producer did not run or failed early. A consumer that silently critiques last hour's draft is much harder to spot than one that fails loudly. When correctness depends on freshness, have the producer write a marker your consumer can check, or clear the key at the start of the pipeline. **Prompt bloat.** Chaining five steps that each template the previous step's full output produces a final prompt containing the entire pipeline. Summarise between steps, or move large payloads to artifacts and pass a reference. **Assuming ordering you did not declare.** In a `SequentialAgent` the ordering is real: B genuinely runs after A. Under a `ParallelAgent` there is no such guarantee between branches, so a branch that reads another branch's `output_key` is a race, not a pipeline. Cross-branch consumption belongs to a step placed *after* the parallel block, typically by nesting the `ParallelAgent` inside a `SequentialAgent` whose next member is the merger agent. ## Why this design Routing everything through state is what keeps the composition types simple. Workflow agents need to know nothing about their sub-agents' signatures — there are none — so any agent can be dropped into any position. It also means the whole intermediate data flow is inspectable: dump session state at the end of a run and you have every step's output, which is the fastest way to find which step in a five-agent chain actually went wrong. The cost is that the coupling is by string key rather than by type. Nothing checks that the consumer's `{draft}` matches the producer's `output_key="draft"` until the run happens and the placeholder resolves to nothing. Keep the keys in one module-level place, name them for the content rather than the producer, and treat a typo in a key as the same class of bug as a typo in a column name.
- What happens if the consumer's placeholder key was never written?A plain `{draft}` placeholder has nothing to resolve to and the substitution fails, which is why ADK supports the optional form `{draft?}` — it resolves to empty rather than erroring. Use the optional form for anything a conditional or parallel branch may not have produced, and make the consumer's instruction explicitly handle the empty case so it does not hallucinate a draft it was never given.
- Two ParallelAgent branches both set output_key='summary'. What goes wrong?They write the same key in one shared session state, concurrently, so the surviving value is whichever branch finished last — non-deterministically. Nothing errors; you just silently lose one branch's work. Give every branch a distinct key and have a merger agent placed after the parallel block read all of them.
- When would you read state inside a tool instead of templating it into the instruction?When the payload is large or the consumer does not need to reason over the whole thing. Templating puts the full value into the prompt, so you pay those tokens on every turn of every agent that references it; a tool reads the same value from state in code for free and can slice or transform it first. Templating is right for short results the model must actually reason about.
- Does output_key capture tool results too?No — it captures the agent's final response for the invocation, not its intermediate tool calls. Intermediate steps are visible in the event history for debugging, but if you need a specific tool's raw result available to a later agent, write it to state from the tool itself rather than expecting `output_key` to have picked it up.
saying these in an interview costs you the question
- Expecting an agent to return a value the next one receives as an argument
- Assuming an agent's visible reply is in state without output_key set
- Reusing one output_key across parallel branches
- Templating a huge document into every downstream instruction
- Trusting a state key that a skipped step was supposed to write