Skip to main content
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_agent builds a PolicySet, constructs one PolicyEnforcer, reads tools off the agent’s internal _function_toolset, copies each tool with an enforcer-gated function_schema.call, and returns a shallow-copied agent whose toolset holds those gated copies — your original agent is untouched, so it can be reused or wrapped again independently.
  • Each invocation method on HexgatePydanticAgent (run / run_sync / run_stream / iter) takes hexgate_context= and opens a HexgateContext scope around the delegated Agent call. The contextvar is per-task, so concurrent run calls for different users do not see each other’s policies.
  • A non-allow Decision raises ModelRetry(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 via Agent.instrument_all() on construction.

Runnable example

examples/devops_pydantic_ai.pywrap_pydantic_agent (Pydantic AI) end-to-end.