Skip to content

Build a multi-agent deep research pipeline with Google ADK

Build a deep research agent in Google ADK: plan, search and critique in a LoopAgent, stop early with exit_loop, and share results through session state.

By Albert Folch ·

Try it live

This agent uses the code in this guide. No account needed.

A single agent with a search tool stops when it feels finished. A research pipeline gives that decision to a separate agent with a written standard: one agent plans, one searches, one checks the coverage and sends the searcher back if it falls short, and one writes the report.

The code below is the Deep Research Agent on this site. Everything was checked against google-adk 2.10.0, using the agent's real objects with a scripted model and a live run on this site.

The short version

Four LlmAgents and two shell agents, none of which call a model themselves. SequentialAgent runs its children in order, and LoopAgent repeats them:

python
research_loop = LoopAgent( name="research_loop", description="Iterates Researcher → Critic until coverage is sufficient or max_iterations is reached.", sub_agents=[researcher, critic], max_iterations=3, ) root_agent = SequentialAgent( name="deep_research_agent", description=( "Autonomous deep-research pipeline. Decomposes a question into a plan, " "iteratively gathers evidence with Tavily, critiques coverage, and " "composes a cited markdown report — all in one turn." ), sub_agents=[planner, research_loop, writer], )

The flow is planner, then researcher and critic up to three times, then writer. All of it happens in one user turn, with no user input between steps.

Pass results through session state

The sub-agents never call each other. Each one saves its final text to session state with output_key, and later agents read it through placeholders in their instructions:

KeyWritten byRead by
research_planplannerresearcher, critic, writer
findingsresearcherresearcher (next pass), critic, writer
critiquecriticresearcher (next pass)
final_reportwriternothing: it's the answer

Here is the researcher, with its Tavily server attached through McpToolset, which the MCP guide covers in detail:

python
researcher = LlmAgent( model=FAST_MODEL, name="researcher", description="Executes the research plan using Tavily web search and content extraction, building cited findings.", instruction=researcher_prompt, tools=[ McpToolset( connection_params=StreamableHTTPConnectionParams( url="https://mcp.tavily.com/mcp/", headers={"Authorization": f"Bearer {TAVILY_API_KEY}"}, ), ) ], before_agent_callback=before_agent_callback_update_tools, output_key="findings", )

FAST_MODEL is a LiteLlm model routed through OpenRouter, built in the repo's agents/model_factory.py. A plain Gemini model name also works, and then pip install "google-adk[mcp]" is all you need. LiteLlm also needs the litellm package, which google-adk[extensions] includes.

Two things in the researcher's prompt make the loop work. It is an f-string, so the doubled braces come out as single ones:

plain text
- **Research plan** (always present): {{research_plan}} - **Previous findings** (present from iteration 2 onwards — empty on first run): {{findings?}} - **Critic feedback** (present from iteration 2 onwards — gaps to fill): {{critique?}}

The ? makes a key optional. On the first pass findings and critique don't exist yet, and without the ? the agent would raise an error instead of starting.

The second is less obvious: output_key overwrites. Each pass replaces findings, it doesn't add to it, so the prompt tells the researcher to "keep them and add new findings" and "never regress". If you leave that out, every pass throws away the one before it.

Stop the loop with exit_loop

Without an exit signal, the loop always runs to max_iterations. The critic stops it early by calling this tool:

python
def exit_loop(tool_context: ToolContext) -> dict: """Signal that research coverage is sufficient and the research loop should exit. Call this BEFORE outputting your STATUS: DONE line. The tool call is what actually exits the loop; the STATUS line is for downstream agents to read. """ tool_context.actions.escalate = True return {"status": "loop_will_exit"}

ADK adds tool_context itself, so the model sees a tool with no arguments. LoopAgent watches each event for actions.escalate and behaves like this:

  • The current sub-agent finishes its turn first. The critic still gets its reply to the tool call and writes STATUS: DONE, which is saved to critique.
  • Only the loop stops. SequentialAgent doesn't check escalate, so the writer still runs.
  • Reaching max_iterations isn't an error. The loop just ends and the writer uses whatever findings exist.

ADK has its own version, from google.adk.tools import exit_loop, which also sets skip_summarization. That ends the critic's turn at the tool call, so it writes no closing text and critique is never saved. Use the built-in tool if nothing reads that text; this agent writes its own so the critic can state why it stopped.

Give the critic a bar it can count

A critic asked whether the research is "good enough" tends to agree after one pass. This one gets rules it can check, and the prompt tells it to apply them mechanically:

plain text
A subquery clears the bar only when ALL of the following are true: - **≥5 sourced facts** are recorded under that subquery - The facts come from **≥2 distinct domains** (not all from one site) - At least one fact addresses a **quantitative or specific detail** (a number, a date, a version, a named entity) — not just qualitative summary

Its output is either STATUS: DONE or STATUS: CONTINUE with a numbered list of gaps. The gaps must be specific, because the researcher reads them through {critique?} on the next pass.

In a live run on a Postgres replication question, the critic returned STATUS: CONTINUE, the researcher went back, and the critic then called exit_loop and returned STATUS: DONE.

All four agents emit text, so decide what the user sees. This site sets "finalSubAgent": "writer" in the agent's metadata.json and shows the other agents' output as collapsed steps. That field belongs to this site, not ADK.

LoopAgent and SequentialAgent are deprecated

In ADK 2.10, creating either one emits a DeprecationWarning: "LoopAgent is deprecated in favor of Workflow and will be removed in a future version. Workflow cannot yet be used as an LlmAgent sub-agent."

They still work, and this agent runs on them. It pins google-adk==2.10.0 so an upgrade can't remove them unannounced, and you should pin too. New projects should look at Workflow in google.adk.workflow, a graph-based replacement this guide doesn't cover.

Common errors

KeyError with "Context variable not found". A placeholder names a state key that doesn't exist yet. Add ? if it can be missing, or check the agent that should write it: output_key only saves a final response that contains text, so an agent that ends without any text saves nothing.

Agent ... already has a parent agent. The same agent object was added to two parents. Create a separate instance for each one.

Failed to create MCP session: … Client error '401 Unauthorized'. The search server rejected the key. This agent builds its header without checking the variable, so an unset TAVILY_API_KEY sends Bearer None. The MCP guide covers the other connection errors.

The loop always runs to the limit. The critic isn't calling the tool. Check it's in the critic's tools, and tell the prompt plainly that the call is what ends the loop, not the status line. A critic that only writes STATUS: DONE changes nothing.

The loop never ends. max_iterations defaults to None, which means run until something escalates. Always set it. Three passes is this agent's limit; each one adds searches and tokens.

Later passes lose earlier findings. output_key overwrote them. See the merge instruction above.

Try it

Ask the Deep Research Agent a question with several angles, such as a comparison with a recent date in it, and open the steps to watch the plan and the critic's decisions. Its page links to the full source on GitHub. For agents that run code instead of searching, see ADK code execution: build a data analyst agent.

Want this in production?

I help teams design ADK agents and take them to production: MCP integrations, evaluation, deployment and guardrails.

Talk to me

Have an agent in mind?

Describe it and the agent builder will design it with you.