How to cap what one agent run can spend
PythonNode.jsA provider spend cap is bound to a project, or to an organization over a calendar month. A single 3-hour research loop is neither. AgentBill's ceiling is bound to a task_ref, consulted before each call, on units you define.
What a monthly cap is bound to
Provider spend caps are real and they fire. What they are bound to is a project, or an organization over a calendar month. So either the run is too small to move a monthly number, or the number low enough to catch it takes every agent in the organization down with it until the 1st. AgentBill's ceiling is bound to one task instead: if the units already used plus this call's estimate would cross it, preflight answers approved: false and the SDK raises before your provider call goes out.
Install
pip install agentbill-sdk
Give the run an id, and give that id a ceiling
Two parameters do the work. task_ref is your name for this run, and every call that passes it is checked against the same ceiling. task_ceiling is that ceiling, in units you define. The preflight that names a new run opens it, or the console does before the run starts; once the run exists a value from code is not applied, so a retry cannot raise the ceiling it was meant to respect, and the console's last save is the one in force.
from agentbill import AgentBillClient client = AgentBillClient(api_key="agb_your_key") # 1 unit = 1 cent here, so this run has $5 across every call, # tool and retry that passes the same task_ref. client.preflight( agent_id="researcher", # a label for attribution, not a budget task_ref="job-142", # your name for this run task_ceiling=500, # the ceiling for the whole run estimated_units=12, # what this one call is worth ) # a refused call raised TaskCeilingExceededError above; nothing to check here result = run_agent() # Settle, or the units stay held until the reservation expires client.record(agent_id="researcher", task_ref="job-142", units=12)
Every later call in the same run passes task_ref and nothing else about the budget. It does not need to know the ceiling, or what the calls before it spent.
# A different agent, a different tool, the same run and the same ceiling.
client.preflight(agent_id="writer", task_ref="job-142", estimated_units=40)Use the @gate decorator (shortest path)
The @client.gate() decorator does the preflight before the body and the record after it, and it takes the same two parameters. On an exception it settles with success=False, which releases the reservation instead of billing it.
@client.gate(agent_id="researcher", task_ref="job-142",
task_ceiling=500, estimated_units=12)
def run_agent(task: str) -> str:
# preflight runs before this body, record runs after it
return do_the_work(task)Handle the refusal
The exception carries the numbers, so the handler can say what happened without a second call.
from agentbill import TaskCeilingExceededError
try:
result = run_agent("analyze this")
except TaskCeilingExceededError as e:
return {"error": f"run {e.task_ref} hit its ceiling of {e.task_ceiling} units"}What a per-request ceiling is, and is not
There is a second, narrower ceiling: ceiling on the client refuses any single call whose estimated_units exceed it. It is a sanity check on one bad estimate. It is not the cross-call ceiling above, and on its own it lets through a loop of two hundred individually reasonable calls.
# No single call may cost more than 50 units. The run still needs a task_ceiling.
client = AgentBillClient(api_key="agb_your_key", ceiling=50)And note what is not on this list. agent_id is a label the console groups tasks and refusals by; it carries no budget of its own. Ceilings are bound to a run, a customer, or a single call, never to an agent name.
Node.js
import { preflight, record } from 'agentbill' // reads AGENTBILL_API_KEY
// The run has 500 units across every call that passes job-142.
// A refused call throws, so your catch clause decides what happens next.
await preflight({ agentId: 'researcher', taskRef: 'job-142',
taskCeiling: 500, estimatedUnits: 12 })
const result = await runAgent()
await record({ agentId: 'researcher', taskRef: 'job-142', units: 12 })Related guides
Task budgets, a hard cost ceiling per agent job How to cap what one agent run can spend Every setup failure, and its fix Integrations: the OpenClaw plugin, the MCP server and framework guides LangChain, one ceiling per job in middleware OpenAI Agents SDK, one ceiling per job in RunHooks CrewAI, one ceiling per crew run in model-call hooks