xstate-statemachine vs LangGraph

An honest comparison of xstate-statemachine's [agents] extra and LangGraph: hierarchy, guards as policy, durable timeouts, human-in-the-loop, persistence, observability, and when to choose which.

LangGraph is the most widely used agent-orchestration library in Python: a graph of nodes over typed state, with checkpointers, interrupt() for human input, streaming, LangSmith (tracing, and Studio for debugging). It is excellent at what it does, and the two are not either/or.

This table is generated from docs/_data/comparisons.json, and corrections are welcome as PRs against that file. Every row carries a source note. It was checked against langgraph 1.2.20, 2026-11-07; see LangGraph for the current state. A test (tests/test_battle_291_a.py) fails the nightly comparisons CI job when this check is more than 180 days old, or when the installed release’s major version differs from the one checked, so the table cannot silently go stale (a pull request is never blocked by a competitor’s release).

Feature table

Capability xstate-statemachine LangGraph Source
Hierarchy (nested states) Yes: compound states at any depth, XState v5 semantics Subgraphs as nodes; no statechart hierarchy langgraph 1.2.20: compiled StateGraph added as a node; no compound/parallel state type
Parallel regions Yes: type: parallel, done.state join Fan-out / fan-in via edges and Send langgraph 1.2.20: langgraph.types.Send; parallel nodes in a superstep
Guards as policy Named guards in the chart; run_tool re-checks per-state tool allow-lists (X0.13) Conditional edges are code; tool policy lives in node code langgraph 1.2.20: add_conditional_edges routing functions
after timeouts Yes: durable deadlines for model, tool and human waits, persisted and woken by DueTimerScanner Per-node timeout= / TimeoutPolicy and RetryPolicy; no durable timers langgraph 1.2.20: StateGraph.add_node(timeout=, retry_policy=), langgraph.types.TimeoutPolicy
Human-in-the-loop as durable state awaiting_human is a persisted state with an escalation deadline; approval names exact call ids interrupt() + checkpointer (strong) langgraph 1.2.20: langgraph.types.interrupt, Command(resume=)
Persistence / replay Snapshots in SQLite / Redis / SQLAlchemy (e.g. Postgres) stores, versioned layout, transition-log replay() Checkpointers with time travel (strong) langgraph 1.2.20: langgraph.checkpoint savers, get_state_history
Visual editor Opens in the Stately editor (the chart is XState JSON; tool policy in meta, plus one engine key, actionErrorPolicy) LangSmith Studio, formerly LangGraph Studio (graph view, debugging) langgraph 1.2.20: LangSmith Studio docs
Typed context / events Pydantic context + event schemas ([pydantic]), strict tool-argument schemas TypedDict / Pydantic graph state langgraph 1.2.20: StateGraph(state_schema) TypedDict / Pydantic
Observability JSONL trace with gen_ai.* fields, OTel spans (on_span="otel"), no content by default, secret-key redaction LangSmith (strong) langgraph 1.2.20: LangSmith tracing
Multi-agent Reference supervisor / pipeline / debate charts; spawn_agent with per-child and global budgets Supervisor / swarm libraries, subgraphs (strong ecosystem) langgraph 1.2.20: langgraph-supervisor / langgraph-swarm packages, subgraphs
Incremental adoption One LangGraph node (statechart_node), a graph as a service (langgraph_service), a pydantic-ai Tool Functional API; wraps any Python langgraph 1.2.20: langgraph.func.entrypoint / task

When to choose LangGraph

  • You are already on LangChain and want the ecosystem: integrations, prebuilt agents, supervisor and swarm libraries.
  • Your control flow is mostly a dataflow graph, and time travel and LangSmith are what you need from persistence and observability.
  • You want a hosted platform for deployment (LangSmith Deployment, formerly LangGraph Platform) and LangSmith Studio for debugging.

When to choose xstate-statemachine

  • The risky part of the loop has to be policy, not prompt: which tools are legal in which state, re-enforced inside run_tool even when a guard is edited out or a snapshot is forged.
  • You need durable timers: a human approval that escalates after an hour, even if every worker restarted in between.
  • You want the control flow as XState JSON that product and ops people can read and edit in the Stately editor.
  • You want to adopt it one node at a time: statechart_node drops a chart into your existing graph, and its snapshot rides in your checkpointer.

Ours, in 20 lines

This is a support agent. The refund tool is legal only in the tool states and needs a human to approve it, and the whole run is capped at 5 turns and 10 cents. It runs offline with FakeModel:

import asyncio
from xstate_statemachine.contrib.agents import (
    FakeModel,
    load_chart,
    run_agent,
    tool,
    tool_registry,
)


def lookup(order_id: int) -> str:
    """Look up an order."""
    return f"order {order_id}: shipped"


def refund(order_id: int) -> str:
    """Refund an order."""
    return "refunded"


chart = load_chart()  # TOOL_LOOP, plain XState JSON
for s in ("awaiting_model", "awaiting_tool"):
    chart["states"][s]["meta"]["tools"] = [
        "lookup",
        "refund",
    ]  # policy lives in the chart
tools = tool_registry(
    tool(lookup, timeout_s=5), tool(refund, timeout_s=5, side_effect=True)
)
model = FakeModel(
    [
        {
            "tool": "lookup",
            "args": {
                "order_id": 42,
            },
        },
        {
            "tool": "refund",
            "args": {
                "order_id": 42,
            },
        },
    ]
)

res = asyncio.run(
    run_agent(
        chart,
        model=model,
        tools=tools,
        prompt="refund 42",
        budgets={
            "max_turns": 5,
            "max_usd": 0.10,
        },
    )
)
assert (
    res.waiting and res.final_state == "toolLoop.awaiting_human"
)  # durable: persist, resume later

See LLM agents for the full reference, including the LangGraph interop and pydantic-ai adapters.

New to the library? Start with the integrations journey: pick your path, then a fifteen-minute tutorial.