The Task API: Structured Agent-to-Agent Delegation
Optimize agent-to-agent communication with ADK 2's Task API. Perform single-turn bounded delegation without conversational context overhead.

The Task API: Structured Agent-to-Agent Delegation
📦 Companion Code: View the complete, runnable code on GitHub:
part07in the cartpilot-adk2-series repository.
When support_agent needs an order's status, it doesn't need a conversation — it needs one bounded answer to one bounded question. Treating that as a full conversational sub-agent hand-off (the sub_agents pattern from Part 6) is more machinery than the job requires, and it's not free: every extra layer of conversational context is tokens you're paying for. ADK 2.0's Task API gives us a lighter-weight shape for exactly this.
This surface is genuinely new.
modeon anAgent("chat","single_turn","task") shows up in ADK 2.0's public discussions and Go documentation but was still stabilizing in the Python SDK at time of writing. Treat the pattern below as directionally correct and verify the exact signature against adk.dev before shipping it — the underlying idea (bounded, structured delegation vs. full conversational hand-off) is the durable lesson here regardless of exact syntax.
The three modes, conceptually
| Mode | What it means | CartPilot example |
|---|---|---|
chat (default for a sub-agent) | Full multi-turn conversation with the user; reachable via transfer_to_agent | search_agent, cart_agent, support_agent from Part 6 |
single_turn (default for a workflow node) | Does its job in one shot, no back-and-forth with the user, structured in → structured out | Order status lookup — a pure query, not a dialogue |
task | Like single_turn, but can hold a short back-and-forth with the user before finishing — useful for something like "collect the details of a return" without becoming a full specialist | Not used in this part; worth reaching for when a bounded job needs 1-2 clarifying questions before it can finish |
A single-turn order status agent
# cartpilot/support_agent.py (excerpt)
from google.adk.agents import Agent
from google.adk.tools.agent_tool import AgentTool
from pydantic import BaseModel, Field
def get_order_status(order_id: str) -> dict:
"""Looks up the status of a previous order.
Args:
order_id: The order reference, e.g. "CP-10234".
"""
return {"status": "success", "order_id": order_id, "state": "shipped",
"estimated_delivery": "2-3 business days"}
class OrderStatusRequest(BaseModel):
order_id: str = Field(description="The order reference to look up.")
class OrderStatusResult(BaseModel):
order_id: str
state: str
estimated_delivery: str
summary: str = Field(description="One friendly sentence summarizing status for the shopper.")
order_status_agent = Agent(
model="gemini-3.5-flash",
name="order_status_agent",
mode="task",
description="Looks up and summarizes a single order's status. Non-conversational.",
instruction="Call `get_order_status` with the given order_id and write a one-line friendly summary.",
tools=[get_order_status],
input_schema=OrderStatusRequest,
output_schema=OrderStatusResult,
)
# AgentTool is the reliable way to invoke a task agent today:
# support_agent calls it, gets a structured OrderStatusResult back, and
# stays in control of the conversation.
order_status_task = AgentTool(agent=order_status_agent)
support_agent = Agent(
model="gemini-3.5-flash",
name="support_agent",
description=(
"Specialist for order status, returns, and refund policy questions. "
"Delegate here for anything post-purchase or policy-related."
),
instruction="""
You handle CartPilot support questions.
- Use `get_return_policy` for return/refund policy questions.
- For order tracking, call `order_status_task` with the order_id — don't
try to answer status questions from memory or guesswork. Ask the
shopper for their order ID if missing.
- Be empathetic but concise. Don't promise anything the policy doesn't state.
""",
tools=[get_return_policy, order_status_task],
)Note input_schema alongside the output_schema we've used since Part 4 — a single_turn agent's contract is fully typed on both sides. That's what makes it feel like a function call rather than a conversation: support_agent doesn't hand off control and wait for a reply from a peer, it invokes something with a known shape and gets a known shape back.
Why this is the bigger token-cost lever than it looks
Compare two ways support_agent could have gotten an order status:
- Full conversational sub-agent (
sub_agents=[order_status_agent]inchatmode): the child agent would need enough conversational context to understand what's being asked, potentially ask its own clarifying questions, and its response becomes part of the ongoing multi-turn history that keeps growing with every future turn (see Part 3's token-growth discussion). - Single-turn task (what we did): one bounded call, typed input, typed output, no persistent conversational baggage added to the parent's history beyond the result itself.
Multiply this by every "just look something up" delegation in a real multi-agent system, and the difference compounds fast. The rule of thumb going forward: if a delegation is answerable with "give me X, I'll give you back Y" — no negotiation, no multi-step clarification needed — reach for a single-turn task, not a full conversational sub-agent. Reserve chat mode for delegations where the specialist genuinely needs to talk to the shopper, not just fetch something on the coordinator's behalf.
Try it
adk webAsk support: "What's the status of order CP-88213?" — in the event inspector you'll see a bounded call to order_status_task with a typed request and typed response, distinct from the open-ended transfer_to_agent hand-offs you saw moving between coordinator and specialists in Part 6.
What's next
Everything CartPilot does so far executes without asking permission. Part 8 adds human-in-the-loop: refunds above a threshold need a person to approve them before the agent acts.