Module 5: Multi-Agent Systems: Agents That Delegate Tasks
4. An agent as another agent's tool: native delegation
Description
By the end of this lesson you'll be able to connect a complete agent — with its own model, its own system prompt, and its own tools — to another agent's ai_tool port, so the first one can delegate work to it whenever the model decides; you'll be able to read the nested trace that delegation produces to know exactly what happened at each level; and you'll be able to choose, with judgment, between the two native mechanisms n8n offers for this, knowing what each one wins and what each one gives up.
This matters because it's the point where the previous two lessons' design stops being a drawing. Up to now you have a paper split of responsibilities and a table of who owns what. This lesson is the wire. And it's also, without exaggeration, the reason this module exists: for years, in n8n, "agents delegating tasks to each other" was a figure of speech for describing a Switch with AI nodes on each branch. Today it's literal — the specialist connects to the same port where you connected a Gmail node in Module 4, and the calling agent doesn't know or care that there's another agent reasoning on the other end. To it, it's just another tool. That symmetry is the entire trick, and understanding it well is what separates someone who builds multi-agent systems from someone who describes them.
Connection to the module: lesson 3 gave you the architecture — orchestrator with the conversation and the memory, workers with their domains and their tools — and left you with the wiring pending. This lesson resolves it. It also brings back two things from Module 4 almost unchanged: a tool's contract from lesson 5 (name and description are the only thing the calling agent has to decide with) and $fromAI() from lessons 3 and 6 (the mechanism by which the model builds the call's arguments). What's still incomplete here — exactly what shape the specialist's returned response takes, and what happens when it can't resolve something — is lesson 5.
From a tool that does one thing to a tool that thinks
Think of the difference between a calculator and an accountant.
When you give a calculator a number, you get exactly what you asked for. There's no judgment involved: you put in 1200 * 0.19, out comes 228. If you give it bad data, it produces a bad result with the same confidence. It's useful precisely because it's predictable.
When you send something to an accountant, something else happens. You tell them "check this $1,200 charge the customer doesn't recognize" and they decide what to look at first. They check the record, find nothing, it occurs to them the purchase might have been made under another name, they check that too, and only then do they respond. You didn't give them a sequence — you gave them an assignment. The steps were theirs to choose, based on what they found along the way.
Module 4's tools are calculators. A Gmail node connected to the ai_tool port does exactly one thing: sends an email with the parameters it receives. A Postgres node queries and returns rows. Predictable, and that's their virtue.
What you're going to connect in this lesson is an accountant. From the calling agent's point of view, it looks identical: a tool with a name, a description, and a parameter. It connects to the same port. The agent decides to call it the same way, comparing its description against what it needs. But on the other end of the wire, instead of a node that executes an action, there's a complete agent that's going to reason, choose between its own tools, evaluate results, and maybe call three tools before responding.
That symmetry isn't an implementation detail: it's what makes the pattern scale. If delegating to an agent required a different mechanism than using a tool, you'd have to design the orchestrator differently. Because it's the same mechanism, everything you learned about tool contracts applies without translation, and an orchestrator can have a mix of agents and regular tools with no conceptual problem.
The AI Agent Tool node
n8n 2.0 has a specific sub-node for this: AI Agent Tool. Its internal type is @n8n/n8n-nodes-langchain.agentTool, and the key word in that string is the last one: agentTool, not agent. It's an agent packaged as a tool.
The difference from the AI Agent node you already know is one of position in the graph, not of nature:
AI Agent (root) | AI Agent Tool (sub-node) | |
|---|---|---|
| Category in n8n | Root node | Sub-node of type tool |
| How its work comes in | Through the main connection, from a trigger | Through another agent's ai_tool port, when that agent decides to call it |
| Who decides it runs | The flow (a message arrived) | The model of the agent it's connected to |
| Where its output goes | Through the main connection, to the next node | Back to the agent that called it, as the tool's result |
| Does it have its own sub-nodes? | Yes | Yes — it also has its own Chat Model, Memory, and Tool ports |
That last row is the important one, and it's what makes everything else possible. An AI Agent Tool isn't a "reduced" agent: it has its own ports underneath, and its own model and its own tools hang off it, same as off the root agent. On the canvas it looks like a second level of branching: three agent-tools hang off the root agent, and each one's own tools hang off it.
The ports
| Port | Connection type | Required? | Notes |
|---|---|---|---|
| Chat Model | ai_languageModel | Yes | Can be a different model than the calling agent's |
| Tool | ai_tool | No, but with no tools it can only talk to itself | Its domain's tools go here |
| Memory | ai_memory | No | By lesson 3's rule, it's normally left empty |
| Output upward | ai_tool | Yes | Connects to the Tool port of the agent that's going to call it |
The parameters
These are the fields you configure in the node's panel. Let's go one by one, because each one answers a different question.
Description. The text the calling agent reads to decide whether this tool applies. It's exactly the same field, with exactly the same function, as the Description of any Module 4 tool — with one difference of emphasis: here you don't describe an action, you describe a scope of competence. "Sends an email" is an action's description; "resolves any case related to charges, unrecognized charges, and billing" is a scope's description. And as in Module 4, the part that pays off the most is saying when NOT to use it.
Source for Prompt (User Message). Where this agent's assignment comes from. When the node is used as a tool, the option that serves you is defining it yourself on the node (in the panel it shows up as defining it below, in the node itself), because the assignment doesn't come from a chat trigger — it comes from the calling agent.
The prompt text (User Message). The assignment goes here. And this is where $fromAI() comes in: you don't write a fixed assignment, you write an expression that tells the calling agent's model what to draft. It's the same mechanism from Module 4, applied to something more interesting than an order_id:
{{ $fromAI("task", "Complete, self-contained description of the case this specialist must resolve. Include every piece of data you already gave or the customer mentioned (IDs, amounts, dates). The specialist has no access to the conversation.", "string") }}
Read that description carefully. It's doing two jobs: it tells the model what to write, and it reminds it of an architecture constraint — the specialist doesn't see the conversation — that if it isn't written there, the model has no way of knowing. A $fromAI("task", "The task") without that clarification produces assignments like "check what they asked me," which the specialist can't resolve because it doesn't have the "asked me."
Options → System Message. The specialist's system prompt. Short, single-domain, with its rules and its output format — the one you wrote in exercise 3 of lesson 3.
Options → Max Iterations. How many reason-act-observe turns this agent can take before giving up. It comes with a default value — 10 in the AI Agent node — and here it matters twice as much as in a standalone agent, because you're nesting loops. Lesson 6 is dedicated entirely to this.
Options → Return Intermediate Steps. Whether the specialist's output includes the detail of its own tool calls. Turn it on while building: without this, when a specialist returns something odd you're only going to see its conclusion, with no way to know which tools it checked to get there. In production you can turn it off if the volume of logs bothers you.
Honest note about labels. n8n moves and renames fields between minor versions fairly often — the
AI Agentnode itself stopped having an "agent type" selector in 1.82, and the configuration panel got redesigned in 2.0. The concepts above are stable: description for the caller, source of the assignment, own system prompt, iteration limit, trace. Verify the exact names and which tab each one lives in on your instance's node panel before assuming they're where a tutorial says. If something doesn't show up under that name, look for the concept, not the exact string.
Worked example: connecting billing_specialist to triage_agent
Let's wire up the system you designed in lesson 3. Let's start with a single specialist to see the mechanism clearly, and the rest is repetition after that.
Step 1 — The orchestrator already exists. On the canvas you have a Chat Trigger connected by main to an AI Agent node called triage_agent, with its Chat Model and its Postgres memory connected to the ai_memory port. Its ai_tool port is empty for now.
Step 2 — You add the AI Agent Tool node. You search for "AI Agent Tool" in the node panel and drop it on the canvas. You rename it to billing_specialist. The node's name matters: it's what you're going to see in the trace and it's part of how the calling agent refers to this tool.
Step 3 — You connect its output to the orchestrator. You drag from billing_specialist's top connector to triage_agent's Tool port. The connection created is of type ai_tool — the same curved line you already used to connect a Gmail node. Visually there's no difference at all, and that's exactly what we want.
Step 4 — You connect its own model. You connect a chat model node to billing_specialist's Chat Model port. Notice it doesn't have to be the same one the orchestrator uses: here you can put the most capable model you have available, because this agent makes decisions about money, while the orchestrator only decides who to call.
Step 5 — You connect its tools. You connect its three domain tools to billing_specialist's Tool port: lookup_charge, get_customer_profile, and open_dispute. Each with its Description and its $fromAI() well written, as you learned in Module 4.
Step 6 — You configure the node. This is the real content:
# Node: AI Agent Tool — Name: billing_specialist
# (connected to triage_agent's Tool port)
Description:
Resolves charge cases, unrecognized charges, invoice amounts,
and dispute requests. Use it when the customer mentions a
charged amount, a card, an account statement, or an invoice.
Do NOT use it for order status, shipments, or product
returns: order_specialist exists for that.
Returns a summary of the finding and the case's status; it doesn't
return text to show the customer as-is.
Source for Prompt (User Message):
Defined in this node
Prompt (User Message):
{{ $fromAI(
"task",
"Complete, self-contained assignment for the billing
specialist. It must include the amount, the charge's
approximate date, the customer's ID, and what the resolution
should look like. This specialist does NOT have access to the
conversation history: any relevant data the customer
mentioned in earlier turns must be written here.",
"string"
) }}
Options:
System Message:
You are TuTienda's billing specialist. You resolve charges
the customer doesn't recognize, amount questions, and dispute
requests. You don't handle product returns or shipping status:
if the assignment belongs to that domain, don't use any tool
and report it as out of your scope.
Procedure: use lookup_charge with the amount and the approximate
date. If it doesn't show up, check get_customer_profile in case
the purchase was made under another name or with another card
from the same customer. Only if it still doesn't show up, use
open_dispute.
Never promise a refund or a resolution deadline. You can
confirm the dispute got opened and its reference number.
You work with the assignment you receive; you don't have a history
of the conversation. If you're missing a piece of data, don't make it up:
report it.
Return: what you found, what action you took, whether the case is
closed or pending, and what data is missing if it's pending.
Don't write greetings or sign-offs: your output is read by another agent.
Max Iterations: 6
Return Intermediate Steps: true
Step 7 — You tell the orchestrator it exists. The triage_agent can already see the tool, because it's connected. But its system prompt has to declare the usage policy:
# Node: AI Agent — Name: triage_agent
# Options → System Message:
You are TuTienda's first line of support. Your only job is to
understand what the customer needs and delegate it to the right
specialist. You don't check systems or resolve cases on your own.
Available specialists:
- billing_specialist: charges, fees, billing, disputes.
- order_specialist: orders, shipments, delays, returns.
- sales_specialist: product recommendations and pricing.
Rules:
- If the message carries more than one topic, delegate each topic
separately and compose a single response at the end.
- Every assignment you send must be self-contained: include the
data the customer gave at any turn of the conversation, because
specialists don't see the history.
- If a specialist reports the case is pending due to a missing
piece of data, ask the customer for it before delegating again.
- Don't make up domain information. If no specialist applies,
say so honestly.
- Compose the final response to the customer with a warm tone, in a
single voice, without repeating greetings.
Step 8 — How it looks exported. If you export the workflow as JSON, the part that matters is connections, because that's where you see the real graph:
{
"connections": {
"billing_specialist": {
"ai_tool": [[ { "node": "triage_agent", "type": "ai_tool", "index": 0 } ]]
},
"Anthropic Chat Model (billing)": {
"ai_languageModel": [[ { "node": "billing_specialist", "type": "ai_languageModel", "index": 0 } ]]
},
"lookup_charge": {
"ai_tool": [[ { "node": "billing_specialist", "type": "ai_tool", "index": 0 } ]]
},
"get_customer_profile": {
"ai_tool": [[ { "node": "billing_specialist", "type": "ai_tool", "index": 0 } ]]
},
"open_dispute": {
"ai_tool": [[ { "node": "billing_specialist", "type": "ai_tool", "index": 0 } ]]
},
"Postgres Chat Memory": {
"ai_memory": [[ { "node": "triage_agent", "type": "ai_memory", "index": 0 } ]]
},
"OpenAI Chat Model (triage)": {
"ai_languageModel": [[ { "node": "triage_agent", "type": "ai_languageModel", "index": 0 } ]]
}
}
}
Read it slowly, because the entire pattern is in those seven entries:
billing_specialistconnects out viaai_tooltowardtriage_agent. It's an orchestrator tool.lookup_charge,get_customer_profile, andopen_disputeconnect out viaai_tooltowardbilling_specialist. They're the specialist's tools.- The same class of connection,
ai_tool, shows up at two levels. That's the nesting, written into the graph. Postgres Chat Memorygoes totriage_agentand nobody else — lesson 3's ownership rule, made into wire.- There are two different model nodes, one per agent.
What to expect. You send through the chat: "Hi, there's a $1,200 charge on my card I don't recognize."
In the execution panel you're going to see the triage_agent node run, and inside its detail a call to the billing_specialist tool. If you open that call, you see the task the orchestrator drafted — something like "Customer reports a $1,200 charge they don't recognize, on the card on file. Customer C-9931. Check whether it matches any purchase and, if not, open a dispute." Notice the orchestrator wrote that assignment: it didn't forward the customer's message as-is, it translated it, because that's what the $fromAI("task", ...)'s description asked for.
Then you see the billing_specialist node run on its own, with its own calls to lookup_charge, get_customer_profile, and open_dispute in sequence. And finally the triage_agent again, composing the response that reaches the chat.
What you're not going to see anywhere: a Switch node, an IF node, or any condition you wrote saying "if the message talks about a charge, go this way." The decision to call billing_specialist was made by the orchestrator's model comparing what the customer asked for against the specialist's Description. It's exactly the same mechanism an agent uses to choose between lookup_order and send_email — except the chosen tool turned out to be another agent.
The second mechanism: an agent inside a sub-workflow
There's a second native way to delegate, and it's worth knowing because in some cases it's the better one. It's the one left announced at the end of Module 4's lesson 6: a sub-workflow that has an AI Agent inside it, exposed to the orchestrator with Call n8n Workflow Tool.
The setup is this:
# Separate workflow: "Billing Specialist"
Execute Sub-workflow Trigger ← Input Source: Define Using Fields Below
(field: task, type String) declares the input contract
→ AI Agent (with its model, its prompt, and its billing tools)
→ Edit Fields (Set) ← last node: defines what gets returned
# In the orchestrator's workflow
Call n8n Workflow Tool
Description: "Resolves charge cases, unrecognized charges..."
Source: Database
Workflow: "Billing Specialist"
Workflow Inputs:
task = {{ $fromAI("task", "Complete, self-contained assignment...", "string") }}
From the orchestrator's point of view, this is indistinguishable from AI Agent Tool: a tool with a description and a parameter. The difference is on the other end of the wire.
Which one to use
AI Agent Tool | Sub-workflow with an agent inside | |
|---|---|---|
| Where the specialist lives | In the same workflow as the orchestrator | In a separate workflow |
| Canvas complexity | Everything together: you see the whole system at a glance, and it fills up fast | The orchestrator stays clean; the detail lives elsewhere |
| Reuse across systems | Only within that workflow | High: several orchestrators can call the same specialist |
| Testing it in isolation | Hard: you have to trigger the orchestrator | Easy: you run the sub-workflow with a test task |
| Input contract | The $fromAI()'s description | Declared with fields and types in the trigger |
| Steps before or after the agent | No room to put them | Yes: you can validate the input, normalize the output, log it |
| Execution trace | Nested inside the same execution | Shows up as a sub-workflow execution, linked |
| Overhead | Lower | One extra execution hop |
The practical rule. Start with AI Agent Tool: it's more direct, everything shows up on one canvas, and for a three-specialist system it's plenty. Move to a sub-workflow when one of these three things comes up:
- More than one system needs the same specialist. The billing specialist that handles the web chat is also useful to the finance team's internal Slack agent. One single place to fix it.
- You want to test it in isolation. Being able to send
Billing Specialisttwenty test assignments and review its twenty responses, without going through the orchestrator, is enormously valuable while you're tuning its prompt. - You need steps around the agent. Validating that the
taskcarries the minimum fields before spending a call to the model; normalizing the output into a fixed shape; writing a line into an audit log. All of that needs nodes before and after the agent, and that requires a workflow.
The two forms can be mixed within the same orchestrator without a problem: two specialists as AI Agent Tool and one as a sub-workflow, if that's what fits.
Why not a Switch: the two designs side by side
We already said it in lesson 1 as a warning. Now that you have the real wiring, it's worth putting them side by side, because the differences stop being abstract.
# OLD DESIGN — simulated with Switch
Chat Trigger
→ Basic LLM Chain ("classify this message: billing | orders | sales")
→ Switch (3 fixed branches on the returned text)
├── billing branch → AI Agent with billing tools → now what?
├── orders branch → AI Agent with orders tools → now what?
└── sales branch → AI Agent with sales tools → now what?
# NATIVE DESIGN — delegation via ai_tool
Chat Trigger
→ AI Agent (triage_agent) ← Postgres Chat Memory
│ ai_tool
├── AI Agent Tool: billing_specialist → its 3 tools
├── AI Agent Tool: order_specialist → its 3 tools
└── AI Agent Tool: sales_specialist → its 2 tools
Four concrete differences, not stylistic ones:
1. A message with two topics. In the old design, the Switch takes one branch. By construction. A customer asking about a charge and an order gets half a response. In the native design, the orchestrator calls two tools on the same turn and composes.
2. The result comes back. In the old design, once the flow entered the billing branch, there's no natural way for the result to get back to whoever classified it. That "now what?" in the diagram is real: you have to hand-build the way back, and decide what to do if a branch didn't run. In the native design, returning is what a tool does: the result goes back to the agent that called it and that agent keeps reasoning with it.
3. You can decide again. If the orders specialist reports "this case is actually a billing one, the customer is talking about a charge, not a shipment," the orchestrator can read that and delegate to whoever's right on the same turn. In the old design, the Switch already ran and it doesn't run again.
4. Adding a specialist. In the native design: you drag a node, connect it to the Tool port, and add a line to the orchestrator's system prompt. In the old design: you add a branch to the Switch, add the new label to the classifier's prompt, and review the reunification logic you hand-built. The first one is a two-minute operation; the second is a change that risks breaking what already worked.
And once more, the honest precision: none of this says Switch is a bad node. It's excellent at what it does. If the input channel gives you a structured field — {"department": "billing"} from a form — using a model to "decide" something that's already decided is throwing money away. The boundary is this: Switch for data, delegation for language.
Reading the nested trace
When you delegate, your ability to debug depends entirely on being able to read what happened at each level. With Return Intermediate Steps turned on on both — the orchestrator and each specialist — the output has this shape:
triage_agent
├── model call (decides to delegate)
├── tool: billing_specialist
│ └── (inside) billing_specialist
│ ├── model call
│ ├── tool: lookup_charge → no matches
│ ├── model call
│ ├── tool: get_customer_profile → one card, no aliases
│ ├── model call
│ ├── tool: open_dispute → D-8842
│ └── model call (produces its result)
├── model call (evaluates the result)
└── final response to the customer
When something goes wrong, that structure tells you which level to look at, which is half the work:
| Symptom | Where to look | What it usually is |
|---|---|---|
| The specialist never gets called | Orchestrator level | Its Description doesn't cover how real customers talk, or the orchestrator's prompt doesn't mention it |
| The wrong specialist gets called | Orchestrator level | Two Descriptions that overlap — lesson 2's tool collision, now between agents |
| The right one gets called but can't resolve | The task it received | The assignment arrived incomplete; a piece of data that was in the conversation is missing |
| It resolves badly even though the assignment was fine | Inside the specialist | Its system prompt, or one of its domain tools |
| It gets stuck partway with no error | The specialist's iterations | It hit its Max Iterations — lesson 6 |
| The final response ignores what the specialist returned | Orchestrator level | Its prompt doesn't tell it what to do with the result, or the specialist's output format is ambiguous |
That "what task did it receive" deserves emphasis: it's the single most useful inspection point in the whole system. Half of a multi-agent system's problems aren't in the agents themselves but in what they hand each other, and the task is where that's written in plain text, exactly as the orchestrator's model drafted it. Before touching a specialist's prompt because it "doesn't know how to resolve" something, read the assignment it received. Often the answer is right there.
Common mistakes
Writing a specialist's Description as if it described an action (conceptual). What happens: someone puts Description: "Checks charges and opens disputes" on billing_specialist. The orchestrator starts calling it only when the customer uses words very close to "charge" or "dispute," and lets obvious cases like "I got overcharged" or "this doesn't match what I bought" slip through. Why it happens: it's the reflex from Module 4, where tools really were actions and describing them by their action was correct. How to spot it: gather ten real customer messages from that domain and check how many share vocabulary with your description; if it's three out of ten, the description is too narrow. How to fix it: describe the scope and the symptoms, not the mechanics — "use it when the customer mentions a charged amount, a card, an account statement, or says something doesn't add up in what they were charged" — and always add the "don't use it for…" that sets it apart from its neighbors.
Passing the specialist the customer's raw message instead of an assignment (conceptual). What happens: the $fromAI("task", ...) gets defined as "the customer's message," and the specialist receives "hi, hey, something weird showed up on my card and also I wanted to ask about my order." The specialist doesn't have the customer's ID (it was in the system, not in the message), doesn't have the amount (the customer said it two turns ago), and also tries to resolve the order thing, which isn't its domain. Why it happens: forwarding the message as-is is the simplest thing to do and seems like the most faithful one. How to spot it: read the task that arrived in the trace; if it looks like how a customer writes rather than how an internal assignment is drafted, that's the problem. How to fix it: the $fromAI()'s description has to require a self-contained assignment, with the data already extracted and with a single topic — and the orchestrator's system prompt has to repeat that rule, because a single mention rarely holds.
Leaving the same expensive model on all five agents (practical). What happens: someone connects the most capable model available to all five nodes, and the per-conversation cost multiplies without quality improving proportionally — the orchestrator is making a three-option decision with a model meant for complex reasoning. Why it happens: the default model is whatever was already configured, and changing it agent by agent feels like premature micro-optimization. How to spot it: look at the trace and classify each model call by how hard the decision is; the orchestrator's are usually "choose between three well-described options." How to fix it: fast, cheap model in the orchestrator, capable model in the specialists that make costly decisions, mid-tier model in the rest — lesson 7 puts the numbers on this lever.
Connecting a specialist and not mentioning it in the orchestrator's system prompt (practical). What happens: the tool is connected, the wire shows on the canvas, and the orchestrator almost never calls it. Why it happens: technically the model sees the tool and its description even if the prompt doesn't name it, so "it should work" — and sometimes it works halfway, which is worse than not working, because it looks like a random problem. How to spot it: count across twenty executions how many times each specialist got called; if one has zero or almost zero calls in cases where it clearly applied, this is why. How to fix it: explicitly declare the roster in the orchestrator's system prompt, with one line per specialist and its scope — the model chooses better when the prompt confirms the policy, not only when the tool is available.
Connecting memory to specialists out of habit (practical). What happens: while building each AI Agent Tool, your hand goes on autopilot and connects a memory node "because that's how you build an agent." The three problems from lesson 3 show up, plus the per-conversation cost rises with no visible explanation. How to spot it: check the exported JSON and count how many ai_memory entries there are; there should be exactly one, pointing at the orchestrator. How to fix it: leave the specialist's Memory port empty and make sure the task carries what it needs — if a specialist genuinely needs its own thread, that's the exception, and it needs to be justified in writing.
Exercises
Exercise 1 — Read the graph. You get handed this connections fragment from a workflow and told it's a multi-agent system with native delegation. Say whether it is, and if not, what's wrong:
{
"connections": {
"billing_specialist": {
"main": [[ { "node": "triage_agent", "type": "main", "index": 0 } ]]
},
"lookup_charge": {
"ai_tool": [[ { "node": "billing_specialist", "type": "ai_tool", "index": 0 } ]]
}
}
}
See solution
It isn't. The error is in the first entry: billing_specialist connects to triage_agent via "main", not "ai_tool". That means the two agents are chained into the normal data flow — one runs after the other, always, in a fixed order — instead of one being a tool of the other.
The practical consequences: billing_specialist runs on every execution, even if the message has nothing to do with billing; triage_agent can't decide whether to call it or not; and there's no way for the orchestrator to reason about the result and decide to delegate to another specialist, because the sequence is already fixed by the wire.
It's, essentially, a fixed chain (lesson 3's Form 1) disguised as a multi-agent system. The fix is deleting that main connection and dragging billing_specialist's output to triage_agent's Tool port, which creates a connection of type ai_tool.
Why it works: the connection type is what defines the relationship between two agents. main is "after"; ai_tool is "available for you to decide to use." Reading the exported JSON is the fastest way to verify which of the two you actually built.
Exercise 2 — Write the Description and the $fromAI(). TuTienda wants to add a fourth specialist: warranty_specialist, which handles product warranties — claims for failures within the warranty period, processing repairs, and replacements for manufacturing defects. order_specialist (orders, shipments, remorse returns) and billing_specialist already exist. Write the AI Agent Tool node's Description and the prompt field's $fromAI() expression.
See solution
Description:
Resolves warranty claims: products that failed or stopped
working within their warranty period, processing repairs, and
replacements for manufacturing defects. Use it when the
customer says something broke, stopped working, arrived defective,
or asks about warranty coverage.
Do NOT use it for remorse returns — when the customer simply
didn't want the product — or for orders that arrived damaged in
shipping: those two cases belong to order_specialist.
Do NOT use it for charge complaints: that's billing_specialist.
{{ $fromAI(
"task",
"Self-contained assignment for the warranty specialist. It must
include: what the product is (name or SKU), when it was
purchased, what failure the customer describes, and the
customer's ID. This specialist does NOT have access to the
conversation history: any data mentioned in earlier turns
must be written here. If any of that data is missing, write
it in anyway noting which one is missing, so the specialist
can report it.",
"string"
) }}
What the Description does well: it describes symptoms in the customer's vocabulary ("broke," "stopped working," "arrived defective"), not internal mechanics; and it draws two explicit boundaries against the two neighbors it would most easily get confused with. The boundary with order_specialist is the trickiest one because "the product is damaged" fits both depending on when the damage occurred — which is exactly why it needs to be said, not left implicit.
What the $fromAI() does well: it lists the minimum fields instead of saying "all relevant information," which is too vague for the model to comply with consistently; and it tells it what to do when a piece of data is missing, instead of letting it make one up.
Why it works: the Description is for the caller — it has to recognize the case — and the $fromAI() is for the assignment — it has to carry the data. They're two texts with two different recipients, and confusing them is one of the most frequent mistakes when building your first system.
Exercise 3 — Choose the mechanism. For each of these three cases, decide whether AI Agent Tool or a sub-workflow with an agent inside is the better fit, and justify it:
(a) A sales_specialist that only serves TuTienda's web chat, with two tools, still under construction and with the prompt changing every day.
(b) An escalation_specialist that decides whether a case goes to a human, and that's going to be called by the web chat agent, the WhatsApp agent, and the finance team's internal Slack agent.
(c) A refund_specialist whose output always has to be logged to an audit table, with a timestamp, before being returned to the orchestrator.
See solution
(a) AI Agent Tool. A single consumer, few tools, and a prompt changing daily — having it on the same canvas makes each iteration a matter of opening the node and editing. Moving this to a sub-workflow would add a navigation hop to every change, in exchange for a reuse that doesn't exist yet.
(b) Sub-workflow. Three different consumers is exactly the reuse case: if the escalation policy changes, it gets fixed in one single place and all three systems stay up to date. If it were an AI Agent Tool, there'd be three copies of the same agent, and sooner or later two of them fall out of sync.
(c) Sub-workflow. The requirement to log to audit before returning needs a node after the agent and before the return, and an AI Agent Tool has nowhere to put that node. In the sub-workflow, it goes between the AI Agent and the final node that defines the response — being careful, as you learned in Module 4's lesson 6, that the audit-logging node doesn't end up at the end of the chain, because then what gets returned would be the logging step's output and not the agent's result.
Why it works: the table's three criteria — reuse, isolated testing, steps around it — cover the vast majority of real decisions. If none of them applies, AI Agent Tool is the default choice for simplicity.
Summary and next step
You now know how to delegate natively. The AI Agent Tool node is a complete agent — with its own Chat Model, its own tools, and its own system prompt — that connects to another agent's ai_tool port, the same way you connected a Gmail node in Module 4. Its Description tells the caller when to use it, describing a scope and not an action; the assignment travels in a $fromAI("task", …) whose description must require it to be self-contained; and its Options carry its system prompt, its Max Iterations, and the trace. The second mechanism — an agent inside a sub-workflow, exposed with Call n8n Workflow Tool — is the one worth reaching for when the specialist gets reused, when you want to test it in isolation, or when you need steps around it. And in the exported JSON, the pattern's signature is the same class of connection, ai_tool, showing up at two levels.
Before moving on you should be able to: connect an AI Agent Tool to an agent's Tool port and explain why that connection is ai_tool and not main; write a scope-level Description with its explicit boundary against neighboring specialists; write a $fromAI("task", …) that produces self-contained assignments; and read a nested trace to know at which level a problem is.
What's still loose is what they hand each other. So far, the assignment is free text and the specialist's response is too. That works in the demo and breaks the moment the system grows: the orchestrator has to guess, by reading prose, whether the case got resolved, whether a piece of data is missing, or whether a human is needed. Lesson 5 turns that exchange into an explicit contract — what goes in, what comes out, with what fields, and what happens when the specialist can't resolve something — which is the same discipline from Module 4's tool contract, applied between agents.
Resources
- AI Agent Tool node — n8n Docs — the reference for this lesson's central sub-node: its parameters, its ports, and its options. Verify the exact field names on your version there.
- AI Agent node — n8n Docs — the root node acting as orchestrator, with its Tool port having no connection limit.
- Call n8n Workflow Tool — n8n Docs — the second delegation mechanism:
Description,Source, and theWorkflow Inputsmapping. - Execute Sub-workflow Trigger — n8n Docs — how to declare the input contract with
Define Using Fields Belowand why the last node defines the response. - Use AI for parameters ($fromAI) — n8n Docs — syntax for the four arguments; the
descriptionis what decides the quality of the assignments the orchestrator drafts. - How tools work — n8n Docs — the tool selection mechanism, which is the same whether the tool is a Gmail node or a complete agent.