Docs Frameworks
Pydantic AI
Status: verified. The adapter ships in
kiff-guard and passes the
conformance suite. The seam was verified against the real pydantic-ai
package.
Shape: vote (inverted-control). Pydantic AI runs the tool itself;
the before_tool_execute hook only votes — return args to allow,
raise SkipToolExecution(result) to block. So the guard uses
observe() / decide_only(), not a run callback. See
how the guard works.
Install
pip install "git+https://github.com/kiffhq/kiff-guard.git#subdirectory=packages/python/kiff-guard"
# plus Pydantic AI, the framework you're guarding:
pip install pydantic-ai
Observe — audit with zero config
from pydantic_ai import Agent
from pydantic_ai.capabilities import Hooks
from kiff_guard import Guard
from kiff_guard.adapters.pydantic_ai import kiff_before_tool_execute
guard = Guard(mode="observe") # no client, no tenant needed
agent = Agent(
"...",
tools=[refund_order, send_email],
capabilities=[Hooks(before_tool_execute=kiff_before_tool_execute(guard))],
)
Run the agent as usual, then read the trail:
for r in guard.receipts:
print(r.state, r.tool, r.outcome)
Activate
Derive a starter domain from what you observed, review it, and activate it in the dashboard:
from kiff_guard import export_yaml
print(export_yaml("my-domain", guard.catalog))
Enforce — govern at runtime
Once you have a tenant and an active domain, point the guard at KIFF and
switch to enforce. Only the guard changes:
from kiff_guard import Guard, HTTPClient, ToolMap
from pydantic_ai import Agent
from pydantic_ai.capabilities import Hooks
from kiff_guard.adapters.pydantic_ai import kiff_before_tool_execute
client = HTTPClient(
api_key="kiff_live_...", # mint in the dashboard
tool_map=ToolMap().bind(
"refund_order", action="REFUND_ORDER",
entity_type="Order", entity_arg="order_id"),
)
guard = Guard(client=client, tenant="<tenant>", agent="support", mode="enforce")
agent = Agent("...", tools=[refund_order],
capabilities=[Hooks(before_tool_execute=kiff_before_tool_execute(guard))])
In enforce mode a withheld decision raises SkipToolExecution, so the
tool never runs and the reason is fed back to the model. An allowed
decision returns the args and the tool runs. On a transport error the
guard fails closed (skips) by default.
The seam (verified)
Pydantic AI’s before_tool_execute hook (on a Hooks capability) fires
before the tool runs, with the validated args and a call carrying
tool_name. Returning args runs the tool; raising
SkipToolExecution(result) skips it — the block path.
Links
- Adapter code:
kiff_guard/adapters/pydantic_ai.py - Pydantic AI docs: ai.pydantic.dev/hooks (tool execution hooks)