feat: On-demand (deferred loading) capabilities, including instructions, tools, model settings, and hooks

Merged
AD
5mo ago
+11632-455

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 and defer_loading=True.
  • Injects the framework-managed load_capability tool when deferred capabilities are present.
  • Uses ToolSearch for 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 in Agent(...)) with a run-time backstop for capabilities supplied per-run or returned by for_run; also reserves loader tool names.
  • Documents the feature in docs/capabilities.md with 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.