COMPARISON

    xBPP + Anthropic Tool Use

    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.

    These aren't competing products

    First, a clarification. Anthropic tool use and xBPP solve different problems:

    • Anthropic tool use lets Claude decide when to call a function and what arguments to pass. It's a model capability. The model picks a tool from a list and produces a structured call.
    • xBPP lets your code decide whether a payment should actually execute after Claude has decided to make it. It's a policy engine. It evaluates the payment against declarative rules and returns ALLOW, BLOCK, or ESCALATE.

    They compose. Claude decides. xBPP evaluates. Your code either executes the payment or escalates it.

    The problem xBPP solves for Claude agents

    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:

    1. Pre-computed limits baked into the tool definition
    2. Checks inside the tool's implementation
    3. System prompt instructions telling Claude not to spend above $X

    None of these work well at scale:

    • Limits in the tool definition become stale and tied to a specific agent.
    • Checks inside the tool are scattered and hard to audit.
    • Instructions in the system prompt are not a security control - they can be bypassed by prompt injection, misunderstood on long contexts, or simply ignored under certain inputs.

    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.

    Composition pattern

    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.

    Feeding the verdict back to Claude

    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.

    Why policy-in-prompt doesn't work

    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:

    • Claude can misinterpret limits on long contexts or under adversarial inputs.
    • Prompt injection in tool results or retrieved documents can override the instructions.
    • There's no audit trail you can point at when something goes wrong.
    • Changing the limit means editing prompt text in multiple places.

    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.

    Feature comparison

    xBPPAnthropic Tool Use
    CategoryPolicy engineModel capability
    Lives whereYour code, between model and payment railInside Claude's inference
    Decides...Whether a payment should executeWhen and what to call
    DeterministicYes (same input = same verdict)No (model output varies)
    DeclarativeYes (JSON policies)No (runtime-generated calls)
    Audit trailStructured verdict logClaude message history
    Prompt-injection-resistantYesNo - prompts can be influenced
    Human-in-the-loopFirst-class (ESCALATE)App-level
    Composes withAny payment railAny tool you define

    When to use xBPP with Claude

    Any time a Claude tool can move money, you want xBPP in the loop. Specifically:

    • When your agent can make x402 payments
    • When your agent can initiate on-chain transfers
    • When your agent can call Stripe / payment APIs
    • When your agent has any variable spending authority
    • When you need an auditable record of why each transaction was allowed or blocked

    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.

    FAQ

    Do I need to change my tool definitions?

    No. xBPP runs after Claude returns a tool_use block, before you execute it. Your tool definitions stay exactly as they are.

    Does xBPP call Claude?

    No. xBPP is synchronous, local, and has zero dependencies. It never makes network calls and knows nothing about the model.

    Can Claude override an xBPP BLOCK?

    No. That's the point. The policy runs outside Claude's control and is not something a prompt can talk it out of.

    Does xBPP work with Claude's streaming API?

    Yes. Evaluation happens at the point you process each tool_use block, which works identically in streaming and non-streaming modes.

    Does xBPP support the Claude Agent SDK?

    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.

    Further reading