Agents SDK tutorial (Python)
This path uses the first-party Python integration in paybond_kit.agents_sdk. The same binding and guardrail that appear below can plug into a real Runner.run(...) flow; the example here executes the guard directly so you can rehearse the full Paybond lifecycle without adding model wiring first.
What you will build
One guarded tool flow:
- Open a tenant-bound Paybond session.
- Create an intent for
travel.book_hotel. - Read the returned
capability_token. - Run the OpenAI Agents input guardrail against the tool call.
- Execute the tool only if Harbor allows it.
- Submit signed evidence and inspect the resulting Harbor state.
Install
For published packages:
pip install "paybond-kit[agents]"For the repo example:
cd examples/paybond-kit-openai-agents-python
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtRequired environment
export PAYBOND_GATEWAY_URL="https://gateway.example.com"
export PAYBOND_HARBOR_URL="https://harbor.example.com"
export PAYBOND_API_KEY="paybond_sk_..."
export PAYBOND_PRINCIPAL_DID="did:web:example.com#principal"
export PAYBOND_PRINCIPAL_SEED_HEX="..."
export PAYBOND_PAYEE_DID="did:web:example.com#hotel-booker"
export PAYBOND_PAYEE_SEED_HEX="..."End-to-end flow
import asyncio
import os
from types import SimpleNamespace
from uuid import UUID, uuid4
from agents.tool_context import ToolContext
from agents.tool_guardrails import ToolInputGuardrailData
from paybond_kit import Paybond
from paybond_kit.agents_sdk import (
PaybondCapabilityBinding,
paybond_capability_input_guardrail,
)
def seed32_from_hex(env_name: str) -> bytes:
raw = bytes.fromhex(os.environ[env_name])
if len(raw) != 32:
raise ValueError(f"{env_name} must decode to exactly 32 bytes")
return raw
async def book_hotel(city: str, nightly_budget_cents: int) -> dict[str, object]:
return {
"hotel": "Harbor House",
"city": city,
"status": "confirmed",
"price_cents": nightly_budget_cents,
"confirmation": "HB-2049",
}
async def main() -> None:
paybond = await Paybond.open(
gateway_base_url=os.environ["PAYBOND_GATEWAY_URL"],
api_key=os.environ["PAYBOND_API_KEY"],
harbor_base_url=os.environ["PAYBOND_HARBOR_URL"],
)
try:
intent_id = uuid4()
created = await paybond.intents.create(
principal_did=os.environ["PAYBOND_PRINCIPAL_DID"],
principal_signing_seed=seed32_from_hex("PAYBOND_PRINCIPAL_SEED_HEX"),
payee_did=os.environ["PAYBOND_PAYEE_DID"],
budget={"currency": "usd", "max_spend_usd": 200},
predicate={
"version": 1,
"root": {
"op": "and",
"clauses": [
{
"op": "completion",
"path": ["reservation", "status"],
"value": "confirmed",
},
{
"op": "budget_cap",
"path": ["reservation", "price_cents"],
},
],
},
},
currency="usd",
amount_cents=20_000,
evidence_schema={"type": "object", "properties": {"reservation": {"type": "object"}}},
deadline_rfc3339="2030-12-31T23:59:59Z",
allowed_tools=["travel.book_hotel"],
intent_id=intent_id,
idempotency_key=f"intent:{intent_id}",
)
capability_token = str(created.get("capability_token") or "")
if not capability_token:
raise RuntimeError("intent created without capability_token; ensure the intent is funded")
binding = PaybondCapabilityBinding(
harbor=paybond.harbor,
intent_id=UUID(str(intent_id)),
capability_token=capability_token,
)
guard = paybond_capability_input_guardrail(requested_spend_cents=18_700)
ctx = ToolContext(
binding,
tool_name="book_hotel",
tool_call_id="call-1",
tool_arguments='{"city":"Lisbon","nightly_budget_cents":18700}',
tool_namespace="travel",
)
decision = await guard.run(
ToolInputGuardrailData(context=ctx, agent=SimpleNamespace(name="hotel-booker"))
)
if decision.behavior["type"] != "allow":
raise RuntimeError(f"tool denied: {decision.behavior}")
reservation = await book_hotel("Lisbon", 18_700)
submitted = await paybond.intents.submit_evidence(
UUID(str(intent_id)),
{"reservation": reservation},
payee_did=os.environ["PAYBOND_PAYEE_DID"],
payee_signing_seed=seed32_from_hex("PAYBOND_PAYEE_SEED_HEX"),
idempotency_key=f"evidence:{intent_id}",
)
print(
{
"intent_state": created["state"],
"guard": decision.behavior["type"],
"settlement_state": submitted["state"],
"predicate_passed": submitted.get("predicate_passed"),
}
)
finally:
await paybond.aclose()
asyncio.run(main())Where the OpenAI Agents SDK fits
The direct guard.run(...) call above uses the same objects that a real OpenAI Agents run uses:
PaybondCapabilityBindingcarries(harbor, intent_id, capability_token).paybond_capability_input_guardrail()performs HarborPOST /verify.ToolContext.qualified_tool_namebecomes theoperationstring, sotravel.book_hotelmust appear in the intent allow-list exactly.
When you wire this into a live agent run, pass context=PaybondCapabilityBinding(...) into Runner.run(...) and attach the guardrail to the tool definition.
Expected Harbor outcome
createreturnscapability_tokenonly after the intent is funded.submit_evidencereturns a state pluspredicate_passed.- Release vs refund confirmation remains an operator-tier action in the current Kit surface. See Harbor API for settlement confirmation.
Common failure cases
- Missing
capability_token: the intent is not funded yet. - Guardrail rejection:
allowed_toolsdid not match the tool name, spend exceeded the capability, or the token is invalid. - Tenant mismatch: treat as severity-zero; do not retry with another client or tenant id.
- Evidence rejection: the payee seed does not match the payee DID bound on the intent, or the payload does not satisfy the predicate/schema.
Reference implementation
- Example app:
examples/paybond-kit-openai-agents-python/app.py - Example README:
examples/paybond-kit-openai-agents-python/README.md