Paybond integrates with Pydantic AI at the tool execution boundary — Harbor verify runs before side-effecting Tool / callable handlers execute, then auto-evidence fires after success. Model inference stays on your LLM provider; Paybond guards paid and side-effecting tool calls only.
Python only — Pydantic AI is a Python framework. TypeScript Kit does not ship a Pydantic AI adapter; use agent-agnostic or MCP for other hosts.
Install
Install
npm install @paybond/kitImport from @paybond/kit/agent
// Pydantic AI is Python-only — use paybond-kit[pydantic-ai] in Python projects.- Python only. TypeScript Kit does not ship a Pydantic AI adapter — use agent-agnostic middleware or MCP for other hosts.
- Python: `paybond agent demo pydantic-ai smoke` requires the optional `pydantic-ai` extra. Use `pip install "paybond-kit[pydantic-ai]"`, `pipx install 'paybond-kit[pydantic-ai]'`, or `pipx inject paybond-kit pydantic-ai` (when base paybond-kit is already installed).
- Smoke: `paybond agent demo pydantic-ai smoke --operation paid-tool --requested-spend-cents 100 --evidence-preset cost_and_completion --format json`.
Recommended wiring
One-liner (sandbox): paybond.instrument({ policy, framework: "pydantic-ai", tools }) or paybond.agent({ policy, framework: "pydantic-ai", tools }) returns guarded Pydantic AI tools. Production: omit sandbox and call instrumented.bind() per session.
from pydantic_ai import Agent, Tool from paybond_kit import Paybond paybond = await Paybond.open(api_key=os.environ["PAYBOND_API_KEY"]) result = await paybond.instrument( policy="./paybond.policy.yaml", framework="pydantic-ai", tools=[book_flight, search_catalog], # callables or Tool instances ) agent = Agent("openai:gpt-4o", tools=result.tools)
Already bound a run? Use create_paybond_pydantic_ai_config(run, tools) — returns { tools, wrap_tool } for incremental wiring. Prefer Agent(tools=config.tools) or FunctionToolset with pre-wrapped tools.
See Agent middleware for run binding, registry rules, and tenant isolation.
Advanced / manual wiring
When you need step-by-step control over registry and bind:
- Bind a
PaybondAgentRunwith a tool registry (sandbox bootstrap or production attach). - Register side-effecting tools on the registry so spend resolvers read tool args (for example
amount_centsfrom tool kwargs). - Wrap tools with
create_paybond_pydantic_ai_configbefore registering them onAgent/FunctionToolset.
from pydantic_ai import Tool from paybond_kit.pydantic_ai import create_paybond_pydantic_ai_config def submit_po(vendor_id: str, amount_cents: int) -> dict: """Submit a purchase order.""" ... config = create_paybond_pydantic_ai_config( run, [Tool(submit_po, name="procurement.submit_po")], ) guarded_tools = config.tools
Non-side-effecting tools (per registry) pass through unchanged.
Scaffold and smoke
paybond init agent-middleware --framework pydantic-ai --out paybond_pydantic_ai.py paybond policy init --preset saas --out paybond.policy.yaml
Validate authorization and evidence without a live Pydantic AI agent or LLM:
paybond login paybond agent demo pydantic-ai smoke \ --operation paid-tool \ --requested-spend-cents 100 \ --evidence-preset cost_and_completion \ --format json
Requires the optional extra: pip install "paybond-kit[pydantic-ai]". With pipx: pipx install 'paybond-kit[pydantic-ai]' or pipx inject paybond-kit pydantic-ai.
Approval holds
When Harbor returns an approval hold, surface it to operators, approve in the tenant console, then retry with the same operation, amount, metadata, and approvalToken. Hard denials must not execute the tool. Guarded Pydantic AI tools raise pydantic_ai.ModelRetry with a descriptive Paybond message so the model loop can recover without performing the side effect.
run.store_approval_token(tool_call_id, approval_token_from_console) # Retry the same tool call; the wrap path reads run.get_approval_token(tool_call_id).
Known limitations
Paybond only governs tools that go through the wrap helper:
- Provider native tools (provider-executed function calling) are not intercepted.
- Unwrapped MCP / remote toolsets bypass Harbor unless you re-wrap local callables.
- Tools registered with
@agent.tool/@agent.tool_plainafter wrap (or without passingconfig.tools) are not guarded — wrap first, then pass tools intoAgent(...). prepare/prepare_toolsonly mutate tool definitions shown to the model; they do not intercept execution.
Related
- Agent middleware — run binding, registry, auto-evidence
- Agent-agnostic adapter — fallback for custom orchestrators
- Support matrix — shipped framework surfaces
- Pydantic AI spend controls guide — task-oriented wiring walkthrough