feat: On-demand (deferred loading) capabilities, including instructions, tools, model settings, and hooks
Summary
This PR adds first-class progressive disclosure of capabilities to Pydantic AI, exposed in the docs as on-demand capabilities.
For users, the shape is simple: give a capability a stable id, set defer_loading=True, and keep using the same agent. A description is optional, but recommended so the model knows when to load the capability.
On the first request, the model sees a compact capability catalog and the framework-managed load_capability tool. When it needs a workflow, it calls load_capability({"id": "..."}); Pydantic AI returns that capability's instructions and reveals its function tools on the next request.
API Shape
Decorator style for a new capability:
from pydantic_ai import RunContext
from pydantic_ai.capabilities import Capability
orders_capability = Capability[Store](
id='orders',
description='Use for order tracking, delivery status, shipping updates, missing packages, or questions involving an order ID.',
instructions='Quote the item name in your reply.',
defer_loading=True,
)
@orders_capability.tool
def order_status(ctx: RunContext[Store], order_id: str) -> str:
"""Look up the current shipping or delivery status for an order."""
order = ctx.deps.orders.get(order_id)
if order is None:
return f'No order found with id {order_id}.'
return f'Order {order.id} ({order.item}) is currently {order.status}.'
Existing-toolset style:
orders_capability = Capability[Store](
id='orders',
description='Use for order tracking, delivery status, shipping updates, missing packages, or questions involving an order ID.',
toolsets=[orders_tools],
defer_loading=True,
)
Custom capability style:
from dataclasses import dataclass
from pydantic_ai.capabilities import AbstractCapability
@dataclass
class AccountSecurityRunbook(AbstractCapability[Store]):
id: str = 'account-security'
description: str = 'Use for suspicious logins, account takeover, or session revocation.'
defer_loading: bool = True
Then pass capabilities to the agent as usual:
support_agent = Agent(
model='openai-responses:gpt-5.4',
deps_type=Store,
instructions='You are a customer-support agent for an e-commerce store.',
capabilities=[orders_capability, returns_capability, AccountSecurityRunbook()],
)
Cache and Tool Search
Deferred capability tools build on Pydantic AI's ToolSearch support.
On OpenAI Responses and Anthropic models with native tool search, tools can be revealed through the provider-native tool-search surface instead of by changing the provider-visible function-tool array. That keeps the discovery exchange append-only, so tool definitions stay stable and prompt-cache prefixes can remain warm across capability loads.
On providers without native tool search, Pydantic AI falls back to the local search_tools function tool. That still keeps irrelevant capability details out of the initial context, but local tool visibility may change between turns, so it does not have the same cache-stability guarantee.
Current scope: on-demand capabilities support instructions, function tools, model settings, lifecycle hooks, deferred-tool handling, and native tools. Deferring a native tool works, but revealing one always breaks the prompt-cache prefix (native tool definitions are part of the request prefix on every provider), so prefer instruction- or function-tool-only deferral when cache stability matters.
Try This PR
Existing pydantic-ai projects can point at a known-green commit on this branch (204cb94f1968d7b55a8c1f5bf0b5e97db746907e — the branch tip may go red while it's under active development):
uv add "pydantic-ai @ git+https://github.com/pydantic/pydantic-ai.git@204cb94f1968d7b55a8c1f5bf0b5e97db746907e"
For pydantic-ai-slim projects with provider extras:
uv add "pydantic-ai-slim[openai] @ git+https://github.com/pydantic/pydantic-ai.git@204cb94f1968d7b55a8c1f5bf0b5e97db746907e#subdirectory=pydantic_ai_slim"
Then add id= and defer_loading=True to an existing capability, ideally with a model-facing description, and run the same prompts. With Logfire tracing enabled, you should see load_capability before the capability's instructions and tools are used.
For a complete, runnable walkthrough, see the On-demand capabilities section of docs/capabilities.md, which covers deferring instructions, function tools, model settings, hooks, and native tools, plus the prompt-cache implications per provider.
Related Work
This is part of the broader progressive-disclosure work for agent capabilities:
- pydantic-ai-harness#22 tracks the Harness-side skills capability and skills-style progressive disclosure.
- pydantic-ai#3780 explored native Agent Skills support. This PR keeps the primitive in Pydantic AI core as on-demand capability bundles; filesystem-backed skills can remain a Harness or third-party layer on top.
Implementation Notes
- Adds capability-scoped deferred loading with stable capability
ids anddefer_loading=True. - Injects the framework-managed
load_capabilitytool when deferred capabilities are present. - Uses
ToolSearchfor deferred function-tool discovery, with native OpenAI Responses / Anthropic support and local fallback elsewhere. - Tracks loaded capabilities from message history so resumed runs and replayed histories keep the same loaded state.
- Keeps unloaded capability tools hidden from tool search and ordinary tool lists.
- Validates duplicate capability IDs and requires an explicit stable ID for deferred capabilities, eagerly at construction (so misconfigured static
capabilities=fail fast inAgent(...)) with a run-time backstop for capabilities supplied per-run or returned byfor_run; also reserves loader tool names. - Documents the feature in
docs/capabilities.mdwith runnable examples and per-provider cache implications.
Checklist
- Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it.
- No breaking changes in accordance with the version policy.
- PR title is fit for the release changelog.