Crews vs Flows: What Is the Difference?
A Crew is good at open-ended, role-based collaboration where agents decide how to divide and complete work. A Flow is good at structured pipelines where you need deterministic control over execution order, branching, and state.
| Crew | Flow | |
|---|---|---|
| Execution model | Agents collaborate, tasks assigned by process mode | Explicit steps connected by decorators |
| Control flow | Emergent from agent decisions | Deterministic — you define the DAG |
| State management | Via task context | Typed pydantic state shared across all steps |
| Best for | Research, writing, analysis with human-like collaboration | ETL pipelines, approval chains, multi-stage generation |
| Mix with Python? | Limited | Yes — any step can be pure Python |
The key insight: Flows let you call Crews as steps within a larger structured pipeline. You get the best of both — deterministic orchestration with agentic execution inside each step.
Your First Flow
from crewai.flow.flow import Flow, listen, start
from pydantic import BaseModel
class ContentState(BaseModel):
topic: str = ""
outline: str = ""
draft: str = ""
final: str = ""
class ContentFlow(Flow[ContentState]):
@start()
def set_topic(self):
"""Entry point — runs first."""
self.state.topic = "The impact of AI on software engineering"
print(f"Topic set: {self.state.topic}")
@listen(set_topic)
def generate_outline(self):
"""Runs after set_topic completes."""
# In a real flow: call an LLM or a Crew here
self.state.outline = "1. Introduction\n2. Code generation\n3. Testing\n4. Conclusions"
print("Outline ready")
@listen(generate_outline)
def write_draft(self):
"""Runs after outline is ready."""
self.state.draft = f"Draft article about: {self.state.topic}\n{self.state.outline}"
print("Draft written")
@listen(write_draft)
def finalise(self):
self.state.final = self.state.draft + "\n\n[Reviewed and approved]"
return self.state.final
flow = ContentFlow()
result = flow.kickoff()
print(result)
Calling a Crew Inside a Flow Step
This is the most powerful pattern — use a Crew for the complex, agentic parts and Flow for the structure around them:
from crewai import Agent, Task, Crew
from crewai.flow.flow import Flow, listen, start
from pydantic import BaseModel
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model="claude-haiku-4-5-20251001")
class ResearchState(BaseModel):
query: str = ""
research_findings: str = ""
summary: str = ""
class ResearchFlow(Flow[ResearchState]):
@start()
def intake(self):
self.state.query = "Latest developments in AI agent memory systems"
@listen(intake)
def run_research_crew(self):
"""Hand off to a Crew for the research step."""
researcher = Agent(
role="Research Specialist",
goal="Find accurate, detailed information on the given topic",
backstory="Expert at synthesising technical research",
llm=llm, verbose=False
)
task = Task(
description=f"Research this topic thoroughly: {self.state.query}",
agent=researcher,
expected_output="Detailed research findings in bullet points"
)
crew = Crew(agents=[researcher], tasks=[task])
self.state.research_findings = str(crew.kickoff())
@listen(run_research_crew)
def summarise(self):
"""A simple Python step — no agent needed."""
lines = self.state.research_findings.split("\n")
self.state.summary = "\n".join(lines[:10]) # first 10 lines
return self.state.summary
flow = ResearchFlow()
result = flow.kickoff()
Conditional Routing with @router
Use @router to branch the flow based on state — different paths for different conditions:
from crewai.flow.flow import Flow, listen, start, router
from pydantic import BaseModel
class ReviewState(BaseModel):
content: str = ""
word_count: int = 0
quality_score: float = 0.0
class QualityFlow(Flow[ReviewState]):
@start()
def generate_content(self):
self.state.content = "This is a sample article..." * 50
self.state.word_count = len(self.state.content.split())
@listen(generate_content)
def score_content(self):
# Simulate quality scoring (in production: call an LLM evaluator)
self.state.quality_score = 0.85 if self.state.word_count > 100 else 0.4
@router(score_content)
def route_by_quality(self):
"""Return the name of the next method to run."""
if self.state.quality_score >= 0.7:
return "publish"
else:
return "revise"
@listen("publish")
def publish(self):
print(f"Publishing content (score: {self.state.quality_score:.0%})")
return "published"
@listen("revise")
def revise(self):
print(f"Content needs revision (score: {self.state.quality_score:.0%})")
self.state.content += " [REVISED]"
return "revised"
flow = QualityFlow()
result = flow.kickoff()
The string returned by a @router method must exactly match the string passed to @listen() on the branch methods. A typo here causes the flow to silently skip that branch.Parallel Steps with @listen on Multiple Events
When a step should only run after multiple predecessors complete, list them in @listen:
from crewai.flow.flow import Flow, listen, start, and_
from pydantic import BaseModel
class ParallelState(BaseModel):
seo_keywords: list = []
market_research: str = ""
final_brief: str = ""
class ContentBriefFlow(Flow[ParallelState]):
@start()
def fetch_seo_keywords(self):
self.state.seo_keywords = ["AI agents", "LLM frameworks", "automation"]
@start()
def run_market_research(self):
self.state.market_research = "Target audience: developers building AI apps"
# Only runs after BOTH start steps complete
@listen(and_(fetch_seo_keywords, run_market_research))
def compile_brief(self):
self.state.final_brief = (
f"Keywords: {self.state.seo_keywords}\n"
f"Research: {self.state.market_research}"
)
return self.state.final_brief
flow = ContentBriefFlow()
result = flow.kickoff() # fetch_seo_keywords and run_market_research run in parallel
Passing Initial State to kickoff()
flow = ContentFlow()
# Pass initial state values at kickoff time
result = flow.kickoff(inputs={
"topic": "Building production-ready RAG systems"
})
When to Use Flows vs Crews
- Use a Flow when you need deterministic execution order — step 1 must always precede step 2.
- Use a Flow when you need to branch based on outputs — quality gates, approval routing, conditional processing.
- Use a Flow when you want to mix LLM steps with regular Python logic without forcing everything through an agent.
- Use a Crew alone when the task is open-ended and benefits from agent collaboration without rigid sequencing.
- Use both together (Crew inside a Flow step) for complex pipelines that need both structure and agentic flexibility.