Delegation between agents
Delegation gives an agent two tools for its coworkers: hand one a job, or ask one a question, and the coworker's answer comes back as the tool's result.
Last updated: 28 Sep, 2026 · CrewAI 1.15
Earlier you decided the order of work yourself. With delegation the writer decides, and asks the clerk only when it needs an order looked up.
Turning on delegation
Set allow_delegation=True on an agent. CrewAI then adds the two coworker tools to it, and the model calls one when it wants help.
writer.allow_delegation = True
# CrewAI now offers the writer:
# delegate_work_to_coworker(task, context, coworker)
# ask_question_to_coworker(question, context, coworker)A model that delegates
ManagerLLM is a stand-in for an agent that delegates: offered the delegate tool, it picks the coworker whose role mentions orders and hands over the customer's request. The snippet below is abbreviated; the complete class ships in the bundled desk_manager.py.
class ManagerLLM(ShopLLM):
def call(self, messages, tools=None, **kwargs):
found = {t["function"]["name"]: t["function"] for t in tools or []}
delegate = found.get("delegate_work_to_coworker")
if delegate and messages[-1]["role"] == "user":
... # build a delegate_work_to_coworker call for the clerk
return super().call(messages, tools=tools, **kwargs)One task for the writer
The clerk and writer are the desk's pair. The writer gets the model that delegates and the one task.
writer.allow_delegation = True
writer.llm = ManagerLLM(model="shop")
task = Task(description="Answer the customer: {question}",
expected_output="One sentence.", agent=writer)
crew = Crew(agents=[clerk, writer], tasks=[task], verbose=False)View the code here
from crewai.tools import tool
ORDERS = {"A17": "shipped on 3 March", "C40": "waiting for stock"}
@tool
def lookup_order(order_id: str) -> str:
"""Look up an order's shipping status by its id, such as A17."""
status = ORDERS.get(order_id)
return f"{order_id} {status}." if status else f"{order_id} is not an order we have."
import json
import os
import re
from crewai import BaseLLM
os.environ["OTEL_SDK_DISABLED"] = "true"
os.environ["CREWAI_DISABLE_TELEMETRY"] = "true"
os.environ["CREWAI_TRACING_ENABLED"] = "false"
os.environ["CREWAI_DISABLE_VERSION_CHECK"] = "true"
class ShopLLM(BaseLLM):
script: list = []
def supports_function_calling(self):
return True
def call(self, messages, tools=None, **kwargs):
if isinstance(messages, str):
messages = [{"role": "user", "content": messages}]
if self.script:
return self.script.pop(0)
names = [t["function"]["name"] for t in tools or []]
return self.decide(messages, names)
def decide(self, messages, tools):
last = messages[-1]
if last["role"] == "tool":
return last["content"]
text = last["content"]
orders = re.findall(r"\b[A-Z]\d+\b", text)
wanted = "refund_order" if "refund" in text.lower() else "lookup_order"
matches = [name for name in tools if name.endswith(wanted)]
if orders and matches:
args = json.dumps({"order_id": orders[0]})
return [{"id": f"call_{orders[0]}", "type": "function",
"function": {"name": matches[0], "arguments": args}}]
if "working with:" in text:
context = text.split("working with:")[1].strip().split("\n\n")[0]
return f"Dear customer, {context}"
if orders:
return f"I have no way to look up {orders[0]} yet."
return "Hello. Which order is this about?"
import json
import re
from shop_llm import ShopLLM
class ManagerLLM(ShopLLM):
def call(self, messages, tools=None, **kwargs):
found = {t["function"]["name"]: t["function"] for t in tools or []}
delegate = found.get("delegate_work_to_coworker")
if delegate and messages[-1]["role"] == "user":
names = delegate["description"].split("coworkers: ")[1].split("\n")[0]
coworker = next(n for n in names.split(", ") if "order" in n.lower())
job = re.search(r"Current Task: (.*)", messages[-1]["content"]).group(1)
args = {"task": job, "context": "A customer wrote in.", "coworker": coworker}
return [{"id": "delegate_1", "type": "function", "function": {
"name": "delegate_work_to_coworker", "arguments": json.dumps(args)}}]
return super().call(messages, tools=tools, **kwargs)
The writer asking the clerk
One task, given to the writer, who delegates the lookup to the clerk.
import os
os.environ["OTEL_SDK_DISABLED"] = "true"
os.environ["CREWAI_DISABLE_TELEMETRY"] = "true"
from crewai import Agent, Crew, Task
from shop_llm import ShopLLM
from tools import lookup_order
clerk = Agent(role="Order clerk", goal="Find the status of customers' orders",
backstory="You can look up any order in the shop's system.",
llm=ShopLLM(model="shop"), tools=[lookup_order])
writer = Agent(role="Reply writer", goal="Write replies to customers",
backstory="You write short, friendly emails.",
llm=ShopLLM(model="shop"))
from desk_manager import ManagerLLM
writer.allow_delegation = True
writer.llm = ManagerLLM(model="shop")
task = Task(description="Answer the customer: {question}",
expected_output="One sentence.", agent=writer)
crew = Crew(agents=[clerk, writer], tasks=[task], verbose=False)
print(crew.kickoff(inputs={"question": "Where is my order A17?"}).raw)A17 shipped on 3 March.
What delegation produced
- The writer's model called
delegate_work_to_coworkerwith the clerk's role. - CrewAI ran a task for the clerk, whose own model looked A17 up with its tool.
- The clerk's real answer came back to the writer as the tool's result, and the writer returned it.
The writer's own conversation
The writer's messages hold one tool call and one tool result; the clerk's work happened in a separate conversation, and only the task and context arguments crossed over.
import os
os.environ["OTEL_SDK_DISABLED"] = "true"
os.environ["CREWAI_DISABLE_TELEMETRY"] = "true"
from crewai import Agent, Crew, Task
from shop_llm import ShopLLM
from tools import lookup_order
clerk = Agent(role="Order clerk", goal="Find the status of customers' orders",
backstory="You can look up any order in the shop's system.",
llm=ShopLLM(model="shop"), tools=[lookup_order])
writer = Agent(role="Reply writer", goal="Write replies to customers",
backstory="You write short, friendly emails.",
llm=ShopLLM(model="shop"))
from desk_manager import ManagerLLM
writer.allow_delegation = True
writer.llm = ManagerLLM(model="shop")
task = Task(description="Answer the customer: {question}",
expected_output="One sentence.", agent=writer)
crew = Crew(agents=[clerk, writer], tasks=[task], verbose=False)
result = crew.kickoff(inputs={"question": "Where is my order A17?"})
for message in result.tasks_output[0].messages[2:]:
print(message["role"], "|", message.get("tool_calls") or message["content"])assistant | [{'id': 'delegate_1', 'type': 'function', 'function': {'name': 'delegate_work_to_coworker', 'arguments': '{"task": "Answer the customer: Where is my order A17?", "context": "A customer wrote in.", "coworker": "Order clerk"}'}}]
tool | A17 shipped on 3 March.
assistant | A17 shipped on 3 March.Delegation vs a sequential crew
| Sequential crew | Delegation | |
|---|---|---|
| Who orders the work | You, by the task list | The delegating agent's model |
| When a coworker runs | Always | Only when the model asks |
| What crosses over | The whole earlier output | Only task and context |
When to let an agent delegate
- The steps depend on the answer: some tickets need a lookup, some do not.
- One agent should stay in charge and pull in a specialist on demand.
task and context the caller writes, not the caller's conversation, so a vague task gives a vague answer. The coworker is matched by its role text, so renaming a role can break the match.Related
- Previous: Conditional tasks that run only sometimes
- Next: Hierarchical process: a manager for the crew
- Reference: Collaboration
- Set
allow_delegation=Falseand run the first example again. - Ask "Hello" and check whether the writer delegates.
- Rename the clerk's role to "Stock checker" and read the error from
next.
Slow is fine. Stopping is the only problem.