Shotoku
← Blog

How to set a hard spending limit on an AI agent

· By the Shotoku team

Short answer:check the budget in code before every paid call, and refuse the call when it would go over. Use two limits: a cap per call for one expensive request, and a rolling daily cap for loops of cheap ones. Billing alerts and instructions in a prompt don’t do this, because neither one blocks the next call.

Why don’t alerts and prompts stop overspending?

MethodBlocks the next call?The problem
Rule in the promptNoThe model can misread it, ignore it or be talked out of it
Billing alertNoIt tells you after the money is spent
Provider spend capSometimesCoverage varies by provider, and it usually applies to a whole project, not one agent
Check in code before the callYesYou have to route every paid call through it

Even a budget setting may only be an alert. GitHub’s Copilot budgets, for example, stop usage only if you turn that option on.

A loop of small calls is the case to plan for. Each call looks harmless, so a per-call cap never fires. One widely repeated story describes agents looping for 11 days and running up a $47,000 bill. We couldn’t trace it to a primary source, but the failure it describes is real: no limit sat in front of the next call.

How do you add a hard limit?

Install Shotoku and create a policy:

bash
npm install -g shotoku-cli
npm install @shotoku/core
shotoku init

Set a limit per call and per day for each vendor. Anything you haven’t listed waits for a human:

policy.yaml
rules:
  - resource: api.openai.com
    actions: [api_call]
    verdict: approved
    maxAmount: 2
    maxDailyAmount: 10

defaultVerdict: pending_approval

Then check before each paid call:

agent.ts
import { authorize } from "@shotoku/core";

const decision = await authorize(
  {
    actor: "research-agent",
    action: "api_call",
    resource: "api.openai.com",
    amount: estimatedCost,
  },
  { policyPath: "./policy.yaml", ledgerPath: "./data/decisions.jsonl" },
);

if (!decision.approved) {
  throw new Error(`Blocked: ${decision.status} [${decision.decisionId}]`);
}

await callTheApi();

What happens when an agent hits the limit?

We ran this policy on the current npm release, with an agent looping on $1.50 calls:

RequestResult
Six calls at $1.50approved, $9 spent
Seventh call at $1.50denied: $10.50 would exceed the $10 daily limit
One call at $3denied: over the $2 limit
$0.10 to a vendor not in the policypending_approval: waits for a human
Any call with the policy file missingdenied

The last row matters most. If something goes wrong with the policy, the agent is refused rather than waved through. The check is deterministic too: the same request, policy and history always give the same answer, with no model involved.

What are the limits of this approach?

Common questions

What should a daily limit be set to?

Start from what a normal day costs and leave some headroom. Too tight and a good run gets blocked. Too loose and a loop runs for longer. Check your history after a week and adjust.

Should an over-limit call fail or wait for a human?

For a background agent, failing is usually right, because nobody is there to answer. If a person is available and the work matters, route it for approval instead. See the refund example.

Does this send my spending data anywhere?

No. The policy and the decision record are local files, and Shotoku holds no funds or keys. It makes the decision and your code makes the payment.

Next, try the quickstart or read the policy reference.

Related