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. TheCostGuardProcessorexport (and itsCostGuard*option and detail types) remains available as a deprecated alias for the same class, including the'token-cost-control'processor id. Migrate imports toTokenCostControl.
Supports six scoping modes:
- Run scope: Tracks cost within a single agent run via trace ID
- Resource scope (default): Tracks cumulative cost per
resourceIdacross runs - Thread scope: Tracks cumulative cost per
threadIdacross runs - User scope: Tracks cumulative cost per
userIdacross runs - Organization scope: Tracks cumulative cost per
organizationIdacross runs - Session scope: Tracks cumulative cost per
sessionIdacross 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
maxCostas 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 exampleDirect link to Usage example
Track cumulative cost per resource (default scope):
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:
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:
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:
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 parametersDirect link to Constructor parameters
maxCost:
scope?:
window?:
strategy?:
message?:
warnAtPercent?:
includeBreakdown?:
Instance propertiesDirect link to Instance properties
id:
name:
onViolation?:
processInputStep:
Error behaviorDirect link to 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 requestscope: The active scopescopeKey: The scope identifier for non-run scopes (if applicable)threshold: Always'hard', since only the hard limit abortsbreakdown: Per-provider/model cost entries (only whenincludeBreakdownis 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 behaviorDirect link to 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, andsessionscopes require annotated traces. These scopes match metric records by theiruserId,organizationId, andsessionIdfields, 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.