chapter six

6 Building complex flows

 

This chapter covers

  • Choosing Flows over a single crew
  • Typed Pydantic state and the @start / @listen / @router primitives
  • Fan-out, fan-in, and retry-via-router
  • @persist for crash-safe resume
  • Real Gmail and Slack providers behind one switch

If you have ever worked a support inbox on a busy Monday morning, you know what it looks like: fifty unread tickets spread across billing disputes, broken dashboards, feature requests, outright spam, and the occasional threatening email from a customer who has been waiting months for a response.

A single crew with one classifier agent and one responder might hold up for a week, but the first unusual edge case will break it. A real system needs a control plane—something that can gather context, route by category, retry when an upstream service returns an error, escalate when things stay broken, and resume cleanly after a crash.

That control plane is what CrewAI calls a Flow.

A Flow does not replace crews; it orchestrates them. A single Flow can call a standalone agent, a full crew, a plain Python function, or an external HTTP service, whichever fits the step best. Most production agentic applications end up with this layered shape. Even non-CrewAI systems like Claude Code are built on a similar workflow underneath, with conversation history, compaction events, tool retries, and error handling. The primitives map directly onto what we are about to build.

6.1 When to choose Flows

6.2 Setting up the Gmail support flow

6.2.1 The support service

6.2.2 Bootstrapping the Flow project

6.3 Designing the flow state

6.4 Wiring the flow lifecycle

6.4.1 The @start step: intake

6.4.2 Fanning out with parallel @listen

6.4.3 Fanning in with @listen(and_(...))

6.4.4 Running the triage crew

6.5 Routing requests with @router

6.5.1 The first router: classification

6.5.2 The five specialist branches

6.6 Error recovery with retry-via-router

6.6.1 Why no try/except in the happy path

6.6.2 The retry-via-router pattern

6.6.3 Reproducing failures with simulate=fail

6.6.4 Escalation as a terminal route

6.6.5 Retry-via-router vs. task guardrails

6.7 Running in production

6.7.1 @persist and resume_flow

6.7.2 Event listeners and JSONL traces

6.7.3 The fake/real provider pattern

6.7.4 Setting up real Gmail

6.7.5 Setting up real Slack

6.7.6 Before going live

6.8 Summary