IDInternals Decoded
All articles
PlaybooksIntermediate15 min readJun 2026

LangGraph vs CrewAI vs Smolagents: Picking an Agent Framework

State machines, role-play crews, and code agents: three philosophies, honestly compared.

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 deterministic control, collaborative role play, or dynamic code execution.

But here is the twist. The framework you pick changes not just your code but the very nature of failure you will debug. LangGraph fails with a precise graph trace. CrewAI fails with a confused crew. Smolagents fails with a Python traceback inside a sandbox. 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 applies reducers to merge those updates and writes 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, evaluates the next node, runs it, and persists the new state. The cycle repeats until a terminal node is reached. This is the core loop of every LangGraph agent. LangGraph docs

The state schema is first class. You annotate keys with reducers. A messages key might use an append reducer so multiple nodes can add to a conversation. A status key might use a last-write-wins reducer. The runtime merges contributions deterministically. This explicitness makes debugging far easier than in conversational agent frameworks.

from langgraph.graph import StateGraph
from langgraph.checkpoint.memory import MemorySaver

class MyState(dict):
    pass

def llm_node(state: MyState) -> MyState:
    response = call_llm(state["messages"])
    return {"messages": state["messages"] + [response]}

graph = StateGraph(MyState)
graph.add_node("llm", llm_node)
graph.set_entry_point("llm")
graph.set_finish_point("llm")

compiled = graph.compile(checkpointer=MemorySaver())
result = compiled.invoke({"messages": ["Hello"]}, config={"thread_id": "t1"})

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. It is why teams that need regulated or auditable workflows often choose it.

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 them to the appropriate agent, and triggers LLM calls and tool executions. Communication happens through shared task outputs and agent memory, not a typed shared state object. CrewAI docs

The core abstractions are Agents, Tasks, Crews, and Flows. An Agent has a role description, a model, and a set of tools. A Task encapsulates an objective and an assigned agent. When you execute a Crew, the runtime manages the loop. For each task, it calls the agent’s LLM, 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.

Checkpointing in CrewAI is event driven. You configure which events trigger a snapshot. The default event task_completed saves a checkpoint after each task. For finer granularity, you can use llm_call_completed. A checkpoint captures the full state of the crew: configuration, memory, task progress, and event history. Restoring a checkpoint reconstructs the crew exactly as it was, skipping already completed work. You can also fork a checkpoint to explore alternative branches. CrewAI checkpointing docs

Flows add enterprise grade orchestration. They let you coordinate multiple Crews and Agents with event driven control, tracing, and a unified control plane. For simple use cases, a Crew is enough. For production deployments, Flows provide the observability and governance you need.

from crewai import Agent, Task, Crew

researcher = Agent(
    role="Researcher",
    goal="Find recent papers on LangGraph",
    tools=[web_search_tool]
)
writer = Agent(
    role="Writer",
    goal="Summarize findings for engineers",
    tools=[]
)

task1 = Task(description="Research LangGraph production deployments", agent=researcher)
task2 = Task(description="Write technical summary", agent=writer, depends_on=[task1])

crew = Crew(agents=[researcher, writer], tasks=[task1, task2])
result = crew.run()

The agents’ roles and tasks drive behavior. There is no explicit graph topology. The runtime manages how the researcher and writer communicate. This style is intuitive for teams that already organize work in terms of roles and tasks.

CrewAI’s strength is speed and simplicity. The runtime is pure Python and does not depend on LangChain. The code path is short. Many practitioners report lower orchestration overhead compared to LangGraph. 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 constructs a prompt that includes the task and the memory log. The LLM returns a Python snippet. The framework executes that snippet in a sandbox. The outputs are appended to memory. The loop repeats until the agent signals completion. Smolagents docs

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. The model can then debug its own code in the next iteration. Smolagents CodeAgent

Smolagents also offers a ToolCallingAgent. This variant uses JSON (JavaScript Object Notation) tool calls, similar to OpenAI function calling. The LLM emits a JSON object specifying which tool to invoke. The framework executes the tool and returns the result. The control flow still emerges dynamically from the sequence of calls. There is no pre defined graph.

State management is intentionally minimal. Each agent has an AgentMemory object that logs steps within a single run. There is no built in persistence across sessions. If you need to resume a run days later, you must persist the memory yourself. This makes Smolagents suitable for self contained runs that do not need to survive process restarts.

Safety hinges on sandboxing. Because the agent executes arbitrary Python, you must isolate execution. Smolagents integrates with Docker containers or services like E2B. The sandbox restricts network and filesystem access. The framework itself does not enforce these controls. It assumes a responsible deployment environment. Smolagents sandboxing

from smolagents import CodeAgent, PythonEnv

env = PythonEnv()  # sandboxed environment

agent = CodeAgent(
    model=my_llm_client,
    env=env,
    tools=[search, fetch_url],
    max_steps=10,
)

result = agent.run("Find and summarize recent LangGraph vs CrewAI comparisons.")

Behind this small API (application programming interface), the agent builds prompts, generates Python, and executes it. The entire agent loop is transparent. You can inspect every step in agent.memory. That transparency is valuable for debugging. But the lack of persistent state and the reliance on sandboxing mean Smolagents is not a drop in replacement for production workflows that require durability.

Why does LangGraph persist state at every step?

LangGraph persists state at every step to enable resumability and auditability. The checkpointer writes a snapshot after each node execution. 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. This design makes long running, multi step workflows reliable. LangGraph persistence

The checkpointer is a pluggable layer. In development, MemorySaver keeps state in memory. In production, AsyncPostgresSaver writes to a database. Every state update is recorded. You can replay a run step by step. You can even fork a run from any checkpoint to explore alternative paths. This is the foundation for LangGraph’s time travel debugging.

The interrupt primitive builds on this persistence. When a node calls interrupt(value), the runtime raises a GraphInterrupt, saves state, and surfaces the value to the client. The client can present it to a human. When the human responds, the client invokes the graph again with a Command carrying the resume value. The runtime reloads state, restarts the node, and on the second call to interrupt, returns the resume value instead of raising an exception. This pattern makes human in the loop feel like a synchronous input() call, but it works across distributed systems. LangGraph interrupt

LangChain’s human in the loop middleware adds tool level review. It intercepts LLM responses, examines proposed tool calls, and can interrupt for human approval. The reviewer can approve, edit, or reject actions. The middleware converts decisions into tool results. Because it uses the core interrupt mechanism, it inherits the same persistence and resumption semantics.

This granular persistence is a double edged sword. It gives you precise control and recovery. It also adds overhead. Every node step writes to the database. For high throughput scenarios, you need to tune the checkpointer and consider batching. 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. Instead of saving state after every LLM call, you choose which events trigger a snapshot. The default is task_completed. That gives you a checkpoint after each task. For finer recovery, you can use llm_call_completed. That captures state after every model interaction. CrewAI checkpointing

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 full crew state: configuration, memory, task progress, intermediate outputs, and event history. Restoring a checkpoint reconstructs the crew exactly as it was. Completed tasks are skipped. Forking a checkpoint creates a new run lineage that starts from the same state but can evolve independently.

The storage layer is pluggable. JsonProvider writes one file per checkpoint. It is convenient for manual inspection. SqliteProvider writes to a single SQLite database. It is better for high frequency checkpointing. In production, you can swap in a cloud backed provider.

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 LLM calls unless you configure checkpointing for that event.

Why does Smolagents rely on sandboxing?

Smolagents relies on sandboxing because its agents execute arbitrary code. The CodeAgent generates Python snippets that can do anything the language allows. If executed directly on the host, a buggy or malicious snippet could read files, make network calls, or worse. Sandboxing isolates execution. Smolagents secure code execution

The recommended sandboxes are Docker containers or E2B. They restrict filesystem access, network access, and system calls. The agent’s code runs inside the sandbox. The framework captures stdout, return values, and any side effects visible to the agent. The host remains protected.

This is a fundamentally 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 explicitly provided. Smolagents, by contrast, trusts the LLM to write safe code and relies on the sandbox to contain any mistakes. That tradeoff gives you maximum flexibility. It also means you must 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 primitive can pause any node, save state, and wait for a human response. The HITL middleware adds tool level review. The entire mechanism is built on the persistence layer. Resuming is seamless. LangGraph HITL

CrewAI models human review as an explicit task. You insert a task where a human must approve an output before the next agent continues. This is simpler to set up. It does not require understanding a state machine. But it is less flexible. You cannot pause an agent mid thought. You can only pause between tasks.

Smolagents has no built in human in the loop primitive. You can manually insert a step in the agent loop. For example, after code execution, you can present the output to a user and wait for input before continuing. But there is no framework support for persistence or resumption. You must implement that yourself.

How do they compare on tool calling?

LangGraph tools are typically exposed via LangChain. Nodes or agents within the graph call them. The tool results become part of the state. The graph topology determines when and how tools are invoked. LangGraph tools

CrewAI tools are callable Python functions. You attach them to agents. When an agent decides to use a tool, the runtime executes the function and feeds the result back into the conversation. CrewAI also supports LangChain tools for compatibility. CrewAI tools

Smolagents tools are Python functions called from generated code. In the CodeAgent, the LLM writes Python that calls search("query") or fetch_url(url). The framework executes that code. In the ToolCallingAgent, the LLM emits JSON specifying the tool name and arguments. The framework parses the JSON and calls the function. Smolagents tools

The key difference is when 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, LLM tokens, and interrupts in real time. You can watch the graph evolve step by step. The checkpointer lets you replay any past run. You can even fork a run from a checkpoint and explore alternative paths. This is the most mature observability story of the three. LangGraph streaming

CrewAI provides event tracing and a unified control plane for Flows. You can see task progress, agent outputs, and checkpoint events. The event driven checkpointing gives you snapshots you can inspect. But you cannot replay a run step by step with the same fidelity as LangGraph. CrewAI observability

Smolagents gives you the agent’s memory log. You can inspect every step, every code snippet, and every output. The log is in memory and lost when the run ends unless you persist it. There is no built in streaming or replay. But the transparency of the code itself is a powerful debugging aid. You can see exactly what the LLM generated and what happened when it ran.

The choice here reflects the frameworks’ philosophies. LangGraph optimizes for deterministic, replayable workflows. CrewAI optimizes for fast, collaborative runs. Smolagents optimizes for flexible, inspectable code.

FeatureLangGraphCrewAISmolagents
Core paradigmState machine graphRole based crewCode generating agent
State modelTyped shared state with reducersImplicit state in memory and task outputsIn memory step log per run
PersistenceDatabase backed checkpointer at every stepEvent driven checkpoints (task or LLM call)None by default
Human in the loopinterrupt primitive, HITL middlewareExplicit review tasksManual insertion
SafetyArchitectural constraints via graphControlled tool definitionsSandboxed code execution
Best forDeterministic, auditable, long running workflowsFast, collaborative, role based automationExploratory, code heavy, flexible tasks

Frequently Asked Questions

Q: Which framework is best for production deployments that require audit trails?

LangGraph. Its step by step persistence and explicit state machine give you a complete, replayable log 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?

LangGraph depends on LangChain for some integrations but the core graph runtime is independent. You can write nodes that call any LLM client directly. The checkpointer and streaming do not require LangChain. The HITL middleware and some tooling do. In practice, most LangGraph deployments use LangChain for convenience.

Q: Does CrewAI support streaming of agent outputs?

CrewAI supports streaming through its event system. You can subscribe to events like llm_token or task_output and stream them to a client. The Flows control plane provides built in streaming endpoints. It is not as deeply integrated as LangGraph’s per token streaming, but it covers common use cases.

Q: How do I debug a Smolagents run that produces a Python error?

The agent memory log contains the full code snippet and the error traceback. The framework feeds errors back to the LLM automatically. You can also inspect the log after the run. For persistent debugging, you can serialize the memory to a file. The sandbox environment can be configured to log all executed code.

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 checkpoint frequency. CrewAI’s simplicity means you may outgrow it for complex, cyclical workflows. Smolagents’ reliance on sandboxing adds infrastructure overhead. The LLM’s code generation can also be unpredictable, leading to more retries and higher token costs.

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#crewai#smolagents
More from the library
The Newsletter

Keep up with AI. One email a week.

One thoughtful email each week. Unsubscribe whenever you like.