Skip to content

Budget

Enforce hard limits on agent spend — steps, tokens, and dollars.

A Budget tracks usage across one or more call() or agent() calls and throws BudgetExhausted when a limit is hit.

Creating a budget

ts
import { budget } from 'flint/budget';

const b = budget({
  maxSteps: 10,       // max number of LLM calls
  maxTokens: 50_000,  // max total tokens (input + output + cached)
  maxDollars: 0.50,   // max spend in USD
});

At least one limit must be set. All limits are optional individually.

BudgetLimits

ts
type BudgetLimits = {
  maxSteps?: number;
  maxTokens?: number;
  maxDollars?: number;
};

Budget interface

ts
type Budget = {
  readonly limits: BudgetLimits;
  consume(x: ConsumeInput): void;
  assertNotExhausted(): void;
  remaining(): BudgetRemaining;
};

type ConsumeInput = Partial<Usage> & { cost?: number };

type BudgetRemaining = {
  steps?: number;
  tokens?: number;
  dollars?: number;
};

You rarely call consume() or assertNotExhausted() directly — call() and agent() do it for you. Use remaining() to inspect headroom.

Checking remaining budget

ts
const b = budget({ maxSteps: 5, maxDollars: 0.10 });

await agent({ ..., budget: b });

const rem = b.remaining();
console.log(`Steps left: ${rem.steps}`);    // 5 - steps used
console.log(`Dollars left: $${rem.dollars?.toFixed(4)}`);

BudgetExhausted

When a limit is hit, call() or agent() returns { ok: false, error: BudgetExhausted }.

ts
import { BudgetExhausted } from 'flint/errors';

const res = await agent({ ..., budget: b });

if (!res.ok) {
  if (res.error instanceof BudgetExhausted) {
    console.log('Hit budget limit:', res.error.message);
  }
}

BudgetExhausted.code is one of 'budget.steps', 'budget.tokens', 'budget.dollars'.

Reusing a budget

A budget is stateful. To share a limit across multiple agent calls (e.g. total session cost):

ts
const sessionBudget = budget({ maxDollars: 1.00 });

await agent({ ..., budget: sessionBudget });
await agent({ ..., budget: sessionBudget }); // continues depleting the same budget

Reusing a budget across calls

The budget object is stateful. Pass the same instance to multiple call() or agent() calls to enforce a cumulative cap:

ts
const sessionBudget = budget({ maxDollars: 1.00 });

// Each call consumes from the same pool
const r1 = await agent({ ..., budget: sessionBudget });
const r2 = await agent({ ..., budget: sessionBudget });

console.log('Remaining:', sessionBudget.remaining());
// → { dollars: 0.78 } (if both calls spent $0.11 and $0.11)

budget() throws on invalid options

budget() requires at least one limit. Passing an empty object throws:

ts
budget({}); // TypeError: budget: at least one of maxSteps, maxTokens, or maxDollars must be set

Dollar cost availability

cost is only available in CallOutput / AgentOutput if the adapter reports it. The Anthropic adapter always reports cost. If adapter.capabilities.cost is false, cost will be undefined and maxDollars will never be exhausted.

Common mistakes

budget is required for agent()

agent() requires a budget argument. There's no default — this is intentional. Agents without budget limits can run indefinitely.

See also

Released under the MIT License.