Multi-Agent Workflow with CrewAI: Team-Based Task Execution
By Diesel
tutorialcrewaimulti-agentpython
A single agent hits a ceiling fast. It can't be an expert researcher and a brilliant writer and a meticulous editor all at once. The context gets muddy, the quality drops, and you end up with mediocre everything instead of excellent anything.
The fix is the same one humans figured out centuries ago. Specialization. Give each agent a role, a goal, and a set of tools. Let them collaborate. CrewAI makes this surprisingly clean.
## What We're Building
A content research crew. Three agents: a Researcher who finds information, a Writer who turns research into articles, and an Editor who polishes the final output. They work sequentially, each building on the previous agent's work.
## Setup
```bash
pip install crewai crewai-tools langchain-anthropic
```
```bash
export ANTHROPIC_API_KEY="your-key-here"
```
## Define the Agents
Each agent gets a role, a goal, and a backstory. The backstory isn't fluff. It shapes how the LLM approaches the task. A "senior investigative journalist" writes differently than a "content marketing intern."
```python
from crewai import Agent, Task, Crew, Process
from crewai_tools import SerperDevTool, ScrapeWebsiteTool
search_tool = SerperDevTool()
scrape_tool = ScrapeWebsiteTool()
researcher = Agent(
role="Senior Research Analyst",
goal="Find comprehensive, accurate information on the given topic",
backstory="""You're a veteran research analyst with 15 years of experience
in technology research. You're known for finding information others miss,
cross-referencing sources, and identifying emerging trends before they
become obvious. You never cite a single source without verification.""",
tools=[search_tool, scrape_tool],
llm="anthropic/claude-sonnet-4-20250514",
verbose=True,
allow_delegation=False,
)
writer = Agent(
role="Technical Content Writer",
goal="Transform research into engaging, technically accurate articles",
backstory="""You write for developers who hate fluff. Your articles are
sharp, practical, and packed with code examples. You've written for major
tech publications and your readers trust you because you never oversimplify
or overpromise. You explain complex topics with clarity, not condescension.""",
llm="anthropic/claude-sonnet-4-20250514",
verbose=True,
allow_delegation=False,
)
editor = Agent(
role="Senior Technical Editor",
goal="Ensure content is accurate, well-structured, and publication-ready",
backstory="""You've edited technical content for a decade. You catch
inaccuracies that writers miss, restructure paragraphs for flow, and
enforce consistency. You're ruthless about cutting filler. If a sentence
doesn't earn its place, it's gone.""",
llm="anthropic/claude-sonnet-4-20250514",
verbose=True,
allow_delegation=False,
)
```
`allow_delegation=False` keeps each agent in its lane. The researcher doesn't try to write, the writer doesn't try to research. Clean separation of concerns.
## Define the Tasks
Tasks are the work units. Each one has a description, expected output, and an assigned agent.
```python
research_task = Task(
description="""Research the topic: {topic}
Requirements:
- Find at least 5 credible sources
- Include recent data and statistics (2025-2026)
- Identify key trends, challenges, and opportunities
- Note any conflicting information between sources
- Provide source URLs for everything The related post on [how CrewAI compares to alternatives](/blog/crewai-autogen-langgraph-comparison) goes further on this point.
Be thorough. The writer needs solid material to work with.""",
expected_output="""A structured research report with:
- Executive summary (3-4 sentences)
- Key findings (numbered list)
- Supporting data and statistics
- Source URLs
- Noted contradictions or gaps""",
agent=researcher,
)
writing_task = Task(
description="""Using the research provided, write a technical article.
Requirements:
- 1000-1200 words
- Include code examples where relevant
- Use clear headers and subheaders
- Write for experienced developers
- No marketing language, no fluff
- Cite sources inline
The research is your source of truth. Don't invent facts.""",
expected_output="""A complete article in markdown format with:
- Compelling opening paragraph
- Clear section structure
- Code examples
- Practical takeaways
- Source citations""",
agent=writer,
context=[research_task], # Writer gets researcher's output
)
editing_task = Task(
description="""Edit the article for publication.
Check for:
- Technical accuracy (verify claims against the research)
- Logical flow between sections
- Redundant or filler content (cut it)
- Code example correctness
- Consistent tone and style
- Grammar and clarity
Return the final, publication-ready version.""",
expected_output="""The final edited article in markdown, plus a brief
editor's note listing what was changed and why.""",
agent=editor,
context=[research_task, writing_task], # Editor sees everything
output_file="output/article.md",
)
```
The `context` parameter is how information flows between agents. The writer receives the researcher's output. The editor receives both. No manual message passing needed.
## Assemble the Crew
```python
crew = Crew(
agents=[researcher, writer, editor],
tasks=[research_task, writing_task, editing_task],
process=Process.sequential,
verbose=True,
)
```
Sequential process means tasks run one after another. Researcher finishes, writer starts, writer finishes, editor starts. Simple, predictable, debuggable.
## Run It
```python
result = crew.kickoff(
inputs={"topic": "Building production AI agents with tool use in 2026"}
)
print(result)
```
## Hierarchical Process: The Manager Pattern
Sequential is great for linear workflows. But what if tasks need dynamic coordination? CrewAI supports a hierarchical process where a manager agent delegates work. The related post on [orchestration patterns](/blog/multi-agent-orchestration-patterns) goes further on this point.
```python
from crewai import Crew, Process
crew = Crew(
agents=[researcher, writer, editor],
tasks=[research_task, writing_task, editing_task],
process=Process.hierarchical,
manager_llm="anthropic/claude-sonnet-4-20250514",
verbose=True,
)
```
The manager decides which agent handles what, can reassign tasks, and coordinates the workflow dynamically. More powerful, but harder to debug. Start with sequential, graduate to hierarchical when you need it.
## Custom Tools
CrewAI tools are straightforward to build. Here's a database query tool.
```python
from crewai_tools import BaseTool
from pydantic import Field
import sqlite3
class DatabaseQueryTool(BaseTool):
name: str = "query_database"
description: str = "Execute a read-only SQL query against the analytics database"
db_path: str = Field(default="./analytics.db")
def _run(self, query: str) -> str:
conn = sqlite3.connect(self.db_path)
try:
# Safety: only allow SELECT
if not query.strip().upper().startswith("SELECT"):
return "Error: Only SELECT queries are allowed"
cursor = conn.execute(query)
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()
result = [dict(zip(columns, row)) for row in rows]
return str(result)
except Exception as e:
return f"Query error: {e}"
finally:
conn.close()
# Give it to an agent
data_analyst = Agent(
role="Data Analyst",
goal="Extract insights from the analytics database",
backstory="You're a data analyst who writes clean SQL and finds patterns in numbers.",
tools=[DatabaseQueryTool()],
llm="anthropic/claude-sonnet-4-20250514",
)
```
## Memory and Learning
CrewAI supports memory so agents remember across executions.
```python
crew = Crew(
agents=[researcher, writer, editor],
tasks=[research_task, writing_task, editing_task],
process=Process.sequential,
memory=True,
embedder={
"provider": "huggingface",
"config": {
"model": "BAAI/bge-small-en-v1.5"
}
}
)
```
With memory enabled, the crew remembers previous runs. The researcher knows what topics were covered before. The writer knows what style worked. The editor remembers past corrections.
## Callbacks for Monitoring
You need visibility into what your agents are doing. CrewAI provides callbacks at the task and step level.
```python
from crewai.utilities.events import (
crewai_event_bus,
TaskStartedEvent,
TaskCompletedEvent,
)
@crewai_event_bus.on(TaskStartedEvent)
def on_task_started(source, event):
print(f"[STARTED] {event.task.description[:80]}...")
@crewai_event_bus.on(TaskCompletedEvent)
def on_task_completed(source, event):
print(f"[COMPLETED] Output length: {len(event.output)} chars")
result = crew.kickoff(inputs={"topic": "AI agent memory systems"})
```
## Practical Patterns
**Parallel research, sequential writing.** Spin up three researchers for different subtopics. Feed all their output to one writer. Faster than sequential for broad topics.
**Quality gate pattern.** Add a reviewer agent between writer and editor that scores the article 1-10. If below 7, send it back to the writer with feedback. Loop until quality threshold is met. This connects directly to [the supervisor pattern](/blog/supervisor-pattern-agent-managers).
**Specialist escalation.** Start with a general agent. If it identifies the task needs domain expertise, delegate to a specialist agent with domain-specific tools and knowledge.
## When Multi-Agent Makes Sense
Don't reach for multi-agent systems when a single agent with good tools will do. Multi-agent adds complexity, latency, and cost. Every agent call is an LLM invocation.
Use multi-agent when:
- Tasks genuinely require different expertise
- Sequential handoff improves quality (research then write then edit)
- Parallel execution provides real speedup
- Quality verification needs a separate perspective
Don't use it when you could just write a better prompt.
The best multi-agent systems feel like well-run teams. Clear roles, clean handoffs, minimal overhead. CrewAI gets you there with surprisingly little code.