Pydantic AI V2 went stable on June 23, 2026, and it changes how Python agents are built. After seven betas, the release’s central contribution is a new primitive called a capability — a composable unit that bundles tools, lifecycle hooks, instructions, and model settings into one object. If you’ve been putting off the upgrade, the architecture is genuinely better and the migration is shorter than you expect.
What Is a Capability? #
In V1, you wired an agent by passing a growing list of keyword arguments to the Agent
constructor: tools=
, mcp_servers=
, instrument=
, prepare_tools=
, history_processors=
. In V2, all of that lives inside capabilities passed as a list. Each capability bundles everything a feature needs: its tools, the instructions that tell the model when to use them, any lifecycle hooks, and model settings — one composable object.
The practical payoff is reusability. Build a memory capability once, drop it into any agent that needs it. Set defer_=True
and the capability collapses to a single catalog entry — the model loads the full bundle only when it decides to use it, reducing token overhead on agents with large tool sets.
from pydantic_ai import Agent
from pydantic_ai.capabilities import Capability, Thinking, WebSearch
from pydantic_ai.mcp 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_=True,
),
],
)
The Pydantic AI Harness #
The Harness is a separate package — pydantic-ai-harness
— that ships capabilities not yet ready for core. The split is deliberate: core stays small and stable; the Harness iterates quickly. When a capability matures and proves broadly useful, it graduates into core. The Harness currently ships memory systems, guardrails, context management, file system access, and the feature most worth your attention: CodeMode.
CodeMode: Fewer Round-Trips, Same Tools #
CodeMode wraps your existing tools into a single run_code
tool. Instead of one model round-trip per tool call, the model writes Python code that orchestrates your tools — with asyncio.gather
for parallel calls, loops, and conditionals — inside a single execution. An agent that previously needed 11 model calls to fetch and process 10 items now does it in roughly 2.
The runtime is Monty, a minimal Python interpreter written in Rust. It runs a safe subset: standard library modules including asyncio
, json
, re
, math
, and pathlib
are available. No third-party imports, no timing primitives — sandboxed by design.
from pydantic_ai import Agent
from pydantic_ai_harness import CodeMode
agent = Agent('anthropic:claude-sonnet-5', capabilities=[CodeMode()])
@agent.tool_plain
def get_weather(city: str) -> dict:
return {'city': city, 'temp_f': 72, 'condition': 'sunny'}
@agent.tool_plain
def convert_temp(fahrenheit: float) -> float:
return round((fahrenheit - 32) * 5 / 9, 1)
result = agent.run_sync("Weather in Paris and Tokyo, in Celsius?")
The model receives one run_code
tool instead of two separate ones. It calls both tools for both cities inside a single code block using asyncio.gather
. Pydantic’s docs note CodeMode is “an early candidate to graduate into core” — meaning this won’t always require the Harness package.
Five Breaking Changes to Fix Before You Upgrade #
The V1-to-V2 migration path is: upgrade to the latest V1 first, fix every deprecation warning, then jump to V2. That handles roughly 80% of the work. The remaining 20% is a short list of behavior changes that warnings couldn’t catch:
The It now routes to the Responses API instead of Chat Completions. If your code usesopenai:
prefix changed silently.openai:
and expects Chat Completions behavior, switch toopenai-chat:
. This is the most dangerous change — it produces no error, just different behavior.Provider extras are now opt-in. Bedrock, Groq, Mistral, Cohere, and others are no longer installed with a barepip install pydantic-ai
. Add them explicitly:pip install 'pydantic-ai[bedrock,groq]'
.The MCP server classes are gone.MCPServerStdio
,MCPServerSSE
, andMCPServerStreamableHTTP
are removed. UseMCPToolset
frompydantic_ai.mcp
instead, and replaceagent.run_mcp_servers()
withasync with agent:
.WebSearch and WebFetch default to native-only. If your model doesn’t support native search, requests will raise instead of falling back silently. Restore fallback behavior:WebSearch(local='duckduckgo')
orWebFetch(local=True)
.Agent constructor kwargs migrated to capabilities. The kwargsinstrument=
,mcp_servers=
,prepare_tools=
, andhistory_processors=
are removed fromAgent()
. PassInstrumentation(...)
,PrepareTools(...)
, andProcessHistory(...)
as capabilities instead.
One thing that does not break: message history. V1-serialized messages continue to deserialize in V2, so persistent conversations survive the upgrade intact.
Upgrade Now #
V1 is not deprecated, but the capability pattern is cleaner architecture for anything beyond a single-tool agent. If you run multi-tool agents — especially with MCP servers — the migration pays off immediately: less constructor configuration, reusable capabilities, and CodeMode to cut tool round-trips. The version policy also changed: breaking change windows shrink from six months to three months between majors, so the sooner you’re on V2, the more runway you have before the next transition.
Start with the upgrade guide, review the V2 announcement for the architectural overview, and explore the CodeMode docs for any agent doing repeated tool calls.