# AgentTool vs sub-agent delegation

Source: https://tpiros.dev/blog/agent-tool-vs-coordinator-delegation

While delivering a workshop about [AI Agents in Skopje, North Macedonia](https://gdg.community.dev/events/details/google-gdg-skopje-presents-the-name-is-agent-ai-agent/) a question came up - what is the different between using `subAgent` and an Agent as a tool via the ADK. I'm sure this question has come up a few times so I thought I'd capture my answer in a post. There are two ways to give an agent help, and they look almost the same on the page.

You can wrap a job as a tool. The agent calls it, gets a result back, and keeps going. Or you can register it as a sub-agent and let the agent transfer the turn to it. The call hands off, and the sub-agent takes over the conversation.

`AgentTool` is the first. `subAgents` is the second. One line of difference in the constructor, and the opposite happens to control.

The whole choice comes down to one question: do you need the result back, or do you want to walk away?

# The data agent

Build a data agent. You ask a question in English, it writes SQL, runs it, and answers. That takes two jobs:

- **NL2SQL.** Turn the question into a SQL string.
- **Run SQL.** Execute that string, return rows.

Both are tools, and that's the whole first version of the agent. Ask it for revenue per region and it answers in prose. Ask it to *draw* the answer and it can't. There's no chart tool on it, and that's exactly the point. Adding one is where the two patterns split.

## NL2SQL and run-SQL are tools

Both are transactions. Clear input, clean output, done.

NL2SQL takes a question and returns one SQL string. It's an `LlmAgent` with a single job, so it gets wrapped as an `AgentTool`. Run-SQL takes a string and returns rows. No model needed, so it's a plain `FunctionTool`. Neither one needs to know what came before or what happens next. You call it, you read the answer, you move on.

```typescript

  name: 'data_assistant',
  model: 'gemini-flash-latest',
  instruction: 'Call nl2sql_agent to get SQL, then run_sql to get rows, then answer.',
  tools: [
    new AgentTool({ agent: nl2sqlAgent }), // the NL2SQL agent, wrapped inline
    runSql,                                // the deterministic FunctionTool
  ],
});
```

You wrap the agent as a tool right there in the `tools` array, export `rootAgent`, and run it with `npx adk run agent.ts`. No `Runner`, no session wiring. The CLI builds that for you.

Run it, and the root stays in the driver's seat the entire time. It calls NL2SQL, reads the SQL, calls run-SQL, reads the rows, writes the answer. Every result comes straight back to the agent that asked.

That's the AgentTool pattern. Discrete jobs, called and answered, with the agent holding control the whole way.

## Adding charts: the second version

Now you want charts. The tempting move is to bolt a third tool, `render_chart`, onto the same agent and call it done.

That gives you a worse agent. Drawing the right chart is a multi-step job, not a lookup. The agent has to read the rows it got back, remember the original question, decide that three categories and one number means a bar chart, build a Vega-Lite spec, call `render_chart`, and then explain what it made and why. It needs its own reasoning and its own tools.

So visualisation gets its own agent, and the data agent hands the turn to it. The chart work is registered as a sub-agent, and the data agent transfers control to it instead of calling it as a tool.

```typescript

  name: 'coordinator',
  model: 'gemini-flash-latest',
  instruction: 'Fetch the rows with the tools, then hand off to visualization_agent for the chart.',
  tools: [
    new AgentTool({ agent: nl2sqlAgent }), // discrete jobs stay tools
    runSql,
  ],
  subAgents: [visualizationAgent],         // the multi-step job is a sub-agent
});
```

Same `tools` array as before, with one line added: `subAgents`. That single field is the whole difference between the two samples.

Watch the run. The coordinator does the data fetch with its tools, the same as before. Then `transfer_to_agent` fires, and `visualization_agent` takes over. It reasons, calls `render_chart`, and answers you directly. The coordinator is done.

And here's the file the sub-agent wrote, live:

The coordinator never sees the chart. It handed off and stepped out. The sub-agent did the work and spoke to you itself.

# The actual difference

Both patterns look like "the root has access to another agent." The split is what happens when that other agent runs.

With **AgentTool**, the call is a function call. Control comes back. The root gets a return value and decides what to do with it. The user is still talking to the root.

With **a sub-agent**, the call is a hand-off. Control transfers. The sub-agent owns the turn now, with its own tools and its own reasoning budget, and it replies to the user directly. The root is out of the loop until the next turn.

| | AgentTool | Sub-agent (delegation) |
|---|---|---|
| Wiring | `tools: [new AgentTool({ agent })]` | `subAgents: [agent]` |
| The call is | a function call | a transfer of control |
| After it runs | result returns to the root | the sub-agent owns the turn |
| Who answers the user | the root | the sub-agent |
| Good for | discrete, transactional jobs | complex, stateful, multi-step work |

That's why NL2SQL is a tool and visualisation is a sub-agent. NL2SQL hands back a string the root needs to keep going, so control has to come back. Visualisation is a self-contained job that answers the user at the end, so control is meant to leave.

# The quick test

Three questions to pick the right one:

1. **Do you need the result back to keep working?** Yes: tool. The root can't continue without that SQL string, so NL2SQL has to return.
2. **Is the job a clean input-to-output transaction?** Yes: tool. One question in, one query out, no memory of the rest.
3. **Does the job need to own a multi-step turn with its own tools and judgment, then answer the user?** Yes: sub-agent. Visualisation analyses, decides, renders, and reports, so let it take the turn.

Same data agent. Two jobs hand back a result, one takes over the turn. The difference is a single field on the constructor, and it changes who's driving.
