← All posts
AIAgentsArchitecture

LangGraph vs Claude Agent SDK: which one for your agent stack?

One is an orchestration runtime you wire by hand; the other is a batteries-included agent loop. They aren't rivals — they sit at different layers. Here's how to tell which job needs which, with diagrams and the hybrid that uses both.

TL;DR


1. Two different philosophies

LangGraph: you draw the flow

LangGraph models an agent as a state graph. You declare a StateGraph, each node is a function that takes the state and returns an update (merged through a reducer), and edges decide where to go next — including conditional edges and loops.

Its real power: you can mix deterministic steps and LLM-decided steps in one graph. Want step 3 to always hit Postgres before the model gets a say? Hard-code that node.

from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
from typing import Annotated, TypedDict
import operator

class S(TypedDict):
    messages: Annotated[list, operator.add]
    retries: int

def plan(s: S): ...
def act(s: S): ...
def route(s: S):
    return "act" if s["retries"] < 3 else END

g = StateGraph(S)
g.add_node("plan", plan)
g.add_node("act", act)
g.add_edge(START, "plan")
g.add_conditional_edges("plan", route, {"act": "act", END: END})
g.add_edge("act", "plan")
app = g.compile(checkpointer=MemorySaver())
flowchart LR
  START((START)) --> plan[plan node]
  plan --> route{route}
  route -->|retry| act[act node]
  route -->|done| E((END))
  act --> plan
  plan -. checkpoint .-> DB[(state store)]
  act -. checkpoint .-> DB

Three features that genuinely earn their keep:

  1. Persistence / checkpointing — state is saved after every super-step. If the process dies mid-run, it resumes exactly where it stopped. This is the foundation for durable execution of workflows that run for hours.
  2. Human-in-the-loop — pause the graph, let a human edit the state, then continue. Not a hack — a first-class primitive (interrupt).
  3. Granular streaming — stream by token, by node, or by state update.

Claude Agent SDK: you assign the work

The Agent SDK goes the other way: the agent loop is already written — by the team behind Claude Code. You don’t build a graph; you configure an agent and drop it into an environment.

from claude_agent_sdk import query, ClaudeAgentOptions

options = ClaudeAgentOptions(
    system_prompt="You are an SRE for a Proxmox homelab.",
    allowed_tools=["Read", "Grep", "Bash"],
    permission_mode="acceptEdits",
    cwd="/opt/hive",
)

async for msg in query(prompt="Find why the gateway container is getting OOM-killed", options=options):
    print(msg)

What you get from the very first line:

flowchart TB
  P[prompt + ClaudeAgentOptions] --> L{{agent loop}}
  L --> M[Claude picks the next step]
  M -->|tool call| G[/permission + PreToolUse hook/]
  G -->|allowed| T[Read · Grep · Bash · WebFetch]
  G -->|denied| M
  T --> PH[PostToolUse hook]
  PH --> L
  M -->|delegate| SUB[subagent · own context]
  SUB --> L
  M -->|MCP| X[(Linear · GitHub · DB)]
  X --> L
  M -->|done| R[ResultMessage]

The SDK ships in TypeScript and Python, and runs on the same engine as Claude Code, so the agent behaves exactly like the thing you already know from the terminal. For multi-turn, interactive sessions there’s also ClaudeSDKClient (connect once, send many prompts, interrupt() mid-run); query() is the one-shot form.


2. Head to head

CriterionLangGraphClaude Agent SDK
Core abstractionGraph + StateAgent loop + Tools
ModelAny (OpenAI, Claude, local…)Claude
Flow controlTotal, explicitMostly model-decided
Durable / resumeCheckpointer (strong, first-class)Session resume, lighter
Human-in-the-loopinterrupt primitivePermission prompt / hook
Built-in coding toolsNo (write your own)Yes (file, bash, search)
Multi-agentBuild your own supervisor/swarmSubagents built in
Policy / guardrailsBake into nodesHooks + permissions
Learning curveSteeperGentler
Vendor lock-inLowHigh

3. Which one do you pick?

Pick LangGraph when:

Pick the Claude Agent SDK when:


4. Use both — the model that actually holds up

For any serious system, this is the split I keep coming back to:

flowchart TB
  START((START)) --> triage[triage_incident]
  triage --> fix[propose_fix]
  fix --> appr{human_approval
interrupt} appr -->|approved| deploy[deploy] appr -->|rejected| fix deploy --> E((END)) triage -. runs .-> A1[Claude Agent SDK
read logs · run commands] fix -. runs .-> A2[Claude Agent SDK
edit code · run tests] deploy -. plain code, no LLM .-> C[shell / API call] subgraph LG[LangGraph — skeleton: state · retry · HITL · checkpoint] START triage fix appr deploy E end

LangGraph is the skeleton: it knows which step you’re on, how to retry, and who approves. The Agent SDK is the muscle: it does the open, messy work that needs real tools. Clean boundary — the graph holds state, the agent holds action.


5. Common traps

  1. Using LangGraph for work the model can already do. Drawing 12 nodes for something an agent loop solves in 3 turns is self-inflicted maintenance cost.
  2. Using the Agent SDK for a flow that must be deterministic. Payments, DB migrations, writing to a ledger — don’t let the model decide the order.
  3. Forgetting the checkpointer. LangGraph without one throws away its single biggest advantage.
  4. Giving an agent bash with no hooks or permissions. On real infrastructure, that’s a when, not an if.
  5. Stuffing everything into the main context. If you have subagents, fan out; keep the root context lean.

6. Closing

LangGraph and the Claude Agent SDK don’t compete head-on — they live at different layers. The right question isn’t “which one,” it’s:

Which part of the system needs a known flow, and which part needs the ability to improvise?

Answer that, and the architecture draws itself.


References: LangGraph · Claude Agent SDK

Want something like this built for your team?

Get a quote →