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 →

Hierarchical process: a manager for the crew

In a hierarchical crew, tasks name no agent: CrewAI builds a manager from manager_llm, and the manager delegates each task to the crew's agents.

Last updated: 28 Sep, 2026 · CrewAI 1.15

Delegation made one worker a delegator. The hierarchical process adds a separate manager on top of the crew instead, so no worker has to be one.

The hierarchical process

Set process=Process.hierarchical and give the crew a manager_llm. Leave the tasks' agent unset; the manager assigns them.

python
crew = Crew(agents=[clerk, writer], tasks=[task],
            process=Process.hierarchical,
            manager_llm=ManagerLLM(model="shop"))   # or manager_agent=<Agent>

A task with no agent

The task names no worker. The manager decides who runs it.

python
task = Task(description="Answer the customer: {question}",
            expected_output="One sentence.")   # no agent= here
crew = Crew(agents=[clerk, writer], tasks=[task],
            process=Process.hierarchical, manager_llm=ManagerLLM(model="shop"))
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)

A manager running the crew

The manager sends the one task to the clerk, who looks the order up.

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 crewai import Process
from desk_manager import ManagerLLM

task = Task(description="Answer the customer: {question}", expected_output="One sentence.")
crew = Crew(agents=[clerk, writer], tasks=[task], process=Process.hierarchical,
            manager_llm=ManagerLLM(model="shop"), verbose=False)

print(crew.kickoff(inputs={"question": "Where is my order A17?"}).raw)

What the manager did

  • CrewAI built a manager called Crew Manager and gave it the two coworker tools.
  • The manager delegated the task to the clerk, whose model looked A17 up.
  • The workers needed no allow_delegation; only the manager delegates.

A hierarchical crew with no manager

Leave out manager_llm and the crew is refused when it is built, not at kickoff.

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 crewai import Process

task = Task(description="Answer the customer: {question}", expected_output="One sentence.")
Crew(agents=[clerk, writer], tasks=[task], process=Process.hierarchical)

A hierarchical process needs manager_llm or manager_agent. Pass a manager_agent when you want to write the manager's role and backstory yourself.

Hierarchical vs delegation

DelegationHierarchical
Who delegatesA worker you setA manager CrewAI builds
Task's agentSet on the taskLeft unset
Extra modelNoneThe manager, on every task

When to add a manager

  • Several tasks whose assignment should be decided at run time, not written down.
  • A crew where you want one coordinator over interchangeable workers.
Watch out. The manager decides who does what, so every task costs at least two models' work. When the order of work is known, as it is for this desk, a sequential crew does the same job without the manager's calls.
Try it yourself
  • Add a second task and see whether the manager delegates it too.
  • Print the manager's role from result.tasks_output[0].agent.
  • Pass a manager_agent with llm=ManagerLLM(model="shop") instead of manager_llm.

This is what real progress feels like.