An over-budget call
never runs.
Cap spend before a call executes — block, downgrade, or degrade gracefully — and get cost attribution per feature and per user, for free. No proxy, no server: a decorator.
from cendor.tokenguard import budget, track, report @budget(usd=0.50, on_exceed="block") # raises BEFORE an over-budget call def answer(q): with track(feature="support", user_id="alice"): return client.chat.completions.create(model="gpt-4o", ...) report(group_by=["feature", "user_id"]) # spend per tag — for free report().assert_under(usd=0.05, feature="search") # cost as a test assertion
See it in action.
Block, downgrade, truncate, raise — or your own policy.
@budget(usd=…, on_exceed=…) wraps any function. Pre-flight modes project the cost of the call about to run and act before a token is spent; post-flight modes account exactly and act on the next call.
Projects the cost of the call about to run; raises BudgetExceeded before a token is spent.
@budget(usd=0.50, on_exceed="block") def answer(q): ... # over the cap → BudgetExceeded, raised BEFORE the call ($0 spent)
Reroutes to a cheaper model (e.g. gpt-4o → gpt-4o-mini) before the call.
@budget(usd=0.50, on_exceed="downgrade") def answer(q): ... # over the cap → rerouted gpt-4o → gpt-4o-mini, pre-flight
Degrades gracefully once the cap is crossed.
@budget(usd=0.50, on_exceed="truncate") def answer(q): ... # past the cap → the work degrades instead of stopping
Exact accounting; stops the NEXT call in a runaway loop.
@budget(usd=0.50, on_exceed="raise") def answer(q): ... # exact accounting — the NEXT call in the loop is the one that stops
Your policy.
def my_policy(ctx): ... @budget(usd=0.50, on_exceed=my_policy) def answer(q): ...
@budget(usd=0.50, on_exceed="block") def answer(q): ... # over the cap → BudgetExceeded, raised BEFORE the call ($0 spent)
Budgets nest — the tightest applicable cap wins; an inner downgrade never masks an outer hard cap. Attribution rides contextvars, so tags survive nested and async calls.
Watch it block, live.
A simulated agent loop under @budget(usd=0.50). Each iteration is one gpt-4o call costing $0.09. Run it past the cap and watch the mode decide what happens.
▸ each click simulates one gpt-4o call ($0.09) · enforcement happens before the call runs
Every dollar gets a feature and a user.
Wrap work in track(feature=…, user_id=…) and every call inside carries those tags — attribution rides contextvars, so tags survive nested and async calls.
unpriced_calls: 0
Every instrumented call is priced and tagged from ambient context — report(group_by=…) aggregates it; assert_under() turns cost into a CI assertion.
Where the edges are — by design.
block and downgrade project cost from offline token estimates plus an output reserve — they're approximate. Post-flight raise is exact, but stops the NEXT call, not the one that breached.
A streamed call is accounted when the stream is drained, not when it starts; a loop launching many streams eagerly can outrun post-flight modes (pre-flight still applies).
contextvars flow through async naturally; a plain threading.Thread starts fresh — use copy_context() for worker threads.
An unknown model warns (UnpricedModelWarning) and is counted in unpriced_calls; configure(on_unpriced="raise") makes block-mode reject such calls outright.
One decorator between you and a $400 surprise.
tokenguard docs → · cost-assertion cookbook → · composes with the Cendor stack · see it live in Cendor Monitor →
Compose tokenguard and the other six libraries into a governed agent — Agent, run, sessions, RAG, multi-agent — with the cendor-sdk. Same objects, re-exported; no migration.
npx @cendor/init / uvx cendor-init writes the rules files for you. Point it at the call-shape trap sheet, or connect the MCP server in agent mode.