Lab 09 — First crew with CrewAI
Lesson 09 · Building Your Own Agents · ~30 min hands-on
Goal: at the end of this lab you will have (a) verified crewai is installed
at the pinned version and pointed at Anthropic Claude, (b) defined two agents with
distinct roles, goals, and backstories, (c) wired two Tasks into a
sequential Crew, (d) run crew.kickoff() end-to-end and read
the verbose log, and (e) felt how a single sentence in a backstory changes behaviour.
This is the short CrewAI exploration — just enough to put the framework
in your hands. L10 builds the canonical 3-role planner + worker + reviewer crew on top
of these same primitives.
Prereqs
-
The
agents/requirements.txtalready installed (which pinscrewai>=0.80,<0.90). If not, runpip install -r agents/requirements.txtfrom the repo root. -
ANTHROPIC_API_KEYexported in your shell. CrewAI uses LiteLLM under the hood, which auto-detects the env var matching the model prefix — foranthropic/...models it readsANTHROPIC_API_KEY. - One terminal where you can run
pythonscripts. - Working directory at the repo root (the paths below assume that).
Step 1 — Confirm crewai is installed
python -c "import crewai; print(crewai.__version__)"
Expected: a version string in the 0.80.x–0.89.x range
(e.g., 0.86.0 — the version snapshotted in
agents/SDK-VERSIONS.md). If you get ModuleNotFoundError,
re-run pip install -r agents/requirements.txt and confirm against
agents/SDK-VERSIONS.md.
✓ Self-check: a version like 0.86.0 prints. No traceback.
Step 2 — The 40-line hello world (two-agent crew)
Create hello_crew.py anywhere convenient (the repo root works fine):
from crewai import Agent, Task, Crew, Process
MODEL = "anthropic/claude-3-5-sonnet-latest"
researcher = Agent(
role="Senior Researcher",
goal="Find 3 short factual statements about the topic",
backstory=(
"You are a meticulous researcher. You prefer specs, papers, "
"and primary sources over blog posts. You always cite."
),
llm=MODEL,
verbose=True,
)
writer = Agent(
role="Technical Writer",
goal="Write a 2-3 sentence brief based on the research",
backstory=(
"You are a concise technical writer. You explain ideas "
"without jargon and never invent facts."
),
llm=MODEL,
verbose=True,
)
research_task = Task(
description="Research the topic: {topic}. Return 3 short factual statements with sources.",
expected_output="3 bullet points, each with a brief source attribution.",
agent=researcher,
)
write_task = Task(
description="Write a 2-3 sentence brief on {topic} using the research above.",
expected_output="A 2-3 sentence paragraph, no jargon.",
agent=writer,
context=[research_task],
)
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, write_task],
process=Process.sequential,
verbose=True,
)
result = crew.kickoff(inputs={"topic": "Linux kernel 6.14 networking"})
print("\n=== final brief ===")
print(result)
Run it:
python hello_crew.py
Expected: a stream of [DEBUG] / agent-coloured log lines as each agent
works its task, then a === final brief === section with a short
paragraph. The researcher runs first (because it appears first in tasks=),
then the writer runs with the researcher’s output wired in via
context=[research_task].
✓ Self-check: the script exits cleanly; the final paragraph reads like a 2–3 sentence brief on the topic; you saw two distinct agent turns in the log.
Step 3 — Read the verbose log
Scroll back through the output. You should see, in order:
- The researcher’s system prompt — which includes the role, goal, and backstory you wrote, plus CrewAI’s framing scaffolding.
- The researcher’s response — 3 bullets, ideally with source attributions.
-
The writer’s system prompt — note that the
researcher’s output is included as context, exactly because of the
context=[research_task]wire. - The writer’s response — a short paragraph based on those bullets.
- A line of cost / token info for each LLM call (LiteLLM logs this).
This is what verbose=True gives you out of the box. In production
you’d disable it and pipe structured events to your logger instead — but
for a lab, verbose is the fastest way to see what each agent actually got
asked and what it answered.
Step 4 — Tighten a backstory; observe behaviour change
Edit the researcher’s backstory to add a recency
constraint:
backstory=(
"You are a meticulous researcher. You prefer specs, papers, "
"and primary sources over blog posts. You always cite. "
"You only trust sources from the last 12 months and refuse "
"to cite anything older."
),
Re-run python hello_crew.py. Expected: the researcher’s bullets
now either include explicit recency in source attributions (“per kernel 6.14
release notes, 2025”) or the agent calls out that older sources were
rejected. The behaviour change is in the prompt; CrewAI just made it
trivial to attribute the change to a specific agent’s persona — you
didn’t touch tasks, the crew, or the writer.
✓ Self-check: the researcher’s output for this run is visibly different from Step 2’s output, in a way that traces back to your one-sentence backstory edit.
Step 5 — Drop the context=; see what happens
Edit write_task and remove context=[research_task]:
write_task = Task(
description="Write a 2-3 sentence brief on {topic} using the research above.",
expected_output="A 2-3 sentence paragraph, no jargon.",
agent=writer,
)
Re-run. Expected: the writer produces a brief based on the {topic}
string alone — the researcher’s bullets are nowhere in the
writer’s system prompt. (Look at the verbose log to confirm.) The brief will
often still read fine, because the model has plenty of background knowledge about
most topics — but it’s not grounded in the researcher’s
actual findings.
This shows that artefact flow between agents is not automatic
— you wire it explicitly with context=[...]. If you forget, your
agents become disconnected solo runs that share a topic but not their work.
Restore the context=[research_task] line before continuing.
✓ Self-check: with context= removed, the writer’s system prompt in the log no longer contains the researcher’s 3 bullets. With it restored, it does.
Step 6 — Add a third agent (reviewer)
Add a skeptical reviewer that checks the writer’s brief against the researcher’s evidence. Append these definitions:
reviewer = Agent(
role="Skeptical Reviewer",
goal="Identify any factual errors or weak claims in the brief",
backstory=(
"You are a friendly but rigorous reviewer. "
"You highlight unsupported claims."
),
llm=MODEL,
verbose=True,
)
review_task = Task(
description="Review the brief on {topic}. List any unsupported claims or factual issues.",
expected_output="A short review with 0-3 issues (or 'no issues found').",
agent=reviewer,
context=[research_task, write_task],
)
Update the Crew:
crew = Crew(
agents=[researcher, writer, reviewer],
tasks=[research_task, write_task, review_task],
process=Process.sequential,
verbose=True,
)
Re-run. Expected: three agent turns in the verbose log, in the order
researcher → writer → reviewer. The final printed result is
now the reviewer’s output — because
Process.sequential returns the last task’s output as
the crew result.
✓ Self-check: the final printed text is a review (issues found or “no issues found”), not the writer’s brief. The writer’s brief is still accessible via crew.tasks[1].output if you want to see both.
What you should leave with
-
A working
hello_crew.pythat demonstrates: two (then three) agents, distinct role/goal/backstory per agent,Tasks wired to agents,context=[...]for artefact flow,Process.sequential, and a singlecrew.kickoff()entry point. - First-hand experience of backstory shaping behaviour — one sentence in Step 4 changed the researcher’s output without touching anything else.
-
The intuition that
context=[...]is the load-bearing wire between agents — without it, your crew is a set of unrelated solo runs. - Readiness for L10, which builds the canonical 3-role planner + worker + reviewer crew on these same primitives and adds tools.
Troubleshooting
Common stumbles
-
AuthenticationErrorfrom LiteLLM —ANTHROPIC_API_KEYis not exported in this shell, or you’re behind a corporate proxy that strips theAuthorizationheader. Confirm withpython -c "import os; print(bool(os.environ.get('ANTHROPIC_API_KEY')))"— it should printTrue. - Very slow first run — CrewAI’s first import compiles a small amount of TypedDict / Pydantic machinery. Subsequent runs are noticeably quicker. This is normal.
-
Costs higher than you expected —
verbose=Truedoesn’t add tokens, but having 3 agents each make 1+ LLM calls multiplies cost. A 3-agent crew on a moderate topic is typically $0.03–$0.08 with Sonnet. Compare to L02’s hand-rolled single-agent (~$0.01). If you’re iterating fast, swap the model toanthropic/claude-3-5-haiku-latestfor cheaper development runs. -
Reviewer agrees with everything (“no issues found” on
obvious problems) — that’s a backstory problem, not a model
problem. Strengthen with:
"You are paid to find issues. Saying 'no issues' too often is a failure mode."— the reviewer will start surfacing weak claims it was previously letting slide. Same intervention as Step 4, applied to a different role. -
The final
resultis the writer’s brief, not the reviewer’s output — you forgot to addreview_taskto the crew’stasks=list.Process.sequentialreturns the last task in that list, so if it’s missing, the writer’s task wins by default.