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:

  1. 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.

  2. Use workflow agents for deterministic orchestration. SequentialAgent and ParallelAgent use sub_agents with control flow fixed in code, so an LLM does not decide when to hand off.

  3. 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_agents may 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 SequentialAgent or ParallelAgent. Put the known steps, order, and termination condition in code.
  • If no, use an LlmAgent with sub_agents or AgentTool, based on who should own the conversation.

Must control always return to the parent?

  • If yes, use AgentTool or workflow agents. Both enforce the return through their structure.
  • If no, sub_agents can 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.