CrewAI: Role-Based Crews and Flows
CrewAI is a role-based multi-agent framework built on four primitives (Agent, Task, Crew, Process) and two shapes: Crews for autonomous collaboration, Flows for event-driven, deterministic control. The docs are blunt about which to reach for first: "for any production-ready application, start with a Flow."
Learning Objectives
- Name CrewAI's four primitives (Agent, Task, Crew, Process) and what each owns.
- Distinguish Sequential, Hierarchical, and the planned Consensus process; pick one per workload.
- Distinguish Crews (autonomous role-based) from Flows (event-driven deterministic), and explain the docs' production recommendation.
- Wire tools with the `@tool` decorator and `BaseTool` subclass; reason about structured outputs vs free text.
- Name the four CrewAI memory types and when each pays off.
- Implement a stdlib three-agent crew (researcher, writer, editor) that produces a brief.
- Spot the four CrewAI failure modes: prompt-bloat, manager-LLM tax, brittle handoffs, Crew-as-prod.
The Problem
Teams adopting multi-agent frameworks tend to hit the same wall. "Autonomous collaboration" looks great in a demo. Then a customer files a bug and you need deterministic replay. Finance asks what an LLM-routed crew costs per run. On-call wants to know which agent stalled at 3 AM.
A free-form, LLM-routed crew answers none of those cleanly. A pure DAG answers all of them, but it loses the exploratory shape a brainstorming agent needs.
CrewAI splits the difference openly. Crews handle collaborative, role-based, exploratory work. Flows handle event-driven, code-owned, auditable production. One framework, two shapes, and you pick per surface.
The Concept
CrewAI is a Python framework for building multi-agent systems out of role-based agents. It has a small surface of four primitives and two top-level shapes, Crews and Flows. This section covers both, then tools, memory, and where the whole thing breaks.
Four primitives
Memorize these four and the rest is config.
- Agent.
role + goal + backstory + tools + (optional) llm. The backstory is load-bearing: it shapes tone, judgment, and when the agent stops. Tools are functions the agent can call (more below). - Task.
description + expected_output + agent + (optional) context + (optional) output_pydantic. A reusable unit of work.expected_outputis the contract,contextlists the upstream tasks whose outputs get passed in, andoutput_pydanticforces a structured shape. - Crew. The container. It owns the list of
agents, the list oftasks, theprocess, and optionalmemory,verbose, andmanager_llmsettings. - Process. The execution strategy: Sequential, Hierarchical, or Consensus (planned). It sets the shape of the run.
Agents never see each other directly. Tasks reference agents, the Crew sequences tasks, and the Process decides who picks the next task. That is the whole mental model.
Validated against CrewAI 0.86 (2026-05). Newer versions may rename or merge process types; check the CrewAI Processes docs before relying on a specific shape.
Sequential vs Hierarchical vs Consensus
- Sequential. Tasks run in declaration order, and the output of task N is available as
contextto task N+1. It is the cheapest and most predictable option. Use it when the order is fixed. - Hierarchical. A manager Agent (a separate LLM call) routes between specialists. CrewAI spawns the manager from your
manager_llmconfig or a default. Each round the manager picks the next task, and it can refuse or re-route. Use it when you have four or more specialists and the order genuinely depends on prior output. - Consensus. Planned, and not implemented in the public API today. The docs reserve the name for a future voting-based process, so do not rely on it.
Hierarchical adds manager LLM calls on top of the specialist calls, and each manager call carries the task list plus prior outputs. Token cost grows faster than the call count, so pay for it only when you need the routing.
Crews vs Flows
A Crew is LLM-driven autonomy: the framework picks the shape of the run at runtime. A Flow is an event-driven graph you own, where @start marks the entry and @listen(topic) marks a step that fires when another step emits that topic. Each Flow step is plain Python, and it can call a Crew internally. The docs lead with this split in 2026.
- Crew fits research, brainstorming, first drafts, and anywhere the path is part of the answer. It is cheap to prototype and hard to replay or test.
- Flow fits production. It is observable, testable, and deterministic.
The docs' 2026 production recommendation is to start with a Flow, then fold Crews in as Crew.kickoff() calls from inside Flow steps when autonomy earns its cost. The Flow gives you the audit trail and the Crew gives you the exploration. Compose them instead of picking one.
Tool integration
There are three ways to give an Agent a tool. Pick the simplest one that fits.
-
@tooldecorator. A pure function becomes a tool. The signature is the schema and the docstring is the description the LLM sees. Best for one-off helpers.pythonfrom crewai.tools import tool @tool("Search the web") def search(query: str) -> str: """Return top results for the query.""" return run_search(query) -
BaseToolsubclass. A class-based tool with an explicit args schema, async support, and retries. Use it when the tool holds state (a client, a cache) or needs structured args.pythonfrom crewai.tools import BaseTool from pydantic import BaseModel class SearchArgs(BaseModel): query: str limit: int = 10 class SearchTool(BaseTool): name = "web_search" description = "Search the web and return top results." args_schema = SearchArgs def _run(self, query: str, limit: int = 10) -> str: return self.client.search(query, limit=limit) -
Built-in toolkits. CrewAI ships first-party adapters:
SerperDevTool,FileReadTool,DirectoryReadTool,CodeInterpreterTool,RagTool,WebsiteSearchTool. One import wires each one up.
Structured outputs use Pydantic. Pass output_pydantic=MyModel on the Task, and CrewAI validates the LLM response against the model, then either coerces it or retries. Pair that with a tight expected_output string. Free text is fine for drafts, but structured outputs are what downstream Flows can actually consume.
Memory hooks
CrewAI ships four memory types, and a Crew can enable all four at once.
Validated against CrewAI 0.86 (2026-05). Recent releases route everything through a unified
Memorysystem that wraps these four stores. The conceptual model below still holds, but the public class surface may collapse to a singleMemoryentry-point in newer versions; check CrewAI memory docs for the current API.
- Short-term. A conversation buffer within a single run, wiped when the run ends.
- Long-term. Persisted across runs in a vector DB (Chroma by default, swappable) and retrieved by similarity to the current task.
- Entity. Per-entity facts, like "Customer X is on the enterprise plan." Keyed by entity rather than by similarity, and it survives across runs.
- Contextual. Assembly-time retrieval. It pulls relevant memory at the moment the Agent needs it instead of preloading.
Enable memory on the Crew with memory=True or per-type config. It is backed by an embeddings provider you configure (OpenAI by default, swappable to local). Memory is one of the places CrewAI earns its keep against thinner frameworks, since pure LangGraph makes you wire each of these yourself.
When CrewAI fits
- Three to six agents with named roles and a collaborative workflow: drafting, reviewing, planning, brainstorming.
- Routing where the LLM's judgment about the next step is part of the value (Hierarchical).
- Teams that would rather read
role + goal + backstorythan a graph definition.
When CrewAI does not fit
- Deterministic DAGs with strict ordering. Use LangGraph (Lesson 13). The graph is the right abstraction there, and CrewAI's role framing is friction.
- Sub-second latency budgets. Hierarchical adds round trips, and even Sequential serializes prompts that carry backstories and prior outputs.
- Single-agent loops. Skip the framework. An agent loop (Lesson 1) plus a tool registry is shorter.
The short version of the framework landscape is that CrewAI sits in the "collaborative role-based" corner. Lessons 13 to 18 cover the alternatives: LangGraph, AutoGen, the OpenAI Agents SDK, the Claude Agent SDK, and Agno/Mastra.
Dependency shape
CrewAI is independent of LangChain, supports Python 3.10 to 3.13, and uses uv. For star count, see crewAIInc/crewAI (snapshot as of 2026-05). AWS Bedrock integration is documented. Vendor benchmarks report a substantial speedup vs LangGraph on QA workloads, but the methodology (dataset, hardware, evaluation metric) is not published, so treat framework-vendor numbers as directional only.
Where this pattern goes wrong
- Prompt-bloat from backstories. A 2000-word backstory per agent across a five-agent crew burns the context budget before the first tool call. Keep backstories under 200 words, and reuse phrases across agents instead of repeating house style five times.
- Manager-LLM token tax. Hierarchical adds a manager LLM call before every specialist call. On a five-task crew that is six LLM calls instead of five, and the manager call carries the full task list plus prior outputs. Switch to Sequential unless routing depends on output.
- Brittle handoffs. Task N's
expected_outputis "an outline". Task N+1 reads it ascontextand tries to parse three sections, but the LLM produced four, so the downstream Agent ad-libs. Putoutput_pydanticon Task N so Task N+1 reads a typed object instead of free text. - Crew-as-prod. A free-form Crew shipped to production without a Flow wrapper. Output variability is high, replay is impossible, and on-call cannot diff a bad run against a good one. Wrap it in a Flow.
Build It
code/main.py implements stdlib versions (plus numpy for the memory mock) of both shapes plus a three-agent crew.
What's in it:
AgentandTaskdataclasses matching CrewAI's surface.SequentialCrew.kickoff(inputs)runs tasks in declaration order, threading outputs through ascontext.HierarchicalCrew.kickoff(topic)adds a manager Agent that picks the next specialist each round and stops at "done".Flowwith@startand@listen(topic)decorators, a tiny event loop, and a trace.tool(name), a decorator mirroring CrewAI's@toolshape.Memorywithshort_term,long_term, andentitystores. Mocked similarity is cosine similarity over hashed token vectors, the one place the file imports numpy.- Mock LLM responses are hardcoded strings keyed off role plus input prefix. No network, fully deterministic.
The demo is a researcher, writer, and editor crew producing a brief on "agent engineering 2026". The researcher pulls (mocked) sources, the writer drafts, and the editor tightens. The same crew then runs through a Flow to show the deterministic shape.
Run it:
python3 code/main.pyThe trace covers five things: a sequential crew threading outputs through context; a hierarchical crew with manager picks (researcher, writer, editor, then "done"); a flow running the same three steps with explicit topics (researched, drafted, edited); tool calls routed through @tool; and long-term memory surviving across two kickoffs.
The Crew trace is fluid, and the manager could in principle re-order. The Flow trace is fixed. That contrast is the lesson.
Use It
- CrewAI Flow for production, even when the Flow is a single step that calls
Crew.kickoff(). The Flow gives you the audit boundary. - CrewAI Crew (Sequential) for collaborative work with a clear order, especially first drafts and review loops.
- CrewAI Crew (Hierarchical) when routing depends on output and you have four or more specialists.
- LangGraph (Lesson 13) for explicit state machines, durable resume, and strict ordering.
- AutoGen v0.4 (Lesson 14) for actor-model concurrency and fault isolation.
- OpenAI Agents SDK (Lesson 16) for OpenAI-first products with handoffs and guardrails.
- Claude Agent SDK (Lesson 17) for Claude-first products with subagents and a session store.
Ship It
outputs/skill-crew-or-flow.md picks Crew vs Flow for a task and scaffolds the minimal implementation. It hard-rejects Crew-without-backstory, Flow-without-explicit-topics, and Hierarchical with only two specialists.
Default to a Flow and let a Crew earn its place inside one. A Flow with a single step that calls Crew.kickoff() already gives you the audit boundary, and you can loosen it later if autonomy turns out to pay for itself. A free-form Crew in production is the failure mode this lesson warns about, and a Flow wrapper is the fix.
Treat the role-based framing as an interface to your prompts, not decoration. Keep each backstory short, test a few variants, and freeze the one you pick. And reach for Hierarchical only when you can say why a fixed order won't work. If you can't, the manager call is a tax you are paying for the look of sophistication.
Pitfalls
- Backstory as flavor. It shapes outputs. Test three variants per agent, since the variance is real, then pick one and freeze it.
- Skipping
expected_output. Without a contract per task, downstream tasks pick up whatever the LLM produced. The Crew runs and the audit fails. - Memory always-on. Long-term memory writes on every run, so the vector DB grows and retrieval gets noisy. Scope writes to tasks where the fact is persistent.
- Manager prompt drift. Hierarchical's manager prompt is implicit. If routing gets weird, dump it in verbose mode and read it.
- Tool side effects in Crews. A Crew can call a tool more times than you expect. POST, DELETE, and payment calls belong in a Flow step, never in a Crew tool.
Exercises
- Convert the Sequential crew to a Flow. Count the touchpoints where variability drops. Note where readability dropped.
- Add entity memory to the crew: facts about a customer persist across kickoffs. Verify retrieval pulls the right entity.
- Implement a Hierarchical process where the manager refuses to route to the editor until the writer's output has at least three paragraphs. Trace the retry.
- Wire a
BaseToolsubclass for a (mocked) web search. Compare the trace shape vs the@tooldecorator version. - Add
output_pydantic=Briefto the editor task, whereBriefhastitle,summary,sections. Make the writer task output malformed JSON once; verify CrewAI's retry behavior in the trace. - Read CrewAI's docs intro. Port the toy to the real
crewaiAPI. Which guarantees did the stdlib version skip? - Wire AgentOps or Langfuse (Lesson 24) to a real run. Which traces did you miss in the stdlib version?
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| Agent | "Persona" | Role + goal + backstory + tools |
| Task | "Unit of work" | Description + expected output + assignee + optional structured output |
| Crew | "Agent team" | Container for Agents + Tasks + Process |
| Process | "Execution strategy" | Sequential / Hierarchical / Consensus (planned) |
| Flow | "Deterministic workflow" | Event-driven, code-owned, testable |
| Backstory | "Persona prompt" | Tone and judgment shaper for the Agent |
@tool | "Function tool" | Decorator that turns a function into a tool the Agent can call |
BaseTool | "Class tool" | Class-based tool with args schema, retries, async support |
| Entity memory | "Per-entity facts" | Memory scoped to a customer / account / issue |
| Long-term memory | "Cross-run memory" | Vector-backed memory that survives between kickoffs |
| Contextual memory | "Just-in-time retrieval" | Memory pulled at the moment the Agent needs it |
| Manager LLM | "Router agent" | Extra LLM in Hierarchical process that picks the next task |
expected_output | "Task contract" | String that tells the Agent (and audit) what shape to return |
Further Reading
- CrewAI docs introduction: concepts and the recommended production path
- CrewAI Flows guide: event-driven shape,
@start,@listen - CrewAI tools reference:
@tool,BaseTool, built-in toolkits - CrewAI memory: short-term, long-term, entity, contextual
- Anthropic, Building Effective Agents: when multi-agent helps and when it does not
- LangGraph overview: the state-machine alternative
Code
- main.py (python, 348 lines)