CrewAICrewAI 1.15 · Python 3.10 to 3.13
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
35 small wins to finish your pathNext lesson →

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.

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

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

python
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)
Project files used on this pageThis lesson builds on a project from earlier lessons. The code below imports these files. Click a file to see its code, or follow the link to the lesson that wrote it. To run the code yourself, keep them in the same folder.
View the code here
tools.py
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."
shop_llm.py
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?"
desk_manager.py
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.

Example
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)

What delegation produced

  • The writer's model called delegate_work_to_coworker with 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.

Example
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"])

Delegation vs a sequential crew

Sequential crewDelegation
Who orders the workYou, by the task listThe delegating agent's model
When a coworker runsAlwaysOnly when the model asks
What crosses overThe whole earlier outputOnly 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.
Watch out. The coworker sees only the 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.
Try it yourself
  • Set allow_delegation=False and 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.