Payment Governance for GPT Agents
The OpenAI Agents SDK gave a lot of teams a clean way to build multi-step GPT agents with tool calls, handoffs, and structured outputs. What it deliberately doesn't give you is a payment policy layer - because, like every good SDK, it's focused on the problem it solves and leaves governance to the application layer.
If your OpenAI-based agent is about to spend money, this is the page for you.
xBPP and the OpenAI Agents SDK don't overlap. One builds agents, the other governs what agents are allowed to spend.
pay() or transfer() function, xBPP decides whether each call actually executes.Think of xBPP as middleware that runs at the moment the SDK hands a tool call back to your code for execution. Your code already has to decide what to do with tool outputs - xBPP just gives you a better way to handle the ones that move money.
Here's the pattern, kept close to the idiomatic OpenAI Agents SDK shape. The pay tool is defined normally; xBPP lives in the tool's implementation.
import { Agent, tool } from '@openai/agents'
import { evaluate } from '@vanar/xbpp'
import policy from './policies/openai-agent.json'
const payTool = tool({
name: 'pay',
description: 'Send a USDC payment to a recipient',
parameters: {
type: 'object',
properties: {
amount: { type: 'number' },
recipient: { type: 'string' }
},
required: ['amount', 'recipient']
},
async execute({ amount, recipient }) {
// xBPP runs before any money moves
const verdict = evaluate(
{ amount, currency: 'USDC', recipient },
policy
)
if (verdict.decision === 'BLOCK') {
return {
status: 'blocked',
reasons: verdict.reasons,
message: verdict.message
}
}
if (verdict.decision === 'ESCALATE') {
const approved = await humanApproval.request({ amount, recipient, verdict })
if (!approved) return { status: 'declined_by_human' }
}
// ALLOW (or escalation approved): execute the payment
const result = await executePayment({ amount, recipient })
return { status: 'sent', txHash: result.hash }
}
})
const agent = new Agent({
name: 'research-agent',
model: 'gpt-4o',
instructions: 'You help users by buying data reports and API credits.',
tools: [payTool]
})Everything that isn't the evaluate() call is plain OpenAI Agents SDK code. If you remove the xBPP line, the agent still works - it just has no policy layer. That's the point: xBPP is additive and non-invasive.
The default instinct on OpenAI-based agents is to put spending rules in the instructions field:
Never spend more than $100 per day. Never pay recipients you haven't
seen before. If you're unsure, ask the user.This is not a security control. It's a suggestion, and the model is free to ignore it under the wrong inputs. Specifically:
xBPP runs deterministically outside the model. Same input always produces the same verdict. Every verdict is logged with structured reason codes. A policy change is a config change, not a prompt edit.
Use prompt instructions to guide the model toward sensible behavior. Use xBPP to enforce the rules that actually matter.
One of the nicer properties of tool-returning-structured-data is that you can let the agent reason about a policy block. When pay() returns { status: 'blocked', reasons: [...] }, the SDK feeds that back into the next model turn, and the agent can:
No exception handling, no brittle retry loops, no "agent got confused and crashed" failure mode. Just structured feedback the model can reason about.
If your agent uses the SDK's handoff feature to delegate work to a sub-agent, xBPP still works the same way - wherever the payment tool is defined, wrap its execution in evaluate(). Some patterns:
Use xBPP any time your agent can:
Skip xBPP if your agent is purely informational (search, summarize, answer questions). The value shows up at the exact moment your agent's decisions start costing money.
Yes. Wrap the payment tool's implementation in evaluate() the same way. The agent framework above it doesn't matter.
Yes. Function calling returns a structured tool call that you execute in your own code - that's where xBPP goes.
Yes. Each tool instance can load its own policy file. The SDK has no global state that would interfere.
Not by default, but you can emit the verdict as a structured log line that your existing tracing stack picks up. Most teams integrate it with whatever observability they already run.
Sub-millisecond. xBPP evaluation is local, synchronous, and has no network calls or external state.