Agent budget control: a copy-paste policy template

A concrete, per-tier budget policy template for agent commerce — what to cap per call, per agent, per endpoint, per day, and how to escalate denials to a human without breaking the agent loop.

Every agent commerce rollout ends up needing the same shape of budget policy. This is that shape, written down once so you can paste it. It uses @harpd/agent-budget-policy, runs entirely locally, and degrades gracefully when a budget is hit.

The tiers

We size budgets by trust level, not by feature. A sandbox agent and a production agent need the same code paths — they differ only in their money ceilings.

Tierper callper agent / dayper endpoint / dayper tool / hour
sandbox$0.01$1$0.50$0.10
staging$0.05$10$2$0.50
production$0.20$100$20$5
enterpriseconfigured per-dealconfiguredconfiguredconfigured

The numbers aren’t magic. They’re the cheapest caps that still let a productive agent do real work, derived from the cases we wrote up in the risk post.

The template

import { defineBudget, type Policy } from '@harpd/agent-budget-policy'

export const TIERS = {
  sandbox:     { perCall: 0.01, perAgent: 1,   perEndpoint: 0.50, perTool: 0.10 },
  staging:     { perCall: 0.05, perAgent: 10,  perEndpoint: 2.00,  perTool: 0.50 },
  production:  { perCall: 0.20, perAgent: 100,  perEndpoint: 20,   perTool: 5 },
} as const

export type Tier = keyof typeof TIERS

export function policyFor(tier: Tier, opts: {
  allowedTokens?: string[]
  agentId?: string
}): Policy {
  const t = TIERS[tier]
  return defineBudget({
    perCall:  { usdMax: t.perCall },
    perAgent: { [opts.agentId ?? '*']: { usd: t.perAgent, window: '1d' } },
    perEndpoint: { '*': { usd: t.perEndpoint, window: '1d' } },
    perTool:  { '*': { usd: t.perTool, window: '1h' } },
    allowedTokens: opts.allowedTokens ?? ['eip155:8453/erc20:0xUSDC'],
  })
}

How denials should feel to an agent

A denial is not an error — it’s a polite redirect. The agent should see a structured response it can route around:

{
  "error": "budget_exceeded",
  "scope": "per-endpoint",
  "endpoint": "https://api.weatherapi.com/v1/current.json",
  "spent": 0.50,
  "limit": 0.50,
  "window": "1d",
  "retryAfter": "2026-07-28T00:00:00Z"
}

A well-behaved agent reads retryAfter and either waits, picks a cheaper tool, or escalates to a human. A badly-behaved agent loops — which is exactly what perCall.usdMax exists to catch, because every retry still proposes a payment and every proposed payment still hits the policy.

The escalation rule

When an agent hits per-agent cap, the right behavior is usually not “stop” — it’s “ask a human.” Harpd ships a webhook for this:

import { harpdPlugin } from '@harpd/observe'

harpdPlugin({
  apiKey: process.env.HARPD_KEY!,
  endpoint: 'https://api.harpd.com',
  onBudgetExceeded: (event) => {
    // Default: email the team owner + post to your Slack #pay-alerts.
    // Override: route to PagerDuty for production tier only.
    if (event.tier === 'production') notifyPagerDuty(event)
  },
})

The webhook fires once when a budget first crosses, not on every denial — that’s deliberate. Spam-free escalation is the difference between a control that gets kept and a control that gets disabled.

What the policy does NOT do

  • It doesn’t meter LLM tokens. Token cost is the provider’s problem; conflating it with tool-call cost is how teams lose track of both.
  • It doesn’t define spend governance (approvals, vendor lists). That lives in Harpd’s hosted policy engine — it’s a separate concern.
  • It doesn’t try to predict spend. Predictions in agent commerce have a 0% track record. Caps are forecasts you can audit.

Next