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.