Payment Governance for Claude Agents
If you're building an agent on Claude, you're almost certainly using Anthropic's tool use API. It's Claude's mechanism for calling functions, querying external systems, and - increasingly - moving money. This page answers a question we get a lot: how do I add a policy layer to a Claude agent that can spend?
The short answer: xBPP slots in between Claude's tool call and whatever payment rail you're using, without touching your tool definitions. Let's walk through it.
First, a clarification. Anthropic tool use and xBPP solve different problems:
They compose. Claude decides. xBPP evaluates. Your code either executes the payment or escalates it.
Claude is good at deciding when to call a tool. It's not responsible for deciding whether that call is safe - that's on you. When the tool in question is pay() or transfer() or checkout(), "you" means some mix of:
None of these work well at scale:
xBPP moves the enforcement out of the model and out of your tool code, into a declarative policy that runs deterministically on every call. Claude can request anything it wants; xBPP decides what actually happens.
Here's the minimal pattern for adding xBPP to a Claude tool-use agent. The tool definition stays the same - xBPP is invisible to Claude.
import Anthropic from '@anthropic-ai/sdk'
import { evaluate } from '@vanar/xbpp'
import policy from './policies/claude-agent.json'
const client = new Anthropic()
const tools = [
{
name: 'pay',
description: 'Send a USDC payment to a recipient',
input_schema: {
type: 'object',
properties: {
amount: { type: 'number' },
recipient: { type: 'string' },
memo: { type: 'string' }
},
required: ['amount', 'recipient']
}
}
]
async function runAgent(userMessage: string) {
const response = await client.messages.create({
model: 'claude-opus-4-6',
max_tokens: 4096,
tools,
messages: [{ role: 'user', content: userMessage }]
})
for (const block of response.content) {
if (block.type === 'tool_use' && block.name === 'pay') {
const tx = {
amount: block.input.amount,
currency: 'USDC',
recipient: block.input.recipient,
memo: block.input.memo
}
// xBPP evaluates the tool call before execution
const verdict = evaluate(tx, policy)
switch (verdict.decision) {
case 'ALLOW':
return await executePayment(tx)
case 'BLOCK':
return { status: 'blocked', reasons: verdict.reasons }
case 'ESCALATE':
const ok = await humanApproval.request(tx, verdict)
return ok ? executePayment(tx) : { status: 'declined' }
}
}
}
}That's the whole integration. Tool definitions unchanged, Claude unchanged, policy lives in JSON.
One nice side effect of this pattern: when a transaction is blocked or escalated, you can feed the structured reason codes back to Claude as a tool_result and let it recover - try a smaller amount, pick a different recipient, or explain to the user why it can't proceed.
if (verdict.decision === 'BLOCK') {
const nextResponse = await client.messages.create({
model: 'claude-opus-4-6',
max_tokens: 4096,
tools,
messages: [
{ role: 'user', content: userMessage },
{ role: 'assistant', content: response.content },
{
role: 'user',
content: [{
type: 'tool_result',
tool_use_id: block.id,
content: `Blocked by policy. Reasons: ${verdict.reasons.join(', ')}. Message: ${verdict.message}`
}]
}
]
})
}Now Claude sees that its payment was blocked, understands why, and can produce a recovery plan instead of failing silently. This is much better UX than catching an exception and returning an opaque error.
A common first instinct is to put spending rules in the system prompt:
You can make payments, but never more than $50 per day and never to
unknown recipients. If in doubt, ask the user.This feels like it should work, and for simple cases it often does. But it's not a security boundary:
Treat system prompts as a guideline for the model and treat xBPP (or any deterministic policy engine) as the actual enforcement. Both are useful; only one is authoritative.
Any time a Claude tool can move money, you want xBPP in the loop. Specifically:
If your Claude agent is purely read-only - searching, summarizing, answering questions - you don't need xBPP. It's specifically for the moment an agent's decisions start costing money.
No. xBPP runs after Claude returns a tool_use block, before you execute it. Your tool definitions stay exactly as they are.
No. xBPP is synchronous, local, and has zero dependencies. It never makes network calls and knows nothing about the model.
No. That's the point. The policy runs outside Claude's control and is not something a prompt can talk it out of.
Yes. Evaluation happens at the point you process each tool_use block, which works identically in streaming and non-streaming modes.
Yes. The Agent SDK's tool-calling flow is the same shape, so the composition pattern above applies directly. Put the evaluate() call wherever you execute the tool.