attenu

Docs › Examples › Example · Pydantic AI source: examples/integrations/pydantic_ai/README.md · updated · markdown

attenu-guard x Pydantic AI

Enforced authority attenuation for Pydantic AI's agent delegation pattern. Tested against pydantic-ai-slim 2.31.1 (MIT, requires Python >= 3.10).

What it hooks

Hook point API used
Child creation GuardedDeps.delegate(...), called inside the delegating tool; the child Guard rides down as the sub-run's deps (RunContext.deps). Pydantic AI has no callback at the delegation site, so this is a construction-site integration.
Tool invocation DelegationGuard.before_tool_execute — an AbstractCapability registered via Agent(capabilities=[...]). ToolManager._run_execute_hooks awaits it (tool_manager.py:459) before the only path to toolset.call_tool (tool_manager.py:463 -> 1003), so the tool body provably cannot run on a denial. One registration covers function tools, every toolset, and MCP.
Tool invocation (alt) GuardedToolset, a WrapperToolset that checks inside call_tool. Use it to guard exactly one toolset instead of the whole agent.

Both hooks share authorize_tool_call(...), which never returns on a denial.

Run it

python examples/integrations/pydantic_ai/demo.py     # offline, no API key
pytest tests/integrations/test_pydantic_ai.py

What you'll see

An orchestrator (crm.*, mail.send, 100 000 rows, egress any) delegates to a summarizer (crm.read, 5 000 rows, egress none, ttl 900) whose model has been poisoned. Then: crm_query(4200) runs; crm_export(...) is denied before its body (ops.exported_to stays None); a delegation asking for more is met down; revoking the sub-agent denies even the tool it was allowed; the hash-chained audit log verifies and carries the deny with reason scope_not_granted.

on_denial="raise" (default) aborts the run with AuthorityDenied; on_denial="tool_failed" hands the model a ToolFailed result it can adapt to — the body never runs either way. Unmapped tools and a missing Guard fail closed.