# Building Multi-Agent Systems with CrewAI

> 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](https://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**](https://github.com/crewAIInc/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.

![](https://cdn.hashnode.com/uploads/covers/651bff05e4455a8ac9ec7688/18d8222f-605d-4716-90d3-465a28584189.png align="center")

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*.

![](https://cdn.hashnode.com/uploads/covers/651bff05e4455a8ac9ec7688/929565a6-5387-4782-a69a-ca8a8a3bcac3.png align="center")

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:

![](https://cdn.hashnode.com/uploads/covers/651bff05e4455a8ac9ec7688/29f1e255-9a7f-4921-828d-eb4b63feaeed.png align="center")

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:

![](https://cdn.hashnode.com/uploads/covers/651bff05e4455a8ac9ec7688/5b638b56-cc47-4fd7-841e-99d97d5c17d7.png align="center")

**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.

![](https://cdn.hashnode.com/uploads/covers/651bff05e4455a8ac9ec7688/fa0e1910-9132-4b96-af06-ce24c173138c.png align="center")

### 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.

```python
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.

```python
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

![](https://cdn.hashnode.com/uploads/covers/651bff05e4455a8ac9ec7688/8d0487fb-c71d-45db-9321-dc0b03d51534.png align="center")

*   **Sequential** — a linear pipeline where you set the order. Ideal for research → write → review.
    
*   **Hierarchical** — you provide a `manager_llm` and 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.

```python
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.

```python
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`.

```python
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.

![](https://cdn.hashnode.com/uploads/covers/651bff05e4455a8ac9ec7688/4f0c0e57-c750-475e-beaa-aeb567f30220.png align="center")

### 6.1 Structured state — typed shared memory

```python
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

![](https://cdn.hashnode.com/uploads/covers/651bff05e4455a8ac9ec7688/352a40e6-fb64-4fb2-aaf3-7f972f873d8f.png align="center")

```python
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.

```python
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

```python
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_metrics` is the *full* token rollup across every LLM call — both crews plus any bare `LLM.call()`. (Don't confuse it with `flow.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.

1.  **Guardrails** — Pydantic on every tool output; reject hallucinated calls.
    
2.  **Retries & circuit breakers** — `tenacity` around flaky APIs; fail loud, not silent.
    
3.  **Observability** — OpenTelemetry GenAI spans; one trace per crew kickoff.
    
4.  **Human-in-the-loop gates** — approval before destructive or costly actions.
    
5.  **Evaluation** — a golden set with promptfoo or DeepEval, per agent *and* per crew.
    
6.  **Cost control** — `usage_metrics` feeding 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

1.  **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.
    
2.  **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](https://github.com/siddheshp/agentic-ai-conference-2026)
    
*   **CrewAI docs** — [docs.crewai.com](https://docs.crewai.com)
    
*   **CrewAI source** — [github.com/crewAIInc/crewAI](https://github.com/crewAIInc/crewAI)
    
*   **Free courses (100k+ certified)** — [learn.crewai.com](https://learn.crewai.com)
    
*   **Example projects** — [crewAIInc/crewAI-examples](https://github.com/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*](https://cloud-authority.com) *·* [*LinkedIn*](https://www.linkedin.com/in/siddheshprabhugaonkar) *·* [*YouTube*](https://www.youtube.com/c/SiddheshPrabhugaonkar)*.*
