JT Book a call

Blog

CrewAI Hierarchical Agents: Manager-Worker Pattern

Published

5 min read

CrewAI Hierarchical Agents: Manager-Worker Pattern
In this post

CrewAI is a Python framework where you describe a team of LLM agents (a role, a goal, a backstory), give them tasks, and let a crew run the tasks. It has two ways to run them. In a sequential crew, tasks run in the order you list them. In a hierarchical crew, a manager agent decides who does what.

This post covers what each does, what a hierarchical crew needs before it will start, and how to test either without an API key. Everything below ran on crewai 1.15.33 with Python 3.12. CrewAI requires Python 3.10 or newer, below 3.14. The agent output in my runs is canned text from a stub model, so this post says nothing about answer quality.

Agents, tasks, crews #

An Agent has a role, goal and backstory. A Task has a description, an expected_output, and usually an agent. A Crew takes agents, tasks and a process.

from crewai import Agent, Task, Crew, Process

researcher = Agent(role="Research Analyst", goal="Find facts", backstory="Careful.")
writer = Agent(role="Writer", goal="Write a short draft", backstory="Clear.")

research = Task(description="Research X", agent=researcher, expected_output="bullets")
draft = Task(description="Write about X", agent=writer, context=[research],
             expected_output="a draft")

crew = Crew(agents=[researcher, writer], tasks=[research, draft],
            process=Process.sequential)
result = crew.kickoff()

context=[research] hands the first task’s output to the second. In the CrewAI task docs, output also flows to the next task automatically in a sequential crew, and context is for when a task needs the output of a task that is not immediately before it.

Testing a crew without an API key #

A crew calls its LLM through a class you can replace. I subclassed BaseLLM to return canned replies and recorded what each agent was sent:

from crewai import BaseLLM

class Canned(BaseLLM):
    def __init__(self, replies):
        super().__init__(model="canned")
        object.__setattr__(self, "_r", list(replies))
        object.__setattr__(self, "seen", [])

    def call(self, messages, tools=None, callbacks=None, available_functions=None,
             from_task=None, from_agent=None, response_model=None):
        self.seen.append("\n".join(m["content"] for m in messages
                                   if isinstance(m.get("content"), str)))
        return self._r.pop(0)

    def supports_function_calling(self):
        return False

llm = Canned([
    "Thought: I now can give a great answer\nFinal Answer: FINDINGS-ALPHA",
    "Thought: I now can give a great answer\nFinal Answer: DRAFT",
])
# build the crew above with llm=llm on both agents, then:
crew.kickoff()
print("FINDINGS-ALPHA" in llm.seen[1])   # True
print(len(llm.seen))                       # 2

Output of my run: the crew result was DRAFT, the writer’s prompt contained FINDINGS-ALPHA, and there were exactly two model calls. That is a real test of your wiring (which task feeds which, how many calls a run makes) that costs nothing and runs in seconds. It does not test whether a real model follows your prompts.

The Thought: / Final Answer: reply format is what worked with this stub in 1.15.33. It is CrewAI’s internal reply format, not a documented API, so expect to adjust the stub when you upgrade.

What a hierarchical crew needs #

The CrewAI processes docs say a hierarchical crew must have a manager_llm or a manager_agent. I checked what happens without one:

Crew(agents=[researcher, writer], tasks=[research, draft],
     process=Process.hierarchical)
ValidationError: 1 validation error for Crew
  Attribute `manager_llm` or `manager_agent` is required when using hierarchical process.

With a manager it builds:

crew = Crew(
    agents=[researcher, writer],
    tasks=[research, draft],
    process=Process.hierarchical,
    manager_llm="gpt-4o",   # or manager_agent=my_manager
)

The docs describe the difference in who decides. In a sequential crew you assign each task to an agent. In a hierarchical crew, tasks “are not pre-assigned; the manager allocates tasks to agents based on their capabilities, reviews outputs, and assesses task completion.”

I ran a one-worker hierarchical crew with a scripted manager that delegates once, then finishes. The manager made 2 model calls, the worker 1, the worker received the delegated task text, and the crew’s result was the manager’s final answer, not the worker’s. After the run, the task’s agent attribute was Crew Manager, so any code that reads task.agent after a hierarchical run sees the manager rather than your worker.

Two consequences follow from that mechanism:

  • You pay for the manager’s calls on top of the workers’. Every delegation is a manager call plus a worker call, and the manager reviews results.
  • The routing is a model decision. If order matters and you know it up front, a sequential crew is simpler and cheaper, and you can read the order in your code.

Anthropic measured the cost side in its own multi-agent research system: multi-agent systems used about 15x more tokens than chats, and single agents about 4x. That is Anthropic’s system and data, not CrewAI’s, but it is the right order of magnitude to expect before you measure your own.

Running tasks side by side #

There is no parallel process. The Process enum in 1.15.33 has two members, sequential and hierarchical. For concurrency, the task docs describe async_execution=True: “the crew will not wait for it to be completed to continue with the next task”, and a later task lists those tasks in context to wait for them.

ideas = Task(description="List 5 ideas", agent=researcher,
             expected_output="5 bullets", async_execution=True)
history = Task(description="List 5 key events", agent=researcher,
               expected_output="5 bullets", async_execution=True)
article = Task(description="Write the article", agent=writer,
               expected_output="4 paragraphs", context=[ideas, history])

I confirmed that async_execution exists on Task in 1.15.33 but did not time a concurrent run.

Controls worth setting #

Fields I confirmed exist on Agent: max_iter (default 20, per the agents docs), max_rpm for requests per minute, and allow_delegation (default False). On Task: guardrail, guardrail_max_retries. On Crew: memory, planning. I did not exercise these, so check each against the docs for your version before relying on one.

When a crew is the wrong choice #

Research on multi-agent failures is blunt. Cemri et al. analyzed more than 1,600 annotated traces across 7 multi-agent frameworks and note that the performance gains of multi-agent systems on popular benchmarks “are often minimal” (arXiv 2503.13657). If one agent with the right tools can do the job, start there. A crew pays off when the steps really need different instructions or tools, and you can say how you will tell whether it did better than one agent.

Sources #

Related: AutoGen, CrewAI and LangGraph compared.

Read next

Tell us what you need built

JetThoughts has been building software since 2008. Book a call, or write to sales@jetthoughts.com. You'll leave the call with a written one-page scope within 2 working days.

#01 Paul Keen
Paul KeenFounder & Open Source Contributor

A 30-minute call; pick a time.