Skip to content

CrewAI and Role-Based Agent Teams

9 min read · updated August 4, 2026

CrewAI expresses a workflow as a set of role-playing agents with tasks and a process that orders them. The abstraction is unusually easy to read, which is its genuine strength. It is also the abstraction most likely to turn a problem that needed three model calls into one that makes thirty, so the section on arithmetic below is the one to read before the section on syntax.

Agents, tasks, crews

ConceptDescription
AgentA role, a goal, a backstory, a set of tools and a model. The three text fields become the system prompt. An agent is a configured prompt with tools attached, and thinking of it that way keeps expectations calibrated.
TaskA unit of work with a description, an expected output and an assigned agent. Tasks can take the output of earlier tasks as context, which is the mechanism by which work accumulates.
CrewA set of agents, a list of tasks and a process. Running the crew executes the tasks and returns the final output.
ProcessSequential runs tasks in order, each seeing the outputs it was given as context. Hierarchical adds a manager agent that decides assignment and reviews results — more capable, considerably more expensive, and harder to predict.

The key implementation fact, and the one that explains the cost section: each agent turn is a fresh model call carrying the accumulated context it has been given. There is no shared memory in the sense of a shared working set — there is text, passed forward, growing.

The smallest crew that does something

researcher = Agent(
    role="Market researcher",
    goal="Find and verify facts about a named company",
    backstory="You check claims against sources and refuse to guess.",
    tools=[search_tool],
    allow_delegation=False,        # see the delegation section
)

writer = Agent(
    role="Analyst",
    goal="Turn verified facts into a one-page brief",
    backstory="You write plainly and cite every claim.",
    allow_delegation=False,
)

gather = Task(
    description="Research {company}: funding, headcount, main product.",
    expected_output="A bulleted list of facts, each with a source URL.",
    agent=researcher,
)

brief = Task(
    description="Write a one-page brief from the research.",
    expected_output="Markdown, under 400 words, every claim cited.",
    agent=writer,
    context=[gather],              # this task receives the other's output
)

crew = Crew(agents=[researcher, writer], tasks=[gather, brief])
crew.kickoff(inputs={"company": "..."})

The expected_output field does more work than its name suggests: it is the instruction that shapes the output format, and vague values here are the most common cause of a crew that produces plausible prose nobody can use. Write it as a specification, not as a hope.

Where roles genuinely help

There are three cases where splitting work across agents earns its cost, and they have nothing to do with the role-play framing.

  • Different tools per step. A researcher with web search and a writer with none is a real separation of privilege. It reduces the tool count each call sees, which measurably improves selection — the problem described in too many tools.
  • Different models per step. A cheap fast model for extraction and a stronger one for synthesis is a genuine cost win, and the agent boundary is a natural place to make that choice explicit.
  • Context that should not travel. A step that reads a hundred pages and emits a summary keeps those hundred pages out of every later call. This is the strongest argument for the pattern: it is a context budget mechanism, and the reasoning is the same as in context compression.

What does not help, on any evidence available in public, is the role-play itself. A backstory saying “you are a world-class…” is a prompt, and it behaves like a prompt: sometimes useful, easily overstated, and not a substitute for a specification of the output. Treat the role fields as prompt engineering under a friendlier name and you will write better ones.

The arithmetic of a crew

Nobody can tell you what a crew costs without knowing your prompts, so here is the derivation instead. Every quantity is one you can measure or choose.

Notation
  a   agents in the crew
  t   tasks (a sequential crew runs t of them)
  r   model turns per task, including tool round-trips   (typically 2–5)
  S   system prompt tokens per agent  (role + goal + backstory + tools)
  C0  starting context tokens         (the input, e.g. a document)
  G   output tokens produced per task

Sequential crew, each task receiving all prior outputs:

  context entering task i  =  S + C0 + G × (i − 1)
  calls in task i          =  r
  input tokens in task i   ≈  r × (S + C0 + G × (i − 1))

  total input ≈ r × ( t·S + t·C0 + G · t(t−1)/2 )
                                   ^^^^^^^^^^^^^
                          quadratic in the number of tasks

Worked example: t = 5, r = 3, S = 400, C0 = 2,000, G = 800

  t·S            = 2,000
  t·C0           = 10,000
  G · t(t−1)/2   = 800 × 10 = 8,000
  total input    ≈ 3 × 20,000 = 60,000 input tokens
  total output   ≈ 5 × 3 × 800 = 12,000 output tokens

The single-agent version of the same job — one call, same input,
same answer length — is 2,400 input and 800 output tokens.

The comparison is roughly twenty-five times the input tokens and fifteen times the output for the crew. That may still be the right trade if the crew produces a better answer, and sometimes it does. The point is that it is a factor of twenty, not a factor of one and a bit, and the term that dominates is the quadratic one — each additional task carries every earlier output.

The lever with the most leverage is therefore G, the output length per task, because it appears in the quadratic term. Capping intermediate outputs at a few hundred tokens costs almost nothing in quality and changes the shape of the bill.

Delegation is the expensive setting

Agents can be permitted to delegate to one another, and the hierarchical process adds a manager agent that assigns and reviews. Both are off by default in the snippet above for a reason.

With delegation enabled, the number of turns stops being something you chose and becomes something the model decides. Two agents can hand a task back and forth; a manager can re-issue work it judges unsatisfactory. In the arithmetic above, r stops being 3 and becomes unbounded until a limit stops it. Set a maximum iteration count and a wall-clock or spend cap on every crew that has delegation on — the general argument is in the unbounded agent and it applies with particular force here.

A second, quieter problem: with delegation the trace becomes hard to read, and “which agent produced this claim” is a question you will ask during the first incident. Instrument before you enable it, not after — see tracing your own calls.

Keeping it cheap

  1. Cap every intermediate output. State a length inexpected_output. This attacks the quadratic term directly.
  2. Pass only what the next task needs. The context list is explicit; use it. A task that receives all four previous outputs when it needs one is paying for three.
  3. Assign a smaller model per agent where the work is mechanical. Extraction, formatting and classification rarely need the strongest model, and the agent boundary makes this a one-line change.
  4. Start sequential and keep delegation off. Turn it on only when you have a concrete case where the fixed order fails.
  5. Compare against one call before shipping. Run the same input through a single well-prompted call and read both answers. If the crew is not clearly better, it is not better, and the arithmetic above is what it cost you to find out. The general question is covered in multi-agent systems.

Debugging a crew

The abstraction that makes a crew readable also makes it opaque when it goes wrong, because the interface between tasks is prose. Three habits deal with most of it.

  • Read the intermediate outputs, not the final one. A bad final answer is almost always a bad handover two tasks earlier — a research step that returned confident nonsense, which every later task then treated as established fact. Turn on the verbose output while developing, and capture each task’s result independently.
  • Do not rely on the shape of a handover. Task output is text unless you have asked for structure, and a downstream task that parses it is depending on a format nothing enforces. Where the handover matters, specify a format in the expected output and validate it — the argument in reliable JSON output applies to agent-to-agent handovers as much as to API responses.
  • Trace it as a tree, not as a log. With several agents and tool calls, a flat log is unreadable within a week. Spans nested under one trace per run, with the agent and task on each, turn “which step produced this claim” into a lookup — the setup is on the tracing page.

There is also a failure that looks like a bug and is not: two runs of the same crew on the same input producing materially different work. Every task is a sampled generation, and errors compound across tasks rather than averaging out. If run-to-run variance is unacceptable, the answer is fewer tasks and tighter output specifications, not a lower temperature.

This library’s configuration surface — memory options, planning flags, the YAML project layout generated by its CLI — has changed repeatedly. The agent-task-crew-process model has not. Take field names from the version you install; the arithmetic above holds regardless of what they are called.