paybondpaybond
Sign in

OpenAI Agents SDK agent spend controls

OpenAI Agents SDK agent spend controls — input guardrails, wrapped invoke, and auto-evidence via @paybond/openai-agents and paybond_kit.openai_agents. TypeScript and Python parity.

Paybond integrates with the OpenAI Agents SDK at the tool input guardrail and invoke boundary — not as model middleware. spend verify runs before side-effecting tools execute; auto-evidence fires after success.

The Python SDK uses the same guardrail and invoke hooks via openai-agents on PyPI.

Install

Install

npm install @paybond/openai-agents @openai/agents

Import from @paybond/openai-agents

import { createPaybondOpenAIAgentsConfig } from "@paybond/openai-agents";
  • Equivalent subpath on the core package: `@paybond/kit/openai-agents` — use `@paybond/kit` when you need multiple adapters in one app.
  • Python: `paybond agent demo openai-agents smoke` requires the optional `openai-agents` extra. Use `pip install "paybond-kit[openai-agents]"`, `pipx install 'paybond-kit[openai-agents]'`, or `pipx inject paybond-kit openai-agents` (when base paybond-kit is already installed).

One-liner (sandbox): paybond.instrument({ policy, framework: "openai-agents", tools, sandbox: true }) or paybond.agent({ policy, framework: "openai-agents", tools }) returns guarded tools plus runConfig for Runner.run.

TypeScript

import { tool } from "@openai/agents";
import { z } from "zod";
import { Paybond } from "@paybond/kit";

const paybond = await Paybond.open({ apiKey: process.env.PAYBOND_API_KEY! });

const bookHotelTool = tool({
  name: "travel.book_hotel",
  description: "Book a hotel room",
  parameters: z.object({
    city: z.string(),
    estimatedPriceCents: z.number().int().nonnegative(),
  }),
  execute: async (args) => bookHotel(args),
});

const { agentTools: tools, runConfig } = await paybond.instrument({
  policy: "./paybond.policy.yaml", // or preset id "travel"
  framework: "openai-agents",
  tools: [bookHotelTool, searchWebTool],
});

// Runner.run(agent, input, { ...runConfig })

Python

from agents import function_tool
from paybond_kit import Paybond

paybond = await Paybond.open(api_key=os.environ["PAYBOND_API_KEY"])

@function_tool
async def book_hotel(city: str, estimated_price_cents: int) -> str:
    ...

runtime = await paybond.instrument(
    {
        "policy": "./paybond.policy.yaml",
        "framework": "openai-agents",
        "tools": [book_hotel, search_web_tool],
        "sandbox": True,
    }
)
tools = runtime.tools
run_config = runtime.hooks.run_config

# await Runner.run(agent, input, run_config=run_config)

Already bound a run? paybond.wrapTools(run, tools, { framework: "openai-agents" }) / paybond.wrap_tools(run, tools, framework="openai-agents").

See Agent middleware for policy files, registry rules, and tenant isolation.

Advanced / manual wiring

When you need step-by-step control over registry and bind, use both primitives from createPaybondOpenAIAgentsConfig / create_paybond_openai_agents_config:

PrimitivePurpose
inputGuardrails / tool_input_guardrails on side-effecting toolsPre-execution spend verify, deny, HITL hold
Wrapped invoke / on_invoke_toolPost-success spend finalize and auto-evidence
runConfig.toolExecution.preApprovalInputGuardrails / RunConfig(tool_execution=ToolExecutionConfig(pre_approval_tool_input_guardrails=True))Enable guardrails before OpenAI approval interruptions

TypeScript

import { tool } from "@openai/agents";
import { z } from "zod";
import { Paybond, createPaybondToolRegistry } from "@paybond/kit";
import { createPaybondOpenAIAgentsConfig } from "@paybond/kit/openai-agents";

const paybond = await Paybond.open({ apiKey: process.env.PAYBOND_API_KEY! });

const registry = createPaybondToolRegistry({
  defaultDeny: true,
  sideEffecting: {
    "travel.book_hotel": {
      spendCents: (args: { estimatedPriceCents: number }) => args.estimatedPriceCents,
      evidencePreset: "cost_and_completion",
      evidenceMapper: (result) => ({
        status: result.reservation.status === "confirmed" ? "completed" : result.reservation.status,
        cost_cents: result.reservation.price_cents,
      }),
    },
  },
});

const run = await paybond.agentRun.bind({
  bootstrap: {
    kind: "sandbox",
    operation: "travel.book_hotel",
    requestedSpendCents: 20_000,
    completionPreset: "cost_and_completion",
  },
  registry,
});

const bookHotelTool = tool({
  name: "travel.book_hotel",
  description: "Book a hotel room",
  parameters: z.object({
    city: z.string(),
    estimatedPriceCents: z.number().int().nonnegative(),
  }),
  execute: async (args) => bookHotel(args),
});

const { tools, runConfig } = createPaybondOpenAIAgentsConfig(run, [bookHotelTool, searchWebTool]);

// Runner.run(agent, input, { ...runConfig })

Python

from paybond_kit import Paybond
from paybond_kit.agent.registry import create_paybond_tool_registry
from paybond_kit.openai_agents import create_paybond_openai_agents_config

paybond = await Paybond.open(api_key=os.environ["PAYBOND_API_KEY"])

registry = create_paybond_tool_registry(
    {
        "default_deny": True,
        "side_effecting": {
            "travel.book_hotel": {
                "spend_cents": lambda args: int(args.get("estimatedPriceCents", 0)),
                "evidence_preset": "cost_and_completion",
                "evidence_mapper": lambda result, _ctx: {
                    "status": result.get("status"),
                    "cost_cents": result.get("cost_cents"),
                },
            }
        },
    }
)

run = await paybond.agent_run.bind(
    {
        "bootstrap": {
            "kind": "sandbox",
            "operation": "travel.book_hotel",
            "requested_spend_cents": 20_000,
            "completion_preset": "cost_and_completion",
        },
        "registry": registry,
    }
)

config = create_paybond_openai_agents_config(run, [book_hotel_tool, search_web_tool])
# Runner.run(agent, input, run_config=config.run_config)

createPaybondOpenAIAgentsConfig / create_paybond_openai_agents_config

  1. Adds a Paybond input guardrail to each side-effecting registered tool.
  2. Wraps invoke with run.interceptor.wrapExecute for auto-evidence.
  3. Returns runConfig with pre-approval input guardrails enabled.

Read-only tools pass through without guardrails or wrapped invoke.

Optional: bridge OpenAI needsApproval

Pass { bridgeNeedsApproval: true } / PaybondOpenAIAgentsAdapterOptions(bridge_needs_approval=True) to also require OpenAI human-in-the-loop approval after Paybond pre-check passes.

Approval hold and retry

When Harbor returns approvalRequired, the input guardrail rejects with a structured message. After operator approval:

run.storeApprovalToken(toolCallId, approvalTokenFromConsole);
// Retry Runner.run with the same run binding.
run.store_approval_token(tool_call_id, approval_token_from_console)
# Retry Runner.run with the same run binding.

Scaffold and smoke

# TypeScript
paybond init agent-middleware --framework openai --out ./paybond-openai-agents.ts

# Python
paybond init agent-middleware --framework openai --out ./paybond_openai_agents.py

paybond agent demo openai-agents smoke \
  --operation paid-tool \
  --requested-spend-cents 100 \
  --evidence-preset cost_and_completion \
  --format json

The smoke command runs guardrail pre-check and guarded invoke directly — no OpenAI API key in CI.

Python optional extra: pip install "paybond-kit[openai-agents]". With pipx, quote extras on zsh: pipx install 'paybond-kit[openai-agents]' or pipx inject paybond-kit openai-agents.

Example app (TypeScript): examples/paybond-kit-openai-agents-typescript.