LangGraph vs CrewAI vs Smolagents: Picking an Agent Framework
State machines, role-play crews, and code agents: three philosophies, honestly compared, plus where the model vendors' own agent SDKs fit.
LangGraph, CrewAI, and Smolagents represent three distinct philosophies for building LLM (large language model) agents. LangGraph treats agents as nodes in an explicit state machine with durable checkpoints. CrewAI models a digital team of role-based agents and tasks. Smolagents lets an LLM generate and execute Python code step by step. The right choice depends on whether you need tight control, collaborative role play, or dynamic code execution. The code in this article was run against the current releases as of October 2026: LangGraph 1.2, CrewAI 1.15 and smolagents 1.26. The model vendors also ship agent SDKs of their own; a section near the end shows where they fit.
The framework you pick changes not just your code but the kind of failure you will debug. LangGraph fails with a precise graph trace. CrewAI fails with a confused crew. Smolagents fails with a Python traceback from code the model wrote. Understanding these failure modes is the key to picking wisely.
Think of LangGraph as a train network. You lay down tracks (nodes and edges) and the train (the runtime) follows them. CrewAI is more like a project manager assigning tasks to a team. Smolagents is a coder who writes a script on the fly. Each mental model maps directly onto the internal mechanics.
How does LangGraph model agent workflows?
LangGraph models agent workflows as an explicit state machine. Every node is a function that takes a shared state and returns a partial update. The runtime merges those updates into the state and, if you attach a checkpointer, saves a checkpoint after each step. This design gives you a typed, resumable, and auditable execution log.
A StateGraph builder defines nodes, a state schema, and directed edges. Conditional edges let you branch based on state. Once compiled, the graph can be invoked with an initial state and a configuration that includes a thread ID. The runtime loads the last checkpoint for that thread, runs the next node, and persists the new state. The cycle repeats until the graph reaches its end. This is the core loop of every LangGraph agent, as the LangGraph overview describes. A run that takes too many steps stops with an error: the graph API docs put the default recursion limit at 1,000 steps since version 1.0.6.
The state schema is first class. Each key can have a reducer, a rule for combining a node's update with the value already in the state. A messages key might use an append reducer, so several nodes can add to a conversation without overwriting each other. A key with no reducer simply takes the newest value (last write wins). The runtime applies these rules the same way every time, which makes debugging far easier than in frameworks where state is implicit.
The node returns only the new message; the add reducer appends it to the list. Call invoke again with the same thread ID and the conversation continues from the saved checkpoint.
The agent's behavior is entirely encoded in the graph topology and the node functions. There is no hidden conversational control loop. That makes LangGraph unusually explicit, which is why it suits workflows that must be audited.
That explicitness comes with a cost. You must model every possible path. The next section shows how CrewAI takes a different approach, trading graph rigidity for role-based collaboration.
How does CrewAI orchestrate multi-agent teams?
CrewAI orchestrates agents by assigning them roles and tasks. A Crew is a group of Agents with defined goals, tools, and a sequence of Tasks. The runtime iterates through tasks, routes each to its agent, and triggers model calls and tool executions. Communication happens through task outputs and agent memory, not a typed shared state object.
The core abstractions are Agents, Tasks, Crews, and Flows. An Agent has a role, a goal and a backstory (all three are required, as the CrewAI agents docs say), plus a model and a set of tools. A Task has a description and an expected output (both required, per the tasks docs), an assigned agent, and optionally a context list of earlier tasks whose outputs it receives. When you run a Crew, the runtime manages the loop. For each task, it calls the agent's model, executes any tools the agent requests, and feeds results back into the conversation. The developer declares what each agent should do. The runtime handles the details.
The agents' roles and tasks drive behavior. There is no explicit graph topology. The runtime manages how the researcher's output reaches the writer. This style is intuitive for teams that already organize work in terms of roles and tasks.
Flows sit on top of crews. A Flow coordinates several crews and plain Python steps with event-driven control (@start and @listen decorators), shared state, persistence (@persist) and human feedback steps (@human_feedback). For a single job, a Crew is enough. For a process with several stages, a Flow gives you more control.
CrewAI's strength is simplicity: you describe a team, not a state machine. It does not depend on LangChain. But that simplicity means you give up the fine-grained state control that LangGraph provides. The next framework, Smolagents, goes even further in the direction of minimal abstraction.
How does Smolagents execute agent actions?
Smolagents executes actions by having an LLM generate Python code. The core component is the CodeAgent. In each step, the agent builds a prompt that includes the task and the memory of earlier steps. The model returns a Python snippet. The framework executes that snippet, by default in its own restricted Python interpreter. The outputs are appended to memory. The loop repeats until the agent calls final_answer or reaches its step limit, as the smolagents guided tour shows.
This code-as-action approach is fundamentally different. A single step can define variables, loop over results, and call multiple tools. The agent is not limited to emitting one function call per turn. It can write a small program that orchestrates complex logic. That flexibility is powerful for exploratory tasks. It also introduces new failure modes. Syntax errors, runtime exceptions, and invalid tool calls are common. The framework catches these errors and feeds them back to the model, which can then fix its own code in the next step.
Smolagents also offers a ToolCallingAgent. This variant uses JSON (JavaScript Object Notation) tool calls, like the native tool calling of the major model APIs. The model emits a JSON object naming the tool to invoke and its arguments. The framework executes the tool and returns the result. The control flow still emerges from the sequence of calls. There is no predefined graph.
State management is intentionally minimal. Each agent keeps an in-memory log of the steps in its current run. There is no built-in persistence across sessions. If you need to resume a run days later, you must save that log yourself. This makes Smolagents suitable for self-contained runs that do not need to survive a process restart.
Tools are plain functions marked with @tool, with type hints and a docstring that describes each argument; the framework turns those into the tool description the model reads. The executor_type chooses where the generated code runs: "local" (the default restricted interpreter), or a sandbox with "docker", "e2b", "modal" or "blaxel". Behind this small API (application programming interface), the agent builds prompts, generates Python, and executes it. You can inspect every step in agent.memory. That transparency is valuable for debugging. But the lack of persistent state means Smolagents is not a drop-in replacement for production workflows that require durability.
How does LangGraph persist state?
Once you attach a checkpointer, LangGraph saves a checkpoint after each step, which enables resumability and auditability. If a node fails, the runtime can reload the last checkpoint and continue from that point. If a human interrupt pauses the graph, the state is saved so the run can resume later with a new input. The persistence docs list the uses: conversation continuity, human-in-the-loop workflows, time travel and fault tolerance. A graph compiled without a checkpointer saves nothing.
The checkpointer is a pluggable layer. In development, InMemorySaver keeps state in memory. In production, a database checkpointer such as PostgresSaver or AsyncPostgresSaver (from the separate langgraph-checkpoint-postgres package) writes every checkpoint to Postgres. Every state update is recorded. You can look back through a thread's checkpoints, replay from one, or fork a run from any checkpoint to explore an alternative path. This is the foundation for LangGraph's time travel debugging.
The interrupt function builds on this persistence. When a node calls interrupt(value), the runtime pauses the graph, saves state, and returns the value to the caller (under __interrupt__ in the result). The caller can show it to a person. When the person responds, the caller invokes the graph again with the same thread ID and a Command(resume=...) carrying the answer. As the interrupts docs explain, the runtime then restarts the interrupted node from the beginning, and this time interrupt returns the person's answer. The graph waits indefinitely, and because the state lives in the checkpointer, the answer can arrive days later from a different process.
LangChain's human-in-the-loop middleware adds tool-level review to agents built with its create_agent. It checks each proposed tool call against a policy and can pause for a decision: approve, edit the arguments, or reject. Because it uses the same interrupt mechanism, it needs a checkpointer and inherits the same persistence and resumption.
This granular persistence is a double-edged sword. It gives you precise control and recovery. It also adds overhead: every step writes to the database. At high throughput, the checkpointer becomes a cost you measure and tune. The next section explains how CrewAI takes a lighter approach to checkpointing.
Why does CrewAI checkpoint on events?
CrewAI checkpoints on configurable events to balance recovery granularity and overhead. You turn it on with checkpoint=True on a Crew, Flow or Agent, or pass a CheckpointConfig to choose the events, the storage and how many checkpoints to keep. The default event, task_completed, writes one checkpoint per finished task. For finer recovery, you can add llm_call_completed, which captures state after every model call and writes far more checkpoints, as the checkpointing docs note.
This event-driven model is less rigid than LangGraph's step-by-step persistence. You can start with coarse checkpoints and increase granularity only for critical workflows. A checkpoint stores the crew's state: configuration, memory, task progress, intermediate outputs, and event history. Restoring a checkpoint skips the tasks that already completed. Forking a checkpoint (Crew.fork) starts a new lineage from the same state, so the new run does not overwrite the original.
The storage layer is pluggable. JsonProvider writes one file per checkpoint, which is convenient for manual inspection. SqliteProvider writes to a single SQLite database, which suits frequent checkpointing. For production, keep that file on durable storage.
This approach gives CrewAI good recovery and branching without forcing you to model the entire workflow as a state machine. It fits the role-based, task-oriented mental model. But it does not offer the same fine-grained state introspection that LangGraph provides. You cannot easily inspect the exact state between two model calls unless you configure checkpointing for that event.
Why does Smolagents rely on sandboxing?
Smolagents relies on sandboxing because its agents execute code the model wrote. If that code ran in a plain Python interpreter on your machine, a buggy or malicious snippet could read files, make network calls, or worse. The model might be fooled by a prompt injection on a web page it reads, or simply make a mistake.
So by default smolagents runs the code in its own restricted interpreter, the LocalPythonExecutor, which the secure code execution guide describes. It executes the code operation by operation, refuses imports that are not on an allowlist (you extend it with additional_authorized_imports), blocks access to dangerous submodules, and caps the number of operations to stop infinite loops. That blocks many mistakes, but it is not a true security boundary: the guide itself warns that no local Python sandbox can ever be completely secure. For untrusted tasks, or models you trust less, you switch executor_type to Docker, E2B, Modal or Blaxel, so the code runs in an isolated container or remote machine and only its output comes back.
This is a different safety model from LangGraph and CrewAI. LangGraph constrains actions through the graph topology: you know exactly which nodes can call which tools. CrewAI constrains actions through the tool definitions: agents can only invoke functions you have provided. Smolagents gives the model far more freedom, limits it with a restricted interpreter, and relies on a sandbox to contain what slips through. That tradeoff gives you maximum flexibility. It also means you manage the sandbox infrastructure.
How do these frameworks handle human in the loop?
LangGraph provides the most deeply integrated human in the loop. The interrupt function can pause any node, save state, and wait for a human response, for as long as it takes. The middleware adds tool-level review. The entire mechanism is built on the persistence layer. Resuming picks up exactly where it stopped.
In CrewAI, you set human_input=True on a task, and a person reviews that task's final answer before the crew moves on. In Flows, a @human_feedback step pauses for a person's input. By default it waits for input at the console; with a feedback provider of your own, the flow saves its state, returns a pending result, and resumes later from its ID when the answer arrives, as CrewAI's guide to human feedback in flows shows. Either way, you pause at task or step boundaries, not in the middle of an agent's reasoning.
Smolagents has no pause-and-resume primitive. You can run your own code between steps with step callbacks, for example to show a plan to a person and wait for a reply. But the run lives in memory, so a pause that must outlast the process is yours to build.
How do they compare on tool calling?
LangGraph tools are typically defined with LangChain's tool interfaces. Nodes or agents within the graph call them, often through a prebuilt tool node. The tool results become part of the state. The graph topology determines when and how tools are invoked.
CrewAI tools are callable Python functions or tool classes. You attach them to agents. When an agent decides to use a tool, the runtime executes it and feeds the result back into the conversation, as the CrewAI tools docs describe.
Smolagents tools are Python functions called from generated code. In the CodeAgent, the model writes Python that calls search("query"). The framework executes that code. In the ToolCallingAgent, the model emits JSON specifying the tool name and arguments. The framework parses the JSON and calls the function. The smolagents tools tutorial covers both.
The key difference is where the tool invocation logic lives. In LangGraph, it lives in the graph. In CrewAI, it emerges from the agent's conversation. In Smolagents, it is part of the generated code. That affects debuggability. LangGraph gives you a static map of all possible tool calls. CrewAI gives you a log of which agent called which tool. Smolagents gives you the actual Python code that made the call.
How do they compare on observability and debugging?
LangGraph streams state updates, model tokens, and interrupts as they happen. You can watch the graph evolve step by step. With a checkpointer, you can look back through any past run, replay it, or fork it from a checkpoint and explore an alternative path. This is the most mature observability story of the three.
CrewAI emits events on an event bus that you can listen to: task, agent and tool events, and streamed model output as LLMStreamChunkEvent, all listed in the event listener docs. The checkpoints give you snapshots you can inspect and restore. But you cannot step through a run with the same per-step fidelity as LangGraph.
Smolagents gives you the agent's memory log. You can inspect every step, every code snippet, and every output. You can stream steps as they happen with agent.run(task, stream=True), and print a past run again with agent.replay(). An optional telemetry extra sends traces over OpenTelemetry. But the log lives in memory and is lost when the run ends unless you save it. The transparency of the code itself is a powerful debugging aid: you see exactly what the model generated and what happened when it ran.
The choice here reflects the frameworks' philosophies. LangGraph optimizes for controlled, replayable workflows. CrewAI optimizes for fast, collaborative runs. Smolagents optimizes for flexible, inspectable code.
| Feature | LangGraph | CrewAI | Smolagents |
|---|---|---|---|
| Core paradigm | State machine graph | Role-based crew | Code-generating agent |
| State model | Typed shared state with reducers | Implicit state in memory and task outputs | In-memory step log per run |
| Persistence | Checkpoint after every step, once you attach a checkpointer | Event-driven checkpoints (task or model call) | None across runs |
| Human in the loop | interrupt, plus LangChain's middleware for tool review | human_input on tasks; @human_feedback steps in Flows | Your own code in step callbacks |
| Safety | Architectural constraints via the graph | Controlled tool definitions | Restricted interpreter by default; Docker, E2B, Modal or Blaxel sandboxes |
| Best for | Auditable, long-running workflows with pauses | Fast, collaborative, role-based automation | Exploratory, code-heavy, flexible tasks |
Each framework's core loop, side by side:
What about the vendor SDKs?
The three frameworks above are independent projects. The model vendors also ship agent SDKs of their own, and in October 2026 these are often the first thing a team tries. Each gives you an agent loop with tools; they differ in what they add around it.
| SDK | Core abstraction | Best fit |
|---|---|---|
| OpenAI Agents SDK | Agents with instructions and tools, handoffs between agents, guardrails on inputs and outputs, sessions and built-in tracing; works with non-OpenAI models through adapters | Teams that want a small set of primitives with tracing included |
| Claude Agent SDK | The same tools, agent loop and context management that power Claude Code, as a Python or TypeScript library: built-in file, command and web tools, hooks, subagents, MCP, permissions and sessions (it replaced the earlier Claude Code SDK packages) | Agents that work on files and run commands, such as coding or operations agents |
| Google ADK | Google's open-source Agent Development Kit, in Python, TypeScript, Go, Java and Kotlin; works with almost any model, with Gemini built in; sequential, parallel and loop workflows for several agents | Teams on Google Cloud, or teams that want workflow agents outside Python |
| Microsoft Agent Framework | The direct successor to Semantic Kernel and AutoGen (version 1.0 in April 2026): agents plus graph-based workflows with state management for long-running and human-in-the-loop work, in .NET and Python | .NET and Azure teams, and anyone moving off AutoGen |
| Pydantic AI | A typed agent loop for Python where switching models is a one-string change, with outputs validated by Pydantic | Python teams that want typed inputs and outputs |
A note on AutoGen, which many older tutorials use: it is in maintenance mode. Its README says contributions are limited to bug fixes, security patches and documentation, and it points new users to Microsoft Agent Framework. If you are starting fresh, do not start there. For Claude Code itself, Headless Claude Code: CI, Scripts, and Automation shows the agent loop driven from scripts.
Worked example: one task, three frameworks
Here is a job that separates the three. Mailmind, an inbox assistant, must read new email, sort it, and draft replies. Every reply needs a person's approval before it is sent, and approval can take hours.
In LangGraph, this is a graph with four nodes: classify, draft, approve, send. The approve node calls interrupt with the draft. The run pauses, its state goes to a Postgres checkpointer, and the process can even shut down. When the person approves hours later, any worker resumes the thread with Command(resume=...), and the send node runs. Every step is in the checkpoint history, so you can show exactly what was drafted and who approved it. This is LangGraph's home ground.
In CrewAI, a triage agent and a drafting agent work as two tasks, the second taking the first's output as context. A plain human_input=True would wait at a console, which does not suit a wait of hours. So you wrap the crew in a Flow and add a @human_feedback step with a feedback provider that notifies the approver and pauses; the flow's state is saved, and it resumes by ID when the answer arrives. It works, with a little more wiring and coarser checkpoints than LangGraph.
In Smolagents, a CodeAgent can triage and draft in one run, writing a loop over the emails. But it has no way to pause for hours. You would end the run after drafting, store the drafts yourself, and send each one in a separate step after approval. Smolagents is a fine engine for the drafting step, and the wrong tool for the waiting.
So for this job: LangGraph first, CrewAI Flows second, and Smolagents only inside one step. Change the job (a one-off research task with no approvals) and the ranking can flip.
Frequently Asked Questions
Q: Which framework is best for production deployments that require audit trails?
LangGraph, with a database checkpointer attached. Its step-by-step persistence and explicit state machine give you a replayable record of every decision. You can trace exactly which node produced which state update. CrewAI's event-driven checkpoints are lighter but less granular. Smolagents has no built-in persistence.
Q: Can I use LangGraph without LangChain?
Mostly. LangGraph does not need the full langchain package, but it installs langchain-core underneath. You can write nodes that call any model client directly, and the checkpointer and streaming work without LangChain. The human-in-the-loop middleware belongs to LangChain's agent layer, so using it brings LangChain in.
Q: Does CrewAI support streaming of agent outputs?
Yes. You can enable streaming on the crew, or listen for LLMStreamChunkEvent on the event bus and forward the chunks to a client. It is less granular than LangGraph's streaming of state updates, but it covers common use cases.
Q: How do I debug a Smolagents run that produces a Python error?
The agent's memory log contains the full code snippet and the error traceback. The framework feeds errors back to the model automatically, so the next step often fixes them. You can also inspect the log after the run, print the whole run again with agent.replay(), and save the steps to a file if you need them later.
Q: What are the hidden costs of each framework?
LangGraph's persistence can become a bottleneck at high throughput. You will need a fast database and may need to tune how often you checkpoint. CrewAI's simplicity means you may outgrow it for complex, cyclical workflows. Smolagents' sandboxing adds infrastructure overhead. The model's code generation can also be unpredictable, leading to more retries and higher token costs.
Test yourself
A bank asks you to build a refund agent. It reads a customer's complaint, checks the account, and proposes a refund. Any refund over a set amount must wait for a compliance officer's approval, which can take two days. Auditors want a record of every step and every decision. Which framework would you pick, and how would you build the approval?
Answer: LangGraph fits best. Model the workflow as a graph: read the complaint, check the account, propose the refund, then an approval node for refunds over the threshold, then pay. Compile it with a durable database checkpointer such as PostgresSaver, so every step is saved and the history doubles as an audit trail. The approval node calls interrupt with the exact refund details. The run pauses, its state stays in Postgres, and the process can stop; when the officer decides two days later, any worker resumes the thread with Command(resume=...). Keep the payment itself in a plain function the graph calls only after approval, not in a model's hands. CrewAI Flows could also pause with a feedback provider, but with coarser checkpoints. Smolagents is the wrong fit: no persistence across runs, and model-written code is harder to audit.
If you want this kind of breakdown every week, how real systems actually work under the hood, subscribe to Internals Decoded at internalsdecoded.com.
Sources
- LangGraph overview
- LangGraph persistence
- LangGraph interrupts
- LangGraph streaming
- CrewAI flows
- CrewAI checkpointing
- CrewAI tools
- Smolagents secure code execution
- Smolagents tools
- graph API docs
- CrewAI agents docs
- docs.crewai.com · Tasks
- smolagents guided tour
- human-in-the-loop middleware
- human feedback in flows
- event listener docs
- OpenAI Agents SDK
- Claude Agent SDK
- adk.dev
- Microsoft Agent Framework
- pydantic.dev · Overview
- github.com · Autogen