Table of Contents
The setup
Say you’re building a multi-agent system in Google ADK: a travel planner, research assistant, or customer service router. A parent orchestrator delegates work to several specialized child agents. Everything works in testing, but production exposes an awkward control-flow problem. A child may keep the conversation instead of handing it back, leaving the user stuck with a specialist. Or the parent may synthesize a response that the child was supposed to own.
Both failures come from the delegation pattern you chose.
Two patterns in plain English
With sub_agents, the parent puts the child agent in a list, and the LLM decides when to hand off via transfer_to_agent(). After the transfer, the child owns the conversation. It responds directly to the user, can hold a multi-turn exchange, and shares the parent’s session history. The parent stays out of the loop unless the child decides to transfer back.
With AgentTool, the child is wrapped as a tool. The parent calls it with a string input, the child runs its full reasoning loop in isolation, and returns a string. The parent uses that result to respond to the user. The child never communicates with the user directly, and control always returns to the parent by design.
The child agent can be nearly identical in both cases, but its place in the runtime is quite different.
Control flow diagrams
This is the control flow with sub_agents:
sequenceDiagram
actor User
participant Parent as TravelAgent (Parent)
participant Child as FlightAgent (Child)
User->>Parent: "I need to book a flight to Tokyo"
Parent->>Parent: LLM decides to transfer
Parent->>Child: transfer_to_agent("flight_agent")
Note over Parent: Parent exits the loop
Child->>User: "What dates are you flying?"
User->>Child: "March 15-22"
Child->>User: "Got it. What's your departure city?"
User->>Child: "New York"
Child->>Child: LLM decides whether to transfer back
Note over Child,Parent: Transfer back is LLM-driven -- may never happen
Child-->>Parent: transfer_to_agent("travel_agent") [maybe]
With AgentTool, the flow looks like this:
sequenceDiagram
actor User
participant Parent as TravelAgent (Parent)
participant FlightTool as FlightAgent (as AgentTool)
participant HotelTool as HotelAgent (as AgentTool)
User->>Parent: "Plan my Tokyo trip, March 15-22, from New York"
Parent->>FlightTool: call tool("Find flights NYC→Tokyo, March 15-22")
FlightTool-->>Parent: "Best option: ANA NH010, $1,240, departs 11:55am"
Parent->>HotelTool: call tool("Find hotels Tokyo, March 15-22, budget mid-range")
HotelTool-->>Parent: "Recommended: Shinjuku Granbell, $145/night"
Note over Parent: Parent synthesizes both results
Parent->>User: "Here's your Tokyo trip plan: [combined itinerary]"
With sub_agents, the parent hands over the conversation. With AgentTool, the parent keeps driving the conversation and uses the child much like a function call.
Code examples
sub_agents: transfer pattern
from google.adk.agents import LlmAgent
# FlightAgent is a specialist -- it owns the flight booking conversation
flight_agent = LlmAgent(
name="flight_agent",
model="gemini-2.0-flash",
instruction="""You are a flight booking specialist.
Help the user find and select flights.
When you have gathered all necessary information and completed
flight selection, transfer back to the travel_agent to continue
planning the rest of their trip.
""", # Explicit transfer-back instruction -- critical for avoiding stickiness
tools=[search_flights, get_flight_details],
)
# TravelAgent delegates to flight_agent via transfer
travel_agent = LlmAgent(
name="travel_agent",
model="gemini-2.0-flash",
instruction="""You are a full-service travel planner.
For flight-related requests, transfer to flight_agent.
For hotel requests, transfer to hotel_agent.
Coordinate the full trip plan when all components are confirmed.
""",
sub_agents=[flight_agent, hotel_agent], # Children available for transfer
)
When the user asks about flights, the travel_agent LLM generates a transfer_to_agent("flight_agent") call. The session then passes to flight_agent, which can ask clarifying questions over several turns, run tools, and transfer back if its prompt leads it to do so.
AgentTool: invocation pattern
from google.adk.agents import LlmAgent
from google.adk.tools.agent_tool import AgentTool
# Same FlightAgent -- but now it's wrapped as a tool
flight_agent = LlmAgent(
name="flight_agent",
model="gemini-2.0-flash",
instruction="""You are a flight search specialist.
Given a flight query string, search for available options
and return a structured summary of the best results.
Return a plain-text summary -- the calling agent will present it to the user.
""", # No need for transfer-back instructions -- control always returns
tools=[search_flights, get_flight_details],
)
hotel_agent = LlmAgent(
name="hotel_agent",
model="gemini-2.0-flash",
instruction="""You are a hotel search specialist.
Given a hotel query, return a structured summary of available options.
""",
tools=[search_hotels, get_hotel_details],
)
# Parent wraps children as AgentTools and synthesizes their output
travel_agent = LlmAgent(
name="travel_agent",
model="gemini-2.0-flash",
instruction="""You are a full-service travel planner.
Use the flight_agent_tool and hotel_agent_tool to gather options,
then present a cohesive trip plan to the user.
""",
tools=[
AgentTool(agent=flight_agent), # Child runs in isolation, returns string
AgentTool(agent=hotel_agent), # Can be called in parallel or sequence
],
)
The parent LLM decides what to query and when. Each child runs its full reasoning loop and returns a result, which the parent combines into its response. The user only interacts with the parent.
Comparison
| Dimension | sub_agents | AgentTool |
|---|---|---|
| Who responds to user | Child | Parent |
| Session history sharing | Shared | Isolated |
| Multi-turn in child | Yes | No |
| LLM hops | Fewer | Extra hop per tool call |
| Control return | LLM-driven (unreliable) | Guaranteed |
| Parallelism | No (use ParallelAgent) | Yes, parent can call multiple |
| Best for | Specialist owns the conversation | Parent synthesizes multiple sources |
The difference in LLM calls matters at scale. Every AgentTool invocation adds another call, so a parent querying four specialists makes five LLM calls that turn. With sub_agents, the child talks directly to the user without another parent call in the middle. At high volume, especially with expensive models, that affects both cost and latency.
When a sub-agent keeps control
A common production failure with sub_agents occurs when the child LLM never generates a transfer_to_agent() call back to the parent.
For example, the child finishes helping with flights and the user asks, “Great, now what about hotels?” The flight agent might try to handle the hotel request itself. It might also say, “I only handle flights, please contact our hotel team,” leaving the user stranded. In either case, the parent never regains control.
ADK GitHub issues #147, #371, and #620 describe variations of this behavior. Because transfer_to_agent() is generated by the LLM, the transfer is never guaranteed. It depends on:
- How clearly the child’s system prompt instructs it to transfer
- Whether the LLM judges the current task as “complete”
- The LLM’s in-context reasoning at that moment
You have three options, listed from least to most structurally reliable:
-
Give the child explicit transfer instructions. Tell it when to transfer back and name the target agent. For example: “When you have confirmed the user’s flight selection, immediately transfer back to travel_agent to continue their trip planning.” This improves the odds, although the LLM still decides whether to make the call.
-
Use workflow agents for deterministic orchestration.
SequentialAgentandParallelAgentusesub_agentswith control flow fixed in code, so an LLM does not decide when to hand off. -
Use
AgentTool. Control returns to the parent automatically. This is usually the better choice when reliable return matters more than a multi-turn conversation with the child.
Workflow agents for fixed orchestration
ADK includes SequentialAgent, ParallelAgent, and LoopAgent for workflows whose control flow belongs in code. These agents use sub_agents internally, but an LLM does not decide the sequence at runtime.
from google.adk.agents import SequentialAgent
# Steps run in fixed order: flight → hotel → itinerary
# No LLM decides the sequence -- it's deterministic
trip_planner = SequentialAgent(
name="trip_planner",
sub_agents=[
flight_research_agent, # Runs first, always
hotel_research_agent, # Runs second, always
itinerary_agent, # Synthesizes results, runs last
],
)
SequentialAgent executes sub-agents in order and passes each output to the next agent. ParallelAgent runs them concurrently and collects their results. LoopAgent repeats until it meets a termination condition.
Workflow agents fit cases where you know the steps before the conversation starts. Each agent still owns a specialized task, while the handoffs remain predictable. In exchange, you lose the LLM orchestrator’s ability to choose agents dynamically from the user’s intent.
Choosing a pattern
Start with the child agent’s role in the conversation:
Does the child need to hold a multi-turn conversation with the user?
- If yes, use
sub_agents. The child can ask follow-up questions, gather context over several turns, present options, and wait for answers. - If no, use
AgentTool. The parent sends a query, the child returns a result, and the parent incorporates it into the response.
Does the parent need to query several specialists and combine their results?
- If yes, use
AgentTool. The parent calls each specialist and assembles their results. - If no,
sub_agentsmay be simpler because it requires fewer LLM calls and lets the specialist own its part of the conversation.
Is the orchestration logic fixed at build time?
- If yes, use a workflow agent such as
SequentialAgentorParallelAgent. Put the known steps, order, and termination condition in code. - If no, use an
LlmAgentwithsub_agentsorAgentTool, based on who should own the conversation.
Must control always return to the parent?
- If yes, use
AgentToolor workflow agents. Both enforce the return through their structure. - If no,
sub_agentscan work with careful transfer instructions in the child prompt.
As a default, start with AgentTool. Move to sub_agents when the child genuinely needs to talk with the user over several turns. AgentTool is easier to reason about and test, and it avoids the uncertain transfer back to the parent.
