Build Python Agents with Pydantic AI V2's Capability Primitive

Pydantic AI V2 shipped June 23, 2026, collapsing tools, hooks, instructions, and model settings into one composable unit called a capability. Here's what actually changed and what it costs to migrate.

Cover art for Build Python Agents with Pydantic AI V2's Capability Primitive

After seven betas, Pydantic AI V2 is now stable, and its central contribution is structural: every extension point an agent needs - tools, lifecycle hooks, instructions, and model settings - collapses into a single composable unit called a capability. That sounds abstract until you read the CodeMode benchmark: instead of one model round-trip per tool call, the model writes Python that orchestrates your tools with asyncio.gather, 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.

That's the hook, and it's earned. But V2 also comes with a migration wrinkle worth knowing before you upgrade.

What the capability primitive actually is

Pydantic AI reached version 2 on June 23, 2026, and the headline change is structural: every extension point an agent needs - tools, instructions, lifecycle hooks, model settings - now collapses into one primitive called a capability. In V1, you scattered these concerns: tools on @agent.tool, system prompt text in a separate argument, model settings somewhere else. The capability lets you build agents from composable units that bundle tools, hooks, instructions, and model settings into reusable pieces - it's the plugin format for agent behavior.

V2 turns that whole outer layer into one thing you compose. Core stays small and stable, shipping the loop, the providers, the capability and hooks API, and only the capabilities that need deep provider support or are fundamental to every agent. Everything else lives in the Harness, where it can move fast, and a capability can graduate into core once it proves broadly essential.

The split into two packages is deliberate. A new first-party package, the Harness, ships pre-built capabilities on top of it - memory, guardrails, sandboxed code execution, and more - kept separate so the framework's core stays small.

Many capabilities benefit from a "fall up" pattern: they typically start as a local implementation that works with every model, then gain provider-native support that uses the provider's built-in API when available, auto-switching between the two.

CodeMode and deferred loading: the two capabilities worth your time first

The Harness currently ships memory systems, guardrails, context management, file system access, and CodeMode. CodeMode wraps your existing tools into a single run_code tool; instead of one model round-trip per tool call, the model writes Python that orchestrates your tools - with asyncio.gather for parallel calls, loops, and conditionals - inside a single execution.

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.

CodeMode is an early candidate to graduate into core , which tells you something about the team's confidence in its stability. But for now it requires the Harness package with the [code-mode] extra.

The second capability worth immediate attention is deferred loading. Some capabilities ship with Pydantic AI itself; more come from the first-party Pydantic AI Harness; and others are third-party or your own. The defer_loading=True flag on any capability means its toolset stays out of the context window until the model explicitly requests it - so you can attach a hundred-tool MCP server without burning tokens on every run.

Beagle in action#engineering, 2:47pm
The ask
'can you check if the open Jira tickets from last sprint are all linked to PRs?'
Beagle drafts
loads the Jira MCP server (deferred until needed), writes a CodeMode batch that calls get_ticket() and get_pr_link() in parallel with asyncio.gather, drafts a reply listing the three unlinked tickets with direct links
You approve
you approve; the answer posts in one round-trip instead of 11 sequential calls
Do this in your workspace
2 model callsCodeMode batched taskdown from ~11 sequential calls
7 betasbefore V2 went stableno V1 code breaks across 100+ V1 releases
40+ featuresin the Harness capability matrixacross 9 capability categories

How it compares to the other two frameworks teams are actually choosing

The seven frameworks that matter in mid-2026, ranked by production-readiness from 18+ real deployments: LangGraph 1.0 - best overall for complex stateful workflows; Claude Agent SDK - best for Anthropic-native production agents; CrewAI 1.14 - fastest path to role-based multi-agent prototypes; Microsoft Agent Framework 1.0 - best for enterprise .NET / Microsoft stacks; LlamaIndex Workflows 1.0 - best for RAG-grounded agents; Pydantic AI V2 - best for type-safe Python.

That "type-safe Python" label undersells it. The capability primitive is a specific architectural bet that LangGraph and CrewAI have not made. LangGraph gives you a graph you wire by hand; CrewAI gives you roles you assign. Pydantic AI V2 gives you a composable layer around the model loop itself. The question is whether you want to own that graph or own the capability set.

Pydantic AI V2 LangGraph 1.0 Microsoft Agent Framework 1.0
Language Python Python Python + .NET
Core abstraction Capability primitive Stateful graph AutoGen-style conversation graph
MCP support Native (client + server) Via integration Native (1.0)
Sandboxed code execution CodeMode / Monty No No
License MIT MIT MIT
V1 → V2 migration Fix warnings, then upgrade; ~80% automated N/A (LangGraph 1.0 is V1) From AutoGen/SK: 2-4h typical

Every other Python agent framework connects to MCP servers but is not one. Pydantic AI does both, and it is documented as a first-class path, not an experimental flag. That distinction matters if you are building agents that need to expose tools to other agents - the same framework handles both sides.

The migration path and the one gotcha you will hit

In September 2025, Pydantic AI reached V1 and committed to API stability: no changes that break your code until V2. V2 is now available, collecting the breaking and behavior changes that stability guarantee didn't allow.

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 warnings couldn't catch. Most notably: the openai: prefix changed silently - it now routes to the Responses API instead of Chat Completions. If your code uses openai: and expects Chat Completions behavior, switch to openai-chat:. This is the most dangerous change - it produces no error, just different behavior.

There is a second, less-documented problem. Root cause: most coding models' training data predates the June 23, 2026 release, so they confidently generate the old multi-argument Agent() pattern instead of capabilities=[...]. One developer's workaround, shared on r/PydanticAI, was blunt about the problem: "Pydantic AI is great and it's even better with the newly released version 2! But unfortunately LLMs don't know anything about this new 2.0 version."

The practical fix: feed the Pydantic AI V2 changelog into your coding agent's context before asking it to write V2 code. Anthropic's Claude Agent SDK has the same problem with its June 2026 subagent spawning release. A teammate like Beagle running inside your Slack channel can surface the up-to-date docs link before the question even becomes a debugging session.

Wiring a new MCP tool into an agent
Without Beagle
scatter tool registration across @agent.tool decorators, system prompt strings, and model settings; update three files when the tool scope changes
With Beagle
write one Capability object with tools, instructions, and settings co-located; attach it to Agent() in one line; toggle defer_loading=True to keep it out of the context window until needed

Pydantic AI V2 capabilities: common questions

What is a capability in Pydantic AI V2?

A capability is a single composable unit that bundles tools, lifecycle hooks, instructions, and model settings into one object, attached to an agent via a capabilities=[...] list. It replaces the scattered arguments and decorator patterns from V1, making agent behavior reusable and independently testable. Think of it as the plugin format for the layer around the model loop.

What is the Pydantic AI Harness?

The Pydantic AI Harness is the official capability library for Pydantic AI - the batteries for your agent. Core ships capabilities that require model or framework support; the Harness ships everything else as standalone building blocks you pick and choose. It ships as a separate PyPI package so it can move faster than core without breaking stable APIs.

How does CodeMode reduce model round-trips?

CodeMode wraps your existing tools into a single run_code tool. 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 code runs inside Monty, a sandboxed Rust-based Python interpreter with no third-party imports.

How does deferred loading work with MCP servers?

Setting defer_loading=True on a capability keeps its toolset out of the context window until the model explicitly requests it. Core stays small and stable, shipping the loop, the providers, and only the capabilities fundamental to every agent. This lets you attach large MCP servers - with dozens of tools - without the token cost appearing on every run.

Is Pydantic AI V2 worth migrating to from V1?

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 manual review of silent behavior changes, particularly the openai: provider prefix. For greenfield agents in Python, V2 is the correct starting point; for production V1 agents, plan a half-day migration window and read the changelog before touching the openai: prefix.

Keep reading