← Back to Build Log
budgetcost-controlsecuritypolicytutorial

Your AI Agent Just Spent $50,000: How to Set Budget Limits That Work

In February 2026, an engineering team running an autonomous research agent discovered a $47,000 charge on their OpenAI bill. The agent had entered a retry loop, calling a token-heavy generation endpoint thousands of times over a weekend before anyone noticed. The model was doing exactly what it was told. There was no authorization layer asking whether the cumulative cost of those calls was acceptable.

This is not an edge case. It is a predictable failure mode of any agent that has access to paid APIs, cloud resources, or financial instruments. Agents do not have an intuitive sense of cost. They do not stop because a budget feels high. They stop when you build something that stops them.

Why Framework-Level Cost Tracking Is Not Enough

Most agent frameworks offer some form of token counting or cost estimation. This is useful for visibility. It is not sufficient for enforcement. There are a few reasons.

First, framework-level counters are advisory. They tell you what was spent. They do not have a mechanism to block the next call when a threshold is crossed. You would need to write that logic yourself, inline, in every agent.

Second, they track model costs but not tool costs. An agent that calls a third-party enrichment API at $0.05 per record and runs it against a 100,000-row dataset is not incurring any LLM token cost. The framework has no visibility into what the tool call actually cost.

Third, they do not operate at the authorization boundary. By the time a framework-level counter fires, the call has already been made. Budget enforcement needs to happen before the action, not after.

The Right Layer: Per-Principal Budget Enforcement

Budget limits belong at the authorization layer, attached to the principal (the agent identity) rather than embedded in the agent's code. When an agent attempts an action, the authorization check should include: has this principal's budget for this window already been exhausted?

Authensor's policy engine supports budget rules as a first-class concept alongside allow/deny/review decisions. Here is what a budget-aware policy looks like:

version: "1"
default: deny

rules:
  - action: generate_image
    principal: "agent:content-creator"
    effect: allow
    budget:
      perCall: 0.04        # $0.04 per image
      daily: 10.00         # $10/day total for this agent
      monthly: 150.00      # $150/month hard cap

  - action: enrich_contact
    principal: "agent:sales-researcher"
    effect: allow
    budget:
      perCall: 0.05
      daily: 25.00
      weekly: 100.00

  - action: run_sql_query
    principal: "agent:analyst"
    effect: allow
    budget:
      daily: 50.00         # Cloud query costs
      monthly: 500.00

  - action: generate_*
    principal: "agent:content-creator"
    effect: allow
    budget:
      daily: 50.00         # Aggregate daily cap across all generation tools

Each budget rule tracks spending against the named window. When a call comes in, the engine checks the principal's current spend against the budget before returning an ALLOW decision. If the budget is exhausted, the call is denied with a BUDGET_EXCEEDED reason rather than a policy rule denial.

Installing and Configuring

npm install @authensor/sdk
export AUTHENSOR_API_KEY=your_api_key

Costs are reported as part of the action envelope when the agent submits an intent:

import { AuthensorClient } from "@authensor/sdk";

const authensor = new AuthensorClient({
  apiKey: process.env.AUTHENSOR_API_KEY,
});

async function callEnrichmentAPI(contactId: string) {
  // Submit the intent with the estimated cost before making the call
  const decision = await authensor.authorize({
    principal: "agent:sales-researcher",
    action: "enrich_contact",
    args: { contactId },
    cost: 0.05,  // cost per call in USD
  });

  if (decision.effect !== "allow") {
    throw new Error(`Action denied: ${decision.reason}`);
  }

  // Proceed with the actual API call
  const result = await enrichmentApi.enrich(contactId);

  // Confirm the actual cost after the call completes
  await authensor.confirmCost(decision.receiptId, { actualCost: 0.05 });

  return result;
}

For tools where cost is only known after execution (cloud queries, variable-length generation), you can report the actual cost in the confirmation step. The engine tracks the estimated cost against the budget when the intent is submitted, then reconciles with the actual cost when confirmed.

Daily, Weekly, and Monthly Windows

The budget engine maintains rolling windows independently. A daily cap resets at midnight UTC. A weekly cap resets on Monday. A monthly cap resets on the first of the month. Windows are per-principal, so different agents have independent counters even if they call the same action.

// Check current budget status for an agent
const budget = await authensor.budget.status({
  principal: "agent:content-creator",
  action: "generate_image",
});

console.log({
  dailySpent: budget.daily.spent,        // e.g., 7.20
  dailyRemaining: budget.daily.remaining, // e.g., 2.80
  dailyLimit: budget.daily.limit,         // e.g., 10.00
  monthlySpent: budget.monthly.spent,
  windowResets: budget.daily.resetsAt,   // ISO timestamp
});

You can surface this in your agent's reasoning context so the agent can self-regulate before hitting a hard stop. An agent that knows it has $2.80 left for the day can decide to batch remaining calls more efficiently rather than hitting a DENY at call 12.

Per-Action Cost Caps

Beyond aggregate windows, you can set per-call cost caps to catch unexpectedly expensive individual calls:

rules:
  - action: run_analysis
    principal: "agent:analyst"
    effect: allow
    budget:
      perCall: 2.00      # No single analysis call should cost more than $2
      daily: 100.00

When the agent submits the intent with an estimated cost higher than perCall, the action is denied before the call is made. This catches cases where an agent has constructed an unusually large query or is about to invoke a tool with arguments that would generate a massive response.

Escalation Instead of Hard Stops

Hard denials are appropriate for daily and monthly caps. But you might want a softer signal before the agent hits a wall. You can configure budget rules to send an alert at a threshold below the hard cap, or to route to REVIEW when spend is high but not exhausted:

rules:
  - action: generate_image
    principal: "agent:content-creator"
    effect: allow
    budget:
      daily: 10.00
      alerts:
        - threshold: 0.75    # Alert at 75% of daily cap ($7.50)
          channel: "webhook:https://hooks.yourcompany.com/budget-alert"
        - threshold: 0.90    # Route to review at 90% ($9.00)
          escalate: review

At 90% of the daily cap, the next call is REVIEW instead of ALLOW. A human can decide whether to approve the remaining spend or call it a day. This prevents the agent from silently burning through the last 10% of a budget without anyone knowing.

Aggregate Caps Across a Fleet

Individual agent budgets are useful. Aggregate caps across a fleet are essential when you are running many agents concurrently:

globalBudgets:
  - scope: "agent:*"              # All agents
    action: "generate_*"           # All generation actions
    daily: 500.00                  # $500/day total across the fleet
    monthly: 5000.00

  - scope: "agent:content-*"      # Just content agents
    action: "*"
    daily: 200.00

Global budgets operate as a second gate. Even if an individual agent is within its own budget, the call is denied if the aggregate fleet budget is exhausted. This prevents a scenario where ten agents each stay within their individual $50/day cap but collectively burn $500 in a morning.

Try It

Budget enforcement, per-principal windows, per-call cost caps, alert thresholds, and fleet-level aggregate limits are all part of Authensor's policy system. The engine is open source. Run npx create-authensor to scaffold a project and explore the budget policy syntax in the docs. Full reference at authensor.com/docs. If you have a cost runaway scenario that is not covered by the current policy model, open an issue on GitHub and describe it — the policy language is designed to be extended.