If you are choosing a Python agent framework this month, the real fork isn't features — both do tools, streaming, MCP, and every model you'd want. The fork is who owns the loop. Pydantic AI V2, which went stable on June 23, 2026 after seven betas, hides the loop and hands you one thing to compose: the capability. LangGraph hands you the loop itself — a graph of nodes and edges — and expects you to draw it. Pick by which of those two jobs is the hard part of your product.
The one-screen answer#
- Pydantic AI V2 organizes everything around the
capability: a named object that bundles an agent's tools, instructions, lifecycle hooks, and model settings. You attach capabilities to anAgentand the framework runs the loop. Core stays small (eight built-in capabilities); heavier batteries live in a separate first-party Harness. - LangGraph makes the loop the product. You declare a
StateGraphover explicit typed state, add nodes and edges, and compile with a checkpointer. Every state transition is saved, so a crash resumes from the last good node and aninterrupt()can pause for a human and resume bythread_id. - Rule of thumb: if the loop is easy and you want ergonomics and reuse, go Pydantic AI V2. If the control flow is the hard, reviewable, must-not-lose-state part, go LangGraph.
Pydantic AI V2: the capability is the whole idea#
V1 agents sprawled — tools here, system prompt there, a callback bolted on the side. V2's answer is to collapse all of that into one composable unit. A capability bundles tools, instructions, hooks, and model settings, and you build behavior by stacking them:
from pydantic_ai import Agent
from pydantic_ai.capabilities import Thinking, WebSearch, Capability
from pydantic_ai.toolsets import MCPToolset
agent = Agent(
'anthropic:claude-sonnet-5',
instructions='Research thoroughly and cite sources.',
capabilities=[
Thinking(effort='high'),
WebSearch(),
Capability(
id='github',
description='Look up GitHub issues, PRs, and code.',
instructions='Use GitHub tools when questions involve repositories.',
toolset=MCPToolset('https://mcp.example.com/github'),
defer_loading=True,
),
],
)
Two things to notice. First, the GitHub capability carries its own instructions — the prompt for "how to use these tools" travels with the tools, not in a giant shared system prompt. Second, defer_loading=True means that toolset only enters the model's context when it's relevant, which is the built-in answer to tool-budget bloat.
The other half of the design is where the batteries live. Core ships eight capabilities — Thinking, WebSearch, WebFetch, ImageGeneration, MCP, ToolSearch, PrepareTools, and Hooks — and stays deliberately small and stable. Everything heavier — memory, guardrails, context management, filesystem access, code mode — is the first-party Pydantic AI Harness, versioned separately so it can move fast without churning your core. Hooks give you ~20 intercept points across four levels (run, node, model-request, tool):
from pydantic_ai.capabilities import Hooks
hooks = Hooks()
@hooks.on.before_model_request
async def log_request(ctx, request_context):
print(f"Sending {len(request_context.messages)} messages")
return request_context
agent = Agent("openai:gpt-4.1", capabilities=[hooks])
The pace since stable tells you how much of the surface area moved to the edges: v2.1.0 added Anthropic web-tool support (June 29), v2.2.0 Claude Sonnet 5 (June 30), v2.3.0 a native Z.AI provider (July 1), v2.4.0 a GEval evaluator (July 2), v2.5.0 message-history sanitization (July 3). Small core, fast Harness — that's the whole architectural thesis.
Pydantic AI V2 asks "what does this agent have?" and answers in capabilities. LangGraph asks "what does this agent do next?" and answers in edges.
LangGraph: the graph is the product#
LangGraph refuses to hide the loop, and that's the point. You define state as a TypedDict, write nodes as plain functions that receive state and return updates, connect them with edges, set an entry point, and compile with a checkpointer:
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt
class State(TypedDict):
draft: str
approved: bool
def write(state: State) -> dict:
return {"draft": "...proposed change..."}
def review(state: State) -> dict:
decision = interrupt({"draft": state["draft"]}) # pause, persist, wait
return {"approved": decision == "approve"}
g = StateGraph(State)
g.add_node("write", write)
g.add_node("review", review)
g.add_edge(START, "write")
g.add_edge("write", "review")
g.add_edge("review", END)
app = g.compile(checkpointer=InMemorySaver())
That interrupt() line is the feature you can't cleanly buy elsewhere. The graph pauses mid-run, the checkpointer persists the exact state, and the run resumes — seconds or hours later — when you call invoke again with the same thread_id. Because every transition is checkpointed, failure recovery becomes "resume from the last good node" instead of "start over." LangGraph 1.0 went GA in late 2025 and 1.2 (May 11, 2026) added per-node timeouts; the durable-state model is the reason teams reach for it when a run has to survive a deploy.
The cost is honesty about how much you're assembling. There is no inherited gather/act/verify loop, no default toolset. You get a runtime and a state machine, and you build the agent on top.
How to actually choose#
Reach for Pydantic AI V2 when:
- You want to ship this week with FastAPI-style ergonomics and Pydantic's validation you already trust.
- Your reuse unit is a capability — "the research bundle," "the billing bundle" — that you want to drop into several agents.
- The loop is not your hard problem. You want a small stable core and let the Harness carry memory, guardrails, and context management.
Reach for LangGraph when:
- The control flow is the product: deterministic steps interleaved with agentic ones, cycles, branches you can point at in review.
- Durable execution and human-in-the-loop are requirements — long jobs that survive a crash, approval gates before money or data moves.
- You need to inspect, replay, and modify state at every step, and you refuse to be single-model.
And the honest middle: they're different layers, so "both" is legitimate. LangGraph as the durable orchestrator; a Pydantic AI agent inside a node when one step is genuinely open-ended. If you've read our take on Claude Agent SDK vs LangGraph, this is the same fault line one framework over: an opinionated harness that owns the loop versus a substrate that hands you the loop. Pydantic AI V2 just moved the harness side into one very well-designed primitive.
The wrong way to decide is to line up feature checklists — they'll tie. The right way is to name your hard part. If your hard part is composing and reusing agent behavior, buy the capability. If your hard part is not losing the thread of a long, branching, human-gated process, buy the graph.



