> Discover all available pages from the documentation index: https://mastra.ai/llms.txt

# TokenCostControl

The `TokenCostControl` enforces monetary cost limits across the agentic loop, blocking or warning when a configurable cost threshold is exceeded.

It uses `processInputStep` to check the cost limit before each LLM call. Cost data is queried from the observability storage APIs (`getMetricAggregate`) for all scopes. For all scopes except `run`, it aggregates cost across runs within a configurable time window (defaults to 7 days). For `run` scope, it queries cost for the current trace.

For token-based limits, use `TokenLimiterProcessor` instead.

> **Renamed from `CostGuardProcessor`.** The `CostGuardProcessor` export (and its `CostGuard*` option and detail types) remains available as a deprecated alias for the same class, including the `'token-cost-control'` processor id. Migrate imports to `TokenCostControl`.

Supports six scoping modes:

- **Run scope**: Tracks cost within a single agent run via trace ID
- **Resource scope** (default): Tracks cumulative cost per `resourceId` across runs
- **Thread scope**: Tracks cumulative cost per `threadId` across runs
- **User scope**: Tracks cumulative cost per `userId` across runs
- **Organization scope**: Tracks cumulative cost per `organizationId` across runs
- **Session scope**: Tracks cumulative cost per `sessionId` across runs

> **Approximate cost control.** Cost data is persisted asynchronously via buffered exporters in the observability pipeline. Fast-running agents may exceed the configured limit before metrics are available for query. Treat `maxCost` as an approximate threshold that fast-running agents may exceed.

> **Agent attribution only.** Cost is attributed via the `entityType: 'agent'` metric filter. Model calls made outside an agent run (for example, direct model usage in workflow steps) have no agent parent span and aren't counted by this guard.

## Usage example

Track cumulative cost per resource (default scope):

```typescript
import { TokenCostControl } from '@mastra/core/processors'

const tokenCostControl = new TokenCostControl({
  maxCost: 1.0,
})
```

Track cumulative cost per thread with a 24-hour window and a soft warning at 80% of the limit:

```typescript
import { TokenCostControl } from '@mastra/core/processors'

const tokenCostControl = new TokenCostControl({
  maxCost: 5.0,
  scope: 'thread',
  window: '24h',
  warnAtPercent: 80,
})
```

Use a per-tier budget by passing a function as `maxCost`:

```typescript
import { TokenCostControl } from '@mastra/core/processors'

const tokenCostControl = new TokenCostControl({
  maxCost: requestContext => (requestContext?.get('tier') === 'pro' ? 10.0 : 1.0),
  scope: 'user',
})
```

Attach to an agent with an `onViolation` callback and a per-provider/model breakdown:

```typescript
import { Agent } from '@mastra/core/agent'
import { TokenCostControl } from '@mastra/core/processors'

const tokenCostControl = new TokenCostControl({
  maxCost: 5.0,
  scope: 'resource',
  window: '30d',
  strategy: 'warn',
  includeBreakdown: true,
})

tokenCostControl.onViolation = ({ detail }) => {
  console.log(
    `Cost ${detail.threshold} threshold for ${detail.scopeKey}: $${detail.usage}/$${detail.limit}`,
  )
  for (const entry of detail.breakdown ?? []) {
    console.log(`  ${entry.provider}/${entry.model}: $${entry.estimatedCost}`)
  }
}

const agent = new Agent({
  id: 'my-agent',
  name: 'my-agent',
  model: 'openai/gpt-5-nano',
  inputProcessors: [tokenCostControl],
})
```

## Constructor parameters

**maxCost** (`number | ((requestContext?: RequestContext) => number)`): Maximum estimated cost allowed (e.g. 0.50 for $0.50 USD). A number must be finite and positive. A function is called with the request's RequestContext on every check, enabling per-tier or per-user budgets; if it returns anything other than a finite positive number, the check is skipped for that request (fail-open) and a warning is logged. This is an approximate limit due to metric persistence delays.

**scope** (`'run' | 'resource' | 'thread' | 'user' | 'organization' | 'session'`): Scope for cost tracking. 'run' tracks cost within the current agent run via trace ID. 'resource' tracks cumulative cost per resourceId across runs (default). 'thread' tracks cumulative cost per threadId across runs. 'user', 'organization', and 'session' track cumulative cost per userId, organizationId, and sessionId respectively, read from the plain RequestContext keys 'userId', 'organizationId', and 'sessionId'. All scopes require observability storage with getMetricAggregate support. (Default: `'resource'`)

**window** (`'1h' | '6h' | '24h' | '7d' | '30d' | '365d'`): Time window for cost aggregation for all scopes except 'run'. (Default: `'7d'`)

**strategy** (`'block' | 'warn'`): Strategy when the cost limit is exceeded. 'block' aborts with a TripWire error. 'warn' logs a warning and calls onViolation at most once per request, then allows the step to proceed. (Default: `'block'`)

**message** (`string`): Custom message template for the abort reason. Supports {usage} and {limit} placeholders. (Default: `'Cost control: estimated cost limit exceeded ({usage}/{limit})'`)

**warnAtPercent** (`number`): Optional soft threshold as a percentage of maxCost (exclusive 0-100, e.g. 80). When the estimated cost reaches this percentage of the limit but is still below it, a warning is logged and onViolation is called once per request with threshold: "soft", regardless of strategy. Never aborts the step.

**includeBreakdown** (`boolean`): When true, violations (soft and hard) include a per-provider/model cost breakdown queried via getMetricBreakdown. The breakdown is capped at the top 10 provider/model groups ranked by aggregated token volume, not by spend. The breakdown query runs only when a violation trips, never on the happy path. If the configured store does not support breakdown queries or the query fails, the violation fires without the breakdown field. (Default: `false`)

## Instance properties

**id** (`'token-cost-control'`): Processor identifier.

**name** (`'Token Cost Control'`): Processor display name.

**onViolation** (`(violation: ProcessorViolation) => void | Promise<void>`): Callback invoked when a cost violation is detected, regardless of strategy. For the warn strategy and for soft thresholds, the guard calls it with a TokenCostControlViolationDetail (usage, limit, threshold, and optional breakdown) at most once per request per threshold level. Errors thrown by the callback on this path are caught and logged through the Mastra logger. For the block strategy, the processor runner invokes it with the TripWire metadata as the detail (see Error behavior) and silently catches callback errors. Use for side effects like alerting, logging to external systems, or emailing users.

**processInputStep** (`(args: ProcessInputStepArgs) => Promise<void>`): Checks cumulative estimated cost against the resolved maxCost before each LLM call. Queries observability storage for cost data: run scope filters by trace ID, all other scopes filter by their respective IDs with a time window. Calls abort() when the limit is exceeded (block strategy) or logs a warning (warn strategy). Cost checks are approximate due to metric persistence delays.

## Error behavior

When the `block` strategy is active (default), `TokenCostControl` calls `abort()` with `retry: false` when the cost limit is exceeded. The TripWire metadata includes:

- `processorId`: `'token-cost-control'`
- `usage`: Current cumulative usage (`estimatedCost`, `costUnit`)
- `maxCost`: The resolved cost limit for the request
- `scope`: The active scope
- `scopeKey`: The scope identifier for non-run scopes (if applicable)
- `threshold`: Always `'hard'`, since only the hard limit aborts
- `breakdown`: Per-provider/model cost entries (only when `includeBreakdown` is enabled and the breakdown query succeeds)

With the `warn` strategy, the hard-limit warning and `onViolation` callback fire at most once per request. Subsequent steps in the same request proceed without repeating the warning.

Numbers interpolated into violation messages are normalized to at most 6 decimal places, so messages never contain float precision artifacts.

## Scoping behavior

| Scope          | Tracks across runs | Filter                         | Requires context                         |
| -------------- | ------------------ | ------------------------------ | ---------------------------------------- |
| `run`          | No                 | `traceId` from current span    | Tracing context (automatic)              |
| `resource`     | Yes                | `resourceId` + time window     | `resourceId` in `RequestContext`         |
| `thread`       | Yes                | `threadId` + time window       | `threadId` in `RequestContext`           |
| `user`         | Yes                | `userId` + time window         | `userId` key in `RequestContext`         |
| `organization` | Yes                | `organizationId` + time window | `organizationId` key in `RequestContext` |
| `session`      | Yes                | `sessionId` + time window      | `sessionId` key in `RequestContext`      |

All scopes require observability storage with `getMetricAggregate` support. If the Mastra instance doesn't have observability storage configured, an error is thrown at registration time.

For `run` scope, the processor reads the trace ID from the current span's tracing context. If no tracing context is available, the check is skipped (fail-open).

For all other scopes, if the required context ID is missing at runtime, the check is skipped. Observability query failures are handled with a fail-open strategy: if a query fails, a warning is logged through the Mastra logger and the step proceeds.

> **The `user`, `organization`, and `session` scopes require annotated traces.** These scopes match metric records by their `userId`, `organizationId`, and `sessionId` fields, which are populated from span metadata on the trace (for example, via tracing options metadata). If your traces don't carry the matching metadata, these scopes match zero records and the guard never trips. Setting the RequestContext key alone isn't enough: both the RequestContext key (for scope resolution) and the span metadata (for cost attribution) must be present.

> **Note on metric persistence delay.** The observability pipeline uses buffered exporters that flush metrics asynchronously. A short delay exists between when an LLM call completes and when its cost metrics are available for query. During high-frequency agent execution, the cost control may not detect a limit breach until one or more steps after the actual cost exceeded the threshold.