Building Multi-Agent Systems with CrewAI
Stop building a smarter agent. Start building a team.

I’m Siddhesh, a Microsoft Certified Trainer, cloud architect, and AI practitioner focused on helping developers and organizations adopt AI effectively. As a Pluralsight instructor and speaker, I design and deliver hands-on AI enablement programs covering Generative AI, Agentic AI, Azure AI, and modern cloud architectures.
With a strong foundation in Microsoft .NET and Azure, my work today centers on building real-world AI solutions, agentic workflows, and developer productivity using AI-assisted tools. I share practical insights through workshops, conference talks, online courses, blogs, newsletters, and YouTube—bridging the gap between AI concepts and production-ready implementations.
This is the written companion to my talk at the Agentic AI Conference (Virtual), 18 July 2026. It walks from the why of multi-agent systems all the way to a production-grade, live-demo Flow with two Crews inside it — the AI Trends Newsroom. Every diagram, code snippet, and design decision here is drawn from the demos and slides I ran on stage.
📓 All the notebooks and runnable demos live here: github.com/siddheshp/agentic-ai-conference-2026 — clone it and follow along.
New here? A 30-second primer
If you've only ever used ChatGPT, here are the three words you need before we start:
LLM (Large Language Model) — the "brain," like GPT-4o. It reads text and writes text.
Agent — an LLM given a job: a role, a goal, and some tools (like web search). Unlike a chatbot that waits for your next message, an agent keeps working toward its goal on its own.
Multi-agent system — several agents, each specialized, working together like a team of coworkers.
That's it. If you know those three, you're ready. Let's go.
Executive Summary
Most teams meet generative AI the same way: one giant LLM call that is asked to research, write, code, review, and reply — all at once. It works in a demo and collapses in production. The fix isn't a smarter prompt. It's architecture: a team of specialized agents, each great at one thing, coordinated by a deterministic workflow.
CrewAI is the leading open-source Python framework for exactly this. It gives you two complementary primitives:
Crews — the intelligence. Teams of role-playing agents that collaborate autonomously.
Flows — the backbone. Event-driven, Pydantic-typed workflows with branching, loops, gates, and persistence.
The line to remember: Flow is the manager. Crew is the specialist team it hires for a hard task.
By the end of this post you'll know when multi-agent is the right tool (and when it's over-engineering), the four primitives that let you read any CrewAI codebase, and how to assemble a real Flow-with-Crews pipeline that publishes an article and draws its own architecture diagram.
1. The problem: one agent doing everything
Here's the pattern almost everyone tries first.
One prompt, one model, six responsibilities. And quality collapses on every dimension at once. Why?
Tunnel vision — the model over-optimizes for the most recent instruction and quietly drops earlier requirements.
Context overload — stuffing research, writing, and review into one window blows the token budget and degrades reasoning.
No specialization — one persona can't credibly be a security auditor, a marketing copywriter, and a refund agent.
No parallelism — a single call runs serially; four agents can think in parallel and merge.
These aren't prompt-engineering problems. They're architectural problems. And you fix architecture with architecture.
2. The mental shift: from soloist to team
Same model. Same tools. Radically different results — because responsibility is now divided.
Instead of one generic agent that's okay at everything, you get a Researcher, a Writer, a Reviewer, and a Lead — each excellent at one thing, each with a focused context window (its own small, uncluttered workspace to think in).
An analogy: the specialist you'd actually trust
Would you go to a general physician — or a cardiologist — for heart surgery?
A general physician is wonderful and knows a bit of everything. But when it's heart surgery, you want the cardiologist who does nothing else all day. Same doctor-brain, same medical training — but years of narrow focus make one dramatically better at the hard, specific task.
That's exactly the multi-agent idea. One "do-everything" agent is your general physician: fine for simple things, out of its depth on the hard ones. A crew of specialists — each with a sharp role and backstory — is a hospital full of experts who each handle the one thing they're best at, then hand off to the next. Same underlying model; far better outcomes.
But don't reach for a crew every time
Multi-agent is not always the answer. Map your problem onto complexity vs. autonomy:
Only the top-right — high complexity and high autonomy — is the multi-agent sweet spot. If the execution order is fixed and predetermined, a plain workflow/DAG is cheaper and more predictable. If it's simple and deterministic, just write a function.
Don't ship a crew to send an email. If your problem sits in one of the other three quadrants, CrewAI is over-engineered.
Back to our analogy: you don't call a cardiologist to put on a band-aid. Match the specialist to the severity of the task — a whole crew of AI agents for a one-line job is expensive over-engineering.
3. What is CrewAI?
CrewAI is a lean, fast, standalone Python framework (it does not depend on LangChain) built specifically for orchestrating autonomous agents from notebook to production.
| Signal | Value |
|---|---|
| GitHub stars | ~55k+ ⭐ (7.9k forks) |
| License | MIT |
| Certified developers | 100,000+ via learn.crewai.com |
| Language | Python 98.8% |
| Latest line | Fortune-500 production deployments |
Its architecture rests on two primitives in the same package, doing different jobs:
Flows give you precision. Crews give you agency. You use them together.
4. The four primitives that unlock everything
If you learn only five words — Agent, Task, Crew, Process, LLM — you can read any CrewAI codebase. Here's the map.
4.1 Agent — the persona triangle
An agent is a digital employee, not a chatbot. A chatbot responds to prompts, is stateless, and handles one task. An agent acts toward a goal, maintains context, and reasons across many steps — it doesn't wait for your next prompt.
from crewai import Agent
researcher = Agent(
role="AI Researcher",
goal="Explain AI agents in simple terms",
backstory="15 years writing reports for Gartner and Forrester. You cite sources.",
llm=llm,
verbose=True,
)
The backstory isn't decoration — it constrains behavior. "You worked at Reuters; you don't ship a number you can't source" produces a measurably more careful agent than a vague role. A vague role gives you a vague result.
4.2 Task — and the magic of context=
A task is a unit of work with a description and an expected_output. The single most under-used feature in CrewAI is context= — it wires the output of one task into the input of the next.
from crewai import Task
research = Task(description="Research the market for {topic}", agent=researcher,
expected_output="A market report")
analysis = Task(description="Analyse the report and extract 3 insights", agent=analyst,
expected_output="3 insights",
context=[research]) # ← receives the research output automatically
Without context, your agents work in silos even inside the same crew. With it, they build on each other and CrewAI handles all the plumbing.
4.3 Crew — sequential vs. hierarchical
Sequential — a linear pipeline where you set the order. Ideal for research → write → review.
Hierarchical — you provide a
manager_llmand it decides at runtime who handles each task. Perfect for support triage where you don't know upfront whether a ticket is a refund, a bug, or an account issue.
Hierarchical is more powerful but costs more tokens and is less predictable — use it only when you actually need the routing.
from crewai import Crew, Process
crew = Crew(
agents=[researcher, analyst, writer, editor],
tasks=[research, analysis, draft, polish],
process=Process.sequential, # or Process.hierarchical + manager_llm=...
verbose=True,
)
result = crew.kickoff(inputs={"topic": "multi-agent AI"})
4.4 Tools — the verbs
If an agent is the noun, tools are what it can do. The @tool decorator wraps any Python function in one line.
from crewai.tools import tool
@tool("lint_check")
def lint_check(path: str) -> str:
"""Run a linter on a Python file and return the findings.
The model reads THIS docstring to decide when to call the tool."""
...
Critical tip: the docstring is a prompt. The model reads it to decide when to call the tool. Write it like an instruction, not documentation.
CrewAI ships dozens of built-ins in crewai-tools: SerperDevTool (web search), WebsiteSearchTool / FirecrawlSearchTool (scraping), RagTool (query PDFs/docs), FileReadTool, CSVSearchTool, GithubSearchTool, and more. You rarely need to write your own for the common cases.
5. Flows — event-driven orchestration
Crews are autonomous but hard to control precisely. Flows give you the deterministic backbone. Three decorators do the wiring:
| Decorator | Meaning |
|---|---|
@start() |
Entry point — runs first (multiple starts can run in parallel) |
@listen(step) |
Runs after the named step completes; receives its output |
@router(step) |
Runs after the step and returns a route string to control which branch fires next |
State is a real Pydantic class, not a loose dict — typed, validated, and auto-completed in your IDE. Every flow run also gets a unique UUID, which becomes your resume key with @persist.
from crewai.flow.flow import Flow, listen, router, start
from pydantic import BaseModel
class ExampleState(BaseModel):
success: bool = False
class RouterFlow(Flow[ExampleState]):
@start()
def begin(self):
self.state.success = check_something()
@router(begin)
def gate(self):
return "publish" if self.state.success else "retry"
@listen("publish")
def ship(self): ...
@listen("retry")
def try_again(self): ...
Also worth knowing: or_() / and_() combine multiple triggers, @persist gives you SQLite-backed resume-after-crash, and @human_feedback (CrewAI ≥ 1.8) pauses a flow for human approval and routes on the response.
6. The live demo: an AI Trends Newsroom
Now we put it together. One Flow, two Crews, five agents — and no prompt longer than a couple hundred lines. A topic goes in; a Research Crew works it; a quality router decides publish or dig deeper; a Writing Crew produces the article; and you get a published markdown file plus an auto-generated flow diagram.
6.1 Structured state — typed shared memory
from typing import Optional
from pydantic import BaseModel
class NewsroomState(BaseModel):
topic: str = ""
audience: str = "developers"
research: str = "" # filled by research_phase
research_word_count: int = 0 # read by quality_gate
deepened_count: int = 0 # loop guard
article: str = "" # filled by write_and_publish
published_path: str = ""
started_at: Optional[str] = None
finished_at: Optional[str] = None
Every field is a typed Pydantic attribute. Any step can read or write self.state.<field>, and it persists across the whole flow.
6.2 The Research Crew — three agents, context in action
from crewai import Agent, Task, Crew, Process
from crewai_tools import SerperDevTool
web_search = SerperDevTool() # needs SERPER_API_KEY
def build_research_crew(llm) -> Crew:
trend_hunter = Agent(
role="Trend Hunter",
goal="Find the freshest, most cited angles on the topic.",
backstory="You scan the timeline so the rest of the team doesn't have to.",
tools=[web_search], llm=llm, allow_delegation=False, verbose=True)
fact_checker = Agent(
role="Fact Checker",
goal="Validate every claim, flag anything that smells like a hallucination.",
backstory="You worked at Reuters. You don't ship a number you can't source.",
tools=[web_search], llm=llm, allow_delegation=False, verbose=True)
analyst = Agent(
role="Industry Analyst",
goal="Turn verified findings into a tight insight memo.",
backstory="Two decades synthesising signal from noise for a Tier-1 advisory firm.",
llm=llm, allow_delegation=False, verbose=True)
hunt = Task(description="Topic: {topic}\nAudience: {audience}\nSurface 5-7 recent angles. Use the search tool.",
expected_output="Markdown bullet list of 5-7 angles.", agent=trend_hunter)
verify = Task(description="Validate each angle. Mark ✅/⚠️/❌. Drop unsupported items.",
expected_output="Annotated list with justifications.", agent=fact_checker,
context=[hunt])
synth = Task(description="Synthesise into a 200-300 word insight memo: headline, 3 points, 1 contrarian view.",
expected_output="A 200-300 word memo.", agent=analyst,
context=[hunt, verify]) # ← sees BOTH prior tasks
return Crew(agents=[trend_hunter, fact_checker, analyst],
tasks=[hunt, verify, synth],
process=Process.sequential, verbose=True)
Notice synth.context=[hunt, verify] — the analyst sees both the hunt and the verification. That's the context pattern from §4.2 doing real work.
6.3 The Flow — the backbone is ~40 lines
The entire orchestration is less code than a Flask route. The intelligence lives in the two crews it calls.
from datetime import datetime
from pathlib import Path
import os
from crewai import LLM
from crewai.flow.flow import Flow, listen, router, start
class NewsroomFlow(Flow[NewsroomState]):
"""Flow = backbone. Crews = intelligence."""
MIN_RESEARCH_WORDS = 120
MAX_DEEPEN_LOOPS = 1
def __init__(self, *a, **k):
super().__init__(*a, **k)
self._llm = LLM(model=os.getenv("OPENAI_MODEL", "gpt-4o-mini"), temperature=0.4)
@start()
def gather_topic(self):
self.state.started_at = datetime.now().isoformat(timespec="seconds")
return {"topic": self.state.topic, "audience": self.state.audience}
@listen(gather_topic)
async def research_phase(self, payload):
result = await build_research_crew(self._llm).kickoff_async(inputs=payload)
self.state.research = result.raw
self.state.research_word_count = len(result.raw.split())
return result.raw
@router(research_phase)
def quality_gate(self):
if self.state.research_word_count >= self.MIN_RESEARCH_WORDS:
return "publish"
if self.state.deepened_count >= self.MAX_DEEPEN_LOOPS:
return "publish" # budget exhausted → publish anyway
return "needs_more"
@listen("needs_more")
async def deepen_research(self):
self.state.deepened_count += 1
result = await build_research_crew(self._llm).kickoff_async(inputs={
"topic": f"{self.state.topic} (deeper, with concrete numbers)",
"audience": self.state.audience})
self.state.research = result.raw
self.state.research_word_count = len(result.raw.split())
return self.quality_gate() # loop back through the gate
@listen("publish")
async def write_and_publish(self):
result = await build_writing_crew(self._llm).kickoff_async(inputs={
"topic": self.state.topic, "audience": self.state.audience,
"research": self.state.research})
self.state.article = result.raw
out = Path("newsroom_article.md").resolve()
out.write_text(self.state.article, encoding="utf-8")
self.state.published_path = str(out)
self.state.finished_at = datetime.now().isoformat(timespec="seconds")
return self.state.article
The deepened_count guard is the important detail: it stops the router from looping forever if quality_gate keeps saying needs_more.
6.4 Run it — and let it draw itself
flow = NewsroomFlow(state=NewsroomState(topic="multi-agent AI", audience="developers"))
flow.plot() # → interactive newsroom_flow.html
await flow.kickoff_async() # runs both crews, ~2-4 min
print(flow.usage_metrics) # full token rollup across ALL 5 LLM calls
print(flow.state.published_path) # newsroom_article.md on disk
Two things worth calling out:
flow.plot()auto-generates an interactive HTML diagram of the entire pipeline — no manual drawing.flow.usage_metricsis the full token rollup across every LLM call — both crews plus any bareLLM.call(). (Don't confuse it withflow.kickoff().token_usage, which only reflects the final crew.)
7. From demo to production: six non-negotiables
Everything above is building. Shipping is a different bar. Skip any one of these and you have a demo, not a product.
Guardrails — Pydantic on every tool output; reject hallucinated calls.
Retries & circuit breakers —
tenacityaround flaky APIs; fail loud, not silent.Observability — OpenTelemetry GenAI spans; one trace per crew kickoff.
Human-in-the-loop gates — approval before destructive or costly actions.
Evaluation — a golden set with promptfoo or DeepEval, per agent and per crew.
Cost control —
usage_metricsfeeding daily budget alerts.
8. An honest look at the landscape
CrewAI isn't the only game in town. Pick based on your team's mental model, not a star count.
| Framework | Best for | Learning curve |
|---|---|---|
| CrewAI | Role-play teams, prototype → production | Gentle |
| LangGraph | Precise, deterministic state graphs | Steep |
| AutoGen | Conversational / chat-between-agents | Medium |
| Microsoft Agent Framework | Enterprise, Azure-native, compliance | Medium |
| LlamaIndex Agents | RAG-first with light agentic glue | Gentle |
Choose CrewAI when the problem is naturally team-shaped, you want fast prototype-to-production, you need both structured pipelines and autonomous teams, and your team thinks in personas, not graphs.
Pick something else when you need a precise deterministic state graph (LangGraph), you're all-in on Azure with enterprise compliance (Microsoft Agent Framework), your problem is fundamentally RAG (LlamaIndex), or chat-between-agents is the primary metaphor (AutoGen).
9. Two things to take away
Start with a Flow. Hire a Crew when a step needs creativity. The Flow gives you deterministic control; the Crew gives you agency exactly where you need it.
Production isn't about the framework — it's about guardrails, evaluation, and cost control. The demo is the easy 20%.
The shift from single prompts to autonomous teams is architectural, not cosmetic. You don't fix a tunnel-visioned mega-prompt with more prompt engineering — you fix it by dividing responsibility across specialists and orchestrating them with a typed, event-driven backbone. That's the whole talk in one line: from single prompts to autonomous teams.
Resources
📓 Talk notebooks & runnable demos — github.com/siddheshp/agentic-ai-conference-2026
CrewAI docs — docs.crewai.com
CrewAI source — github.com/crewAIInc/crewAI
Free courses (100k+ certified) — learn.crewai.com
Example projects — crewAIInc/crewAI-examples
Written by Siddhesh Prabhugaonkar — architect, consultant, and trainer across IT, Cloud, and Generative AI; Microsoft Certified Trainer and Pluralsight instructor. More at cloud-authority.com · LinkedIn · YouTube.




