CrewAI Flows vs Crews: Which One Should You Build?
How-to — — by Mahmoud Zalt
CrewAI Flows vs Crews explained: what @start, @listen and @router do, how flow state works, and why the docs put a Flow on the outside.
Most people meet Crews first, build something that works, then hit a wall: they want to run step A, look at the result, and choose between B and C. A crew does not give you that. It gives you agents collaborating toward a goal, which is a different shape of problem.
Flows exist for that gap. This guide covers what the decorators do, how flow state works, how a flow calls a crew, and what the documentation says about running the result in production.
One version note. The flows documentation imports from crewai.flow.flow, while the quickstart shows a shorter crewai.flow path. Both appear in the current docs, so use whichever your installed version accepts rather than assuming a snippet is stale.
What is a CrewAI Flow?
It is a class where methods become steps, and decorators declare what triggers each one. You mark an entry point with @start, you mark a method that reacts to an earlier step with @listen, and the framework handles execution order and passes state along.
That file runs with no API key, which makes it a good way to feel the mechanics before adding model calls. The return value of a step is handed to whatever listens for it, so the plumbing between steps is ordinary Python values rather than prompt text.
The framework also ships a visualiser. Calling flow.plot("my_flow") generates an interactive HTML view of the graph, which is genuinely useful the moment a flow grows past four or five steps.
How does state work in a Flow?
Two ways, and the documentation shows both. Unstructured state is a dictionary you write to as self.state["key"]. Structured state means declaring a Pydantic model and typing the class as Flow[YourModel], after which self.state is that model with real attributes.
Use the structured form for anything you will keep. A typo in a dictionary key silently creates a new entry, while a typo on a Pydantic attribute fails where you made the mistake.
This is the piece a crew cannot give you. The @router decorator returns a string, and whichever @listen is waiting on that string runs next. Everything else stays unexecuted, so you are not paying for branches you did not take.
Note also that the routing above is plain string matching, not a model call. Cheap deterministic branching is often the right answer, and a flow lets you mix it with model judgement wherever judgement is genuinely needed.
For steps that should wait on more than one predecessor, the documentation provides two helpers. Wrapping conditions in or_ triggers the listener when any of them completes, while and_ waits until all of them have.
How do a Flow and a Crew fit together?
The flow owns the sequence, the crew does one chunk of work inside a step. A flow step is ordinary Python, so calling crew.kickoff() from inside it and storing the result on state is all there is to the integration.
The check_length step is doing something worth copying. Cheap deterministic validation between expensive model steps catches an empty or truncated result immediately, instead of letting it flow downstream and confuse three later prompts.
Once a flow exists, the question shifts from whether it runs to whether it survives contact with real traffic. That is where the documentation gets opinionated about which layer should be on the outside.
What does the documentation recommend for production?
Wrap crews and agents in a Flow. The production architecture page gives three reasons: state management across steps, precise control including loops and conditionals, and observability, since a defined structure is far easier to trace and debug than an open-ended collaboration.
- Orchestration layer: the Flow, owning sequence, branching and state.
- Execution layer: the crews and agents doing the actual work inside steps.
- Persistence layer: the @persist decorator saves flow state so a run can resume after a crash or a wait.
- Observability layer: traces and metrics. Running crewai login enables CrewAI Tracing, and the CLI exposes crewai traces enable, disable and status.
Persistence deserves a second look because it changes what your system can do. Once state is saved, the documentation shows resuming a run by passing its id to kickoff, and branching a new lineage from a previous run with restore_from_state_id, which produces a fresh state id rather than extending the original history.
For validation between steps, the documented mechanism is a task guardrail: a function that inspects an output and returns a tuple of a boolean and a value, so a bad result is caught at the boundary rather than accepted and passed on.
How do you run this without blocking a web request?
A crew run can take minutes, so calling kickoff on a synchronous web endpoint is a timeout waiting to happen. The crews documentation lists several entry points, and the distinction between them matters.
| Method | Kind | What it does |
|---|---|---|
| kickoff() | Synchronous | Runs the crew per the defined process |
| kickoff_for_each(inputs=[...]) | Synchronous | Runs the crew once per input in a list |
| akickoff(inputs={...}) | Native async | Async and awaitable through the execution chain |
| akickoff_for_each(inputs=[...]) | Native async | Native async run for each input in a list |
| kickoff_async(inputs={...}) | Thread-based | Wraps the synchronous run in asyncio.to_thread |
| kickoff_for_each_async(inputs=[...]) | Thread-based | Thread-based async for each input |
Inside a web framework that already runs an event loop, await the async entry point rather than starting a second loop of your own. The common production shape is to accept the request, start the run in the background, and give the caller a job id to poll.
So which one should you build?
Build a Crew when one team of agents produces one deliverable and there is nothing to decide along the way. Build a Flow the moment you need branching, retries, persistence, or a mix of crews and plain function calls in one pipeline.
| What you need | Crew alone | Flow |
|---|---|---|
| One goal, one deliverable | Yes | Unnecessary overhead |
| Branch on an intermediate result | No | Yes, via @router |
| State shared across steps | Limited to task context | First class, and typed with Pydantic |
| Resume after a crash | No | Yes, with @persist and a state id |
| Mix model calls with plain Python | Awkward | Natural, steps are just methods |
| A diagram of what runs | Read the code | flow.plot() renders the graph |
The most common mistake is reaching for a Flow too early. If your task is a single crew with no branching, a flow adds a class and a set of decorators around code that was already correct, and you carry that structure forever.
It is fair to name what you take on when you build this way. State, branching, persistence, retries and tracing are real infrastructure, and they belong to you now. If the pipeline is the product you are shipping, that ownership is the right call and this is a capable set of primitives for it.
Some readers arrive at this page from the other direction. They wanted a report every Monday, or tickets triaged before they open their laptop, and building an orchestration layer was simply the route they found first. That is the case Sistava is built for: you hire an AI Employee, describe the job in plain English, and the branching, memory and follow-through come with the hire instead of being a graph you maintain.
If you are building the pipeline yourself, the sequence that works is boring and reliable. Get one crew right, wrap it in a flow only when you need to branch, add persistence when a failed run actually costs you something, and turn tracing on before you need it rather than after.
Frequently asked questions
FAQ
What is the difference between a Crew and a Flow in CrewAI?
A Crew is a team of agents collaborating toward a goal, optimised for autonomy. A Flow is an event-driven Python class where methods are steps, decorated with @start, @listen and @router, and it can contain crews, single model calls and ordinary functions. A crew decides how to reach a goal, a flow decides what runs next and holds the state between steps.
Do I need a Flow if I already have a working Crew?
Not if the crew is a single pipeline with no branching. Add a Flow when you need to inspect an intermediate result and choose a path, when you want typed state shared across steps, when you need to resume after a failure, or when you want deterministic Python between model calls.
What do @start, @listen and @router do?
@start marks an entry point that runs when the flow kicks off. @listen marks a method that runs in response to an earlier step, receiving that step's return value. @router returns a string, and whichever @listen is waiting on that string runs next, which is how branching works. There are also or_ and and_ helpers for listening to several conditions at once.
How does state work in a CrewAI Flow?
Either unstructured, as a dictionary you access with self.state["key"], or structured, by declaring a Pydantic model and typing the class as Flow[YourModel] so self.state has real attributes. Prefer the structured form for anything you keep, because a mistyped attribute fails immediately while a mistyped dictionary key silently creates a new entry.
Should I use Flows in production?
The production architecture page recommends wrapping crews and agents in a Flow, citing state management, precise control including loops and conditionals, and observability. It also describes a @persist decorator for saving state, resuming a run by passing its id to kickoff, and branching a new lineage with restore_from_state_id.
How do I run a crew without blocking my web server?
Use one of the documented async entry points instead of the plain synchronous kickoff. The crews documentation lists akickoff and akickoff_for_each as native async, plus kickoff_async and kickoff_for_each_async which wrap the synchronous run in asyncio.to_thread. Inside a framework that already runs an event loop, await these rather than starting another loop yourself.
Flows and Crews answer different halves of the same question. The crew answers who does the work, the flow answers what happens next and what we remember. Get the crew right first, then reach for the flow when the second half starts mattering.