New to the adapters? Start with the wrapping overview and comparison
table.
Compatible versions: see the compatibility
table for the verified
pydantic-ai-slim range.wrap_pydantic_agent returns a HexgatePydanticAgent proxy backed by a clone of
the original agent whose tools are gated by a freshly built PolicyEnforcer.
Tools registered via the Agent(...) constructor or via @agent.tool /
@agent.tool_plain are all picked up. The hexgate_context is supplied per call, so a
single wrapped agent can serve many users concurrently — role resolution happens
at call time from the contextvar.
Under the hood
wrap_pydantic_agentbuilds aPolicySet, constructs onePolicyEnforcer, reads tools off the agent’s internal_function_toolset, copies each tool with an enforcer-gatedfunction_schema.call, and returns a shallow-copied agent whose toolset holds those gated copies — your originalagentis untouched, so it can be reused or wrapped again independently.- Each invocation method on
HexgatePydanticAgent(run/run_sync/run_stream/iter) takeshexgate_context=and opens aHexgateContextscope around the delegatedAgentcall. The contextvar is per-task, so concurrentruncalls for different users do not see each other’s policies. - A non-allow
DecisionraisesModelRetry(decision.as_error_message()); pydantic_ai surfaces it back to the model as a tool-result message —[policy_denied]/[approval_required]markers in the same shape as the OpenAI / Google adapters — instead of aborting the run. - Identity propagation uses
propagate_attributes(...)so Langfuse spans carry the caller identity. Global tracing is enabled viaAgent.instrument_all()on construction.
Runnable example
examples/devops_pydantic_ai.py — wrap_pydantic_agent (Pydantic AI) end-to-end.