Lesson 10 A Multi-Agent Crew
Theme:

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:

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:

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:

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:

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