Skip to main content
Anvil’s Cost Governor gates every model call in an agent run against a per-request spending budget. Set a budget on defineAgent and the runtime automatically tracks cumulative token usage and USD cost, checking the budget before each iteration of the agent loop. When a cap is breached you choose the response: throw an error, switch to a cheaper model, or route the run to human approval.

Configuring a budget on defineAgent

Pass a BudgetConfig to the budget option on defineAgent. The governor is instantiated per request, so limits apply to a single agent run, not across all users.
import { defineAgent } from 'anvil/agent';
import { openai } from 'anvil/llm';

export const post = defineAgent({
  client: openai({ model: 'gpt-4o' }),
  budget: {
    maxTokens: 4000,
    maxUsd: 0.10,
    onBreach: 'block',
  },
  // ...
});

BudgetConfig fields

maxTokens
number
Hard cap on the total number of tokens (input + output) consumed by this run. The governor checks cumulative usage before each model call; once the count equals or exceeds this value, the configured onBreach action fires.
maxUsd
number
Hard cap on the total USD cost for this run. Computed from per-call costUsd values reported by the LLM client. Like maxTokens, this is checked before each model call.
onBreach
'block' | 'degrade' | 'approve'
default:"'block'"
Action to take when a cap is hit:
  • 'block' — throws BudgetExceededError immediately, stopping the run and returning a 500 to the client.
  • 'degrade' — signals that the run should continue on a cheaper model. The onBreach callback on CostGovernor receives the breach info so your code can switch models.
  • 'approve' — suspends the run and routes it through the human-in-the-loop approval queue, just like a tool that throws ApprovalRequiredError.

BudgetExceededError

When onBreach is 'block', the runtime throws a BudgetExceededError. Catch it in error-handling middleware to return a structured response:
import { BudgetExceededError } from 'anvil/trace';

// In a global error handler or try/catch around the agent run:
try {
  // ... agent logic
} catch (err) {
  if (err instanceof BudgetExceededError) {
    console.warn(
      `Budget exceeded: $${err.spentUsd.toFixed(4)} spent, ` +
      `${err.spentTokens} tokens used. ` +
      `Limit: maxUsd=${err.limit.maxUsd}, maxTokens=${err.limit.maxTokens}`
    );
    return Response.json({ error: 'Budget exceeded' }, { status: 429 });
  }
  throw err;
}
BudgetExceededError exposes three properties:
spentUsd
number
Total USD cost accumulated by this run at the time of the breach.
spentTokens
number
Total tokens (input + output) consumed by this run at the time of the breach.
limit
BudgetConfig
The BudgetConfig that was in effect — useful for building informative error messages.

Using CostGovernor directly

defineAgent wires CostGovernor for you when you pass a budget. For custom agent loops built with runAgent or streamAgent, construct and pass the governor explicitly:
import { runAgent } from 'anvil/agent';
import { CostGovernor } from 'anvil/trace';

const governor = new CostGovernor(
  { maxUsd: 0.05, maxTokens: 2000, onBreach: 'block' },
  (info) => {
    // Called for 'degrade' and 'approve' — not for 'block'.
    console.log(`Budget breach (${info.action}): $${info.spentUsd.toFixed(4)}`);
  },
);

const result = await runAgent({
  client,
  messages,
  governor,
  // ...
});

CostGovernor methods

governor.record(usage, costUsd?)
method
Accumulate usage from a completed model call. Called automatically by the Anvil agent runtime; call it manually only if you are driving the LLM client directly.
governor.assertWithinBudget()
method
Check the current spend against configured caps. Returns 'ok' if within budget, throws BudgetExceededError if onBreach is 'block', or calls the onBreach callback and returns the action for 'degrade' and 'approve'.
governor.isOverBudget()
boolean
Returns true if the current accumulated spend has reached or exceeded any configured cap. Does not throw or trigger the breach action.
governor.spentUsd
number
Read-only accessor for the total USD cost accumulated so far.
governor.spentTokens
number
Read-only accessor for the total token count accumulated so far.

Tracking cost across LLM calls

The LLM client reports costUsd per generate call based on the model’s pricing. The governor accumulates these values across every iteration of the agent loop. Aborted streams (client disconnects mid-response) are counted too, so a disconnect never creates a cost blind spot.
Set both maxTokens and maxUsd together to guard against runaway loops independently of pricing changes. A maxTokens cap fires predictably regardless of the model’s cost per token.

Example: degrade to a cheaper model on breach

import { defineAgent } from 'anvil/agent';
import { CostGovernor } from 'anvil/trace';
import { openai } from 'anvil/llm';

const premiumClient = openai({ model: 'gpt-4o' });
const fallbackClient = openai({ model: 'gpt-4o-mini' });

export const post = defineAgent({
  // resolveClient picks the model dynamically based on request state
  resolveClient: (ctx) => ctx.state.useFallback ? fallbackClient : premiumClient,
  budget: {
    maxUsd: 0.20,
    onBreach: 'degrade',
  },
  // ...
});
When onBreach is 'degrade', the CostGovernor fires its onBreach callback with { action: 'degrade', spentUsd, spentTokens }. Wire that callback to update request state or swap the active client.