# LangGraph vs CrewAI: Which Agent Framework to Use

By Agent Signal · Published 2026-10-15 · Updated 2026-10-15


If you're choosing between LangGraph and CrewAI, you aren't really picking a "better" framework. You're picking where the control flow lives. LangGraph has you draw the graph yourself. CrewAI starts from a team of role-based agents and gives you Flows when you need to pin the steps down. This guide compares the two on the things that decide production fit: how execution is ordered, how state is kept, how a human approves a step, and how memory works. Every claim links to the official docs.

## TL;DR

| You need… | Lean toward |
|---|---|
| Explicit, inspectable control flow you define node by node | LangGraph |
| A quick "team of specialists" with roles and tasks | CrewAI (Crews) |
| Event-driven pipelines that chain crews and plain code | CrewAI (Flows) |
| Pause-for-approval that survives restarts | Both can do it. Read the persistence and HITL sections |
| Long-term memory that works out of the box | CrewAI's `Memory` is more batteries-included; LangGraph gives you a store primitive |

## 1. Execution model: who decides what runs next?

**LangGraph.** You build a `StateGraph`, add nodes (plain functions that read and return state), and connect them with edges and conditional edges. The [LangGraph interrupts docs](https://docs.langchain.com/oss/python/langgraph/interrupts) show the full pattern: `builder.add_node(...)`, `builder.add_edge(START, "agent")`, `builder.add_conditional_edges("agent", should_continue, [...])`, then `builder.compile(checkpointer=...)`. The classic agent loop is literally a cycle in your graph: an `agent` node calls the model, a conditional edge routes to a `tool_node` when the model asked for tools, and an edge leads back to `agent`. Nothing runs that you didn't wire up.

**CrewAI Crews.** A crew is a set of agents plus a list of tasks, and you choose a **process**. The [CrewAI Processes docs](https://docs.crewai.com/en/concepts/processes) define two:

- **Sequential:** tasks run in list order, and each task's output is context for the next. You can override that with the `context` parameter on `Task`.
- **Hierarchical:** you must supply a `manager_llm` or a custom `manager_agent`. Tasks aren't pre-assigned. The manager "allocates tasks to agents based on their capabilities, reviews outputs, and assesses task completion."

So with hierarchical crews, an LLM manager decides the order at runtime. That's handy for open-ended work, but it's less predictable than a graph you wrote yourself.

**CrewAI Flows.** For deterministic orchestration, CrewAI has [Flows](https://docs.crewai.com/en/concepts/flows): "structured, event-driven workflows" built from decorated methods.

- `@start()` marks entry points.
- `@listen(method)` runs a method when another one emits output.
- `or_` and `and_` combine triggers.
- `@router()` returns a label (typed with `Literal[...]`) that picks the next branch.

The docs say Flows support "conditional logic, loops, and branching" and can call whole Crews, single Agents, or bare LLM calls from any step.

**Takeaway:** a LangGraph graph and a CrewAI Flow are much closer than their marketing suggests. Both give you explicit, code-defined control flow. The real difference is that CrewAI also offers the higher-level crew abstraction (roles plus processes), while LangGraph starts low-level.

## 2. State

**LangGraph** state is a typed schema (the docs use `TypedDict` or Pydantic) that every node reads and partially updates. You can attach reducers to fields. The [interrupts docs](https://docs.langchain.com/oss/python/langgraph/interrupts) annotate a list with `operator.add` so parallel nodes append to it instead of overwriting it.

**CrewAI Flows** give you both options. Unstructured state is a dict on `self.state`. Structured state is a Pydantic `BaseModel` passed as `Flow[MyState]`. Either way, each state automatically gets a UUID `id` ([Flows docs](https://docs.crewai.com/en/concepts/flows)).

Both get you typed, validated state if you want it. Use it. Untyped dict state is the first thing to rot in a growing agent.

## 3. Persistence and resuming

This is where production pain usually shows up.

**LangGraph** has two persistence layers ([Persistence docs](https://docs.langchain.com/oss/python/langgraph/persistence)):

- **Checkpointers** save a thread's graph state as checkpoints. They're used for "conversation continuity, human-in-the-loop workflows, time travel, and fault tolerance," and you pick the thread with a `thread_id` in the config.
- **Stores** hold application-defined key-value data that lives across threads.

The docs give clear warnings:

- `InMemorySaver`/`MemorySaver` "store checkpoints in RAM. When the process restarts, all checkpoints are lost." Use `PostgresSaver` in production. `SqliteSaver` is for local development.
- With `PostgresSaver`, keep `thread_id` values under 255 characters.
- Checkpoints "accumulate" over long conversations. Prune them or set a retention policy.

**CrewAI** puts persistence on Flows with the `@persist` decorator, at class or method level ([Flows docs](https://docs.crewai.com/en/concepts/flows)).

- **Default backend:** `SQLiteFlowPersistence`. You can write your own `FlowPersistence` backend.
- **Resume:** `kickoff(inputs={"id": <uuid>})` loads the latest snapshot and keeps writing under the same ID.
- **Fork:** `kickoff(restore_from_state_id=<uuid>)` hydrates a new run with a fresh `state.id` and leaves the original history alone.
- **Watch out:** if the ID you pass doesn't match any persisted state, kickoff "falls back silently." Alert on that yourself, or a typo quietly starts a fresh run.

## 4. Human-in-the-loop

**LangGraph:** you call `interrupt(payload)` inside any node ([Interrupts docs](https://docs.langchain.com/oss/python/langgraph/interrupts)).

- **What it needs:** a checkpointer and a `thread_id`.
- **How it pauses:** the graph saves state and "waits indefinitely." You resume by invoking again with `Command(resume=value)` on the same `thread_id`, and that value becomes the return value of `interrupt()`.
- **Typed answers:** `langgraph>=1.2.12` adds a `response_schema` that validates the resume value.

Three rules from the docs that will bite you if you skip them:

1. **The node re-runs from the top on resume.** Code before `interrupt()` runs again, so side effects before it must be idempotent. Put them after the interrupt or in a separate node.
2. **Don't wrap `interrupt()` in a bare `try/except`.** It works by raising a special exception, and catching it breaks the pause.
3. **Keep interrupt order deterministic.** Resume values are matched to interrupts by index, so skipping one conditionally or looping over changing data misaligns them. For validation loops, call `interrupt()` once per node and loop with a conditional edge, not `while True`.

**CrewAI:** the `@human_feedback` decorator (CrewAI ≥ 1.8.0) pauses a Flow method to collect feedback ([Flows docs](https://docs.crewai.com/en/concepts/flows)).

- **Routing on feedback:** with `emit=["approved", "rejected", ...]`, an LLM reads the human's free-text feedback, "collapses" it into one of those outcomes, and triggers the matching `@listen`.
- **Default outcome:** you can set a `default_outcome`.
- **Feedback history:** `self.human_feedback_history` keeps every answer.
- **Async channels:** the docs point to custom providers (Slack, webhooks) for non-blocking feedback.

**Design note:** CrewAI's LLM step that turns feedback into an outcome is convenient, but it adds a model call to your approval path. For anything irreversible (payments, deletes, outbound email), consider a strict boolean outcome rather than free text parsed by an LLM. LangGraph's typed `response_schema` gets you that directly.

## 5. Memory

**LangGraph** keeps short-term memory in thread state through checkpointers and long-term memory in stores ([Persistence docs](https://docs.langchain.com/oss/python/langgraph/persistence)). What you remember, when, and how you retrieve it is up to you.

**CrewAI** ships a unified `Memory` class ([Memory docs](https://docs.crewai.com/en/concepts/memory)) that replaces the older short-term, long-term, entity, and external memory types.

- **On save:** an LLM infers the scope, categories, and importance.
- **On recall:** results are ranked by a weighted score of semantic similarity, recency (exponential decay), and importance.
- **In a crew:** `memory=True` means "after each task, the crew automatically extracts discrete facts from the task output and stores them," and recalls relevant context before each task.
- **Defaults:**
  - embeddings: OpenAI `text-embedding-3-large`
  - analysis LLM: `gpt-4o-mini`
  - storage: LanceDB under `./.crewai/memory`
- **Privacy:** the docs warn that memory content is sent to the configured LLM for analysis. Use a local model if that's a problem.

So CrewAI gives you a lot for free, including hidden LLM calls and an OpenAI default. LangGraph gives you primitives and leaves the policy to you. We cover this in depth in our [AI agent memory guide](/p/ai-agent-memory).

## 6. Observability and cost

- **CrewAI:** Flows expose `flow.usage_metrics`, which adds up tokens across every LLM call in a run, including nested crews and bare `LLM.call(...)`. The docs warn that `flow.kickoff().token_usage` only covers the last crew, so it's the wrong number for cost tracking ([Flows docs](https://docs.crewai.com/en/concepts/flows)). `flow.plot()` renders an interactive HTML graph of the flow.
- **LangGraph:** the docs point to LangSmith for inspecting traces and debugging interrupts, and to `interrupt_before` and `interrupt_after` as static breakpoints you can use for debugging ([Interrupts docs](https://docs.langchain.com/oss/python/langgraph/interrupts)).

## How to choose

Pick **LangGraph** if:

- you want every transition visible in code and testable node by node
- your approval steps need typed, validated input
- you're happy to own the memory policy

Pick **CrewAI** if:

- your problem naturally splits into roles and tasks
- you want working long-term memory with one flag
- you'd rather write event-driven Python methods than graph wiring

Use Flows as soon as you need determinism. Hierarchical crews let an LLM manager decide the order.

Either way, the production checklist is the same:

- a durable persistence backend (not in-memory)
- idempotent side effects around pause points
- explicit run IDs you can alert on
- token accounting at the workflow level, not just the last step

---

*Agent Signal is a weekly newsletter for people building AI agents. Subscribe to get the next deep dive.*
