A Multi-Agent Crew
Lesson 10 · Building Your Own Agents · ~12 min read
What makes a workload “genuinely multi-agent”
Working definition: a genuinely multi-agent workload has three or more roles, each of which (a) benefits from its own persona and instructions, (b) operates on artefacts produced by another role, and (c) would degrade if you collapsed it into a single agent with a longer system prompt. All three clauses matter. Without (a) you don’t need roles; without (b) the agents aren’t collaborating; without (c) merging them is a free win.
It’s worth contrasting that shape with three things it isn’t:
- One agent, many tools (Module 2’s sweet spot) — many tools per agent, but only one role and one persona. Not multi-agent.
- Triage and handoff (Module 4’s sweet spot — OpenAI Agents SDK) — multiple specialists, but they don’t collaborate; the user is handed from one to another. No artefact flows back.
- Fixed pipeline (Module 3’s sweet spot — LangGraph) — a fixed graph of nodes; nodes can be LLM calls but they aren’t really “agents with roles” in the team sense.
CrewAI is at its sweet spot when (a) roles are clear, (b) artefacts flow between them, and (c) the team metaphor helps the design rather than getting in the way. This lesson’s planner + worker + reviewer trio is the canonical example by construction.
Why planner + worker + reviewer is the canonical trio
Three roles map cleanly to three modes of cognition that show up in almost every non-trivial knowledge task:
- The planner decides what to produce — an outline, a structure, a scope statement. It needs judgement and (often) access to source material via tools.
- The worker executes the plan — turns structure into prose, code, or any other artefact. It needs to follow instructions faithfully and benefits from a deliberately constrained scope.
- The reviewer critiques the output — finds unsupported claims, unclear sentences, gaps versus the plan. It needs to be paid (in backstory) for finding problems, not for being agreeable.
This trio appears in many production systems: writing pipelines (outline → draft → edit), code review (architecture → implementation → review), research reports (research plan → write-up → fact-check). The trio is not magic — it’s just a clean separation of plan / do / verify that most non-trivial work has.
Why this shape works in CrewAI specifically
The role boundaries are visible in code. A new engineer reading the crew sees
three Agent(...) objects with three goals and three backstories; the
design is self-documenting. Other frameworks can build the same shape but won’t
make it as legible — a LangGraph DAG with three nodes communicates “graph,”
not “team.”
Per-agent tool attachment
A practical implication of CrewAI’s per-agent model: tools are attached to the agents that should have them, not globally to the crew.
In our crew:
-
The planner has
tools=[topic_lookup]— it needs to ground its outline in real knowledge-base entries before deciding what sections to include. -
The worker has
tools=[]— drafting works from the planner’s outline alone. Giving the worker tools would tempt it to re-research instead of write. -
The reviewer has
tools=[]— review works from the artefacts (plan + draft) alone. The reviewer can’t go off and fetch new sources mid-review, which keeps the critique focused on what was actually produced.
This per-role restriction is a feature. It enforces the division of labour by removing the temptation for an agent to do another’s job. Compare with Module 2’s single-agent research assistant, which gets all tools and has to be prompted in the system message to use them in the right order. With CrewAI, the role boundary does the enforcement — you literally cannot call a tool the agent doesn’t have.
Artefact flow via context=[...]
CrewAI tasks don’t share state implicitly. If task_b should see
task_a’s output, you wire it explicitly via the context
parameter:
draft_task = Task(
description="Draft a brief on {topic} following the outline above EXACTLY.",
agent=worker,
context=[plan_task], # worker sees the planner's output
)
review_task = Task(
description="Review the draft against the original plan.",
agent=reviewer,
context=[plan_task, draft_task], # reviewer sees both
)
Why explicit? Because in a multi-step crew, what each agent sees is part of the design. If the reviewer doesn’t see the plan, it can’t tell whether the draft diverged from what was planned. If the worker sees too much (e.g., a prior conversation log), it might cherry-pick from history instead of following the outline.
Why “CrewAI doesn’t figure this out” is the point
The explicit context= list is one of the things people sometimes complain
about in CrewAI (“why can’t it figure this out?”). The answer is:
because making it figure out would require a stronger autonomy primitive
— a manager agent, or a graph — which is exactly what Process.hierarchical
and LangGraph give you. Plain Process.sequential trades autonomy for
auditability: the artefact flow is visible at a glance.
Structured outputs via output_pydantic
For the draft, we don’t want free-form prose — we want a typed
BriefDraft(title, sections) shape we can hand to downstream code. CrewAI
supports this directly on the task:
from pydantic import BaseModel
class BriefSection(BaseModel):
heading: str
body: str
class BriefDraft(BaseModel):
title: str
sections: list[BriefSection]
draft_task = Task(
description="Draft a brief following the planner's outline.",
expected_output="A BriefDraft object with a title and 3 sections.",
agent=worker,
context=[plan_task],
output_pydantic=BriefDraft,
)
After crew.kickoff(), draft_task.output.pydantic is a real
BriefDraft instance — you can iterate .sections in
ordinary Python without parsing prose. The reviewer’s
context=[draft_task] automatically serialises the object back to text for
the next LLM call, so the reviewer sees a faithful textual representation while your
code sees the typed object.
Spelling and a common mistake
The 0.86 (pinned for this course) spelling is output_pydantic=. Older
CrewAI blog posts and tutorials use output_json= or
output_format= — those are deprecated. Common mistake:
assuming the LLM will produce valid JSON without the constraint. It sometimes
won’t, and CrewAI’s auto-retry on parse failure adds a turn (and cost)
silently. Define the Pydantic model and let CrewAI tell the LLM what shape to produce.
This is the CrewAI equivalent of Module 2’s write_report
final-answer-tool pattern. Different mechanism (typed task output vs. a final
tool), same goal (structured artefacts that downstream code can rely on).
Cost, latency, and the “multi-agent tax”
Realistic numbers for this lesson’s crew, measured against the mocked
topic_lookup tool with Sonnet 4.7 for all three agents:
- Cost: roughly $0.04–$0.08 per brief with all three on Sonnet. That’s about 2–3× Module 2’s single-agent tool-heavy assistant on a similar question. The multi-agent tax is real and measurable.
-
Latency: roughly 20–35 seconds. Sequential, no parallelism
between agents (each agent waits for the prior task’s output). Inside one
agent’s task, the underlying LLM call can still emit parallel tool calls —
but agents themselves don’t parallelise under
Process.sequential. -
Lines of code: roughly 120 lines for the full
agent.py— most of it the role + goal + backstory text. The collaboration glue (Crew(...),Process.sequential) is around six lines.
The trade-off: you pay ~2× the cost for ~2× the latency in exchange for a structurally clearer team, better debuggability per task, and the option to mix models per agent (which can claw most of the cost back — see L10’s Homework Task 2, where you route the worker to Haiku and keep the planner and reviewer on Sonnet).
Don’t reach for CrewAI just because “multi-agent sounds cool”
If your problem fits Module 2’s tool-heavy shape, Module 3’s graph shape, or Module 4’s handoff shape, those will be cheaper and faster. CrewAI earns its keep when the team metaphor actually matches the work — that is, when you genuinely have three roles that each benefit from a distinct persona, scope, and tool subset. Otherwise you’re paying the tax without getting the structure.
What the lab gives you
By the end of the lab you’ll have run
python agent.py "What changed in Linux kernel 6.14’s networking stack?"
against the mocked topic_lookup tool and watched a three-agent trace end in
a structured BriefDraft plus a reviewer summary. You’ll have used
output_pydantic to constrain the draft shape, wired artefacts between tasks
via context=[...], and swapped one agent’s model to a cheaper tier to
see the cost effect first-hand.
Most importantly: you’ll have one self-contained agent.py (~120 lines)
committed at agents/lesson10-multi-agent-crew/agent.py in the course repo.
This is the same file Plan F (Lesson 11) imports into the cross-framework
scorecard as the CrewAI row. When you see the side-by-side numbers in Module 6
— “Claude SDK costs $X, CrewAI costs $Y, LangGraph costs $Z, OpenAI SDK costs
$W” — you’ll know exactly which crew produced the CrewAI number, and
why.
References
- CrewAI documentation —
docs.crewai.com/concepts/crews,docs.crewai.com/concepts/tasks,docs.crewai.com/concepts/processes. - LiteLLM provider docs (Anthropic) —
docs.litellm.ai/docs/providers/anthropic. - L02 —
agents/lesson02-agent-loop/theory.html, the hand-rolled baseline this lesson contrasts against. - L09 —
agents/lesson09-crewai-intro/theory.html, the CrewAI primitive primer this builds on.