local-first · private-first · exact
Every message, every tool call, every model — silently adding up on someone else's server.TokenOps tracks exact usage, enforces budgets, and keeps every byte on your machine.
the problem, in real time
scenario: rogue coding agent
↑ real session data format — budget guard stops it at the limit, every time.
how it works
OpenCode
A native plugin hooks the runtime. Every message.updated carries real cost + tokens — zero effort, zero config.
Claude Code · Cursor · Windsurf
A local HTTP proxy reads the usage field in every provider response. No binary touched, no license risk.
Kiro · Copilot · managed
For managed backends with no configurable endpoint: the agent calls the MCP tools itself.
the guard
Four states, like a real circuit breaker. It doesn't kill your workflow at the limit — it warns you first, downgrades gracefully, and only stops when there's no other choice.
Requests pass through untouched.
Passes, logged — you see it coming.
Model capped / swapped for a cheaper one.
HTTP 429 — the agent never reaches the provider.
which agent? which tier?
| agent | tier | how | tokens | needs steering? |
|---|---|---|---|---|
| OpenCode | 2a | Native plugin · in-process · automatic | exact | no |
| Claude Code | 2b | Anthropic protocol via local gateway | exact | no |
| Cursor | 2b | OpenAI-compatible base URL | exact | no |
| Windsurf | 2b | Point base URL at localhost:8787 | exact | no |
| Kiro | 1 | AWS-managed LLM · can’t intercept | estimate | yes |
start now
# 1 — install (one package, two tools)
npm i -g @finoptix/tokenops-mcp
# 2 — point any agent at the gateway
tokenops-gateway
→ serving on http://localhost:8787
# 3 — or let OpenCode track itself (native plugin)
opencode plugin -g path/to/tokenops.ts
# 4 — check the ledger
sqlite3 ~/.tokenops/usage.db "SELECT model, cost_usd FROM usage_events;"
→ 8 rows · exact cost, per call, yours onlyinside the mcp server
Everything the MCP server exposes. Your agent can call these itself — that's how the steering tier works on managed backends.
Log a single LLM call — model, tokens, cost, metadata. Cost auto-computed from tokens when you skip it.
Remaining allowance for daily, monthly or session periods — limits, used, and left.
Dollar limit per period plus the over-limit action: allow, warn, degrade, block.
Cheapest model that can handle a task. Auto-classifies complexity and checks budget before routing.
Per-session cost summary — duration, calls, tokens, breakdown by model.
Monthly report: cost breakdown, top models, complexity mix, savings opportunities.
read this before assuming anything:
track_usage · get_budget · session_report · run_audit — autonomous. Pure data, no guard, no steering.
set_budget — writes config, doesn't apply it. Enforcement happens at the call site: gateway (tier 2b), Sayay runtime guardrails, or the agent following instructions (tier 1). Without one of those, a budget protects nothing.
route_llm — recommends, doesn't impose. Someone must follow it: the agent calls it and uses the result (steering), or the gateway uses it internally.
none of these is the guard. Sayay sits at the call site — reads get_budget, respects set_budget, and decides allow / warn / degrade / block before the request ever reaches the provider.
what's next
A week of real usage tracked across OpenCode, Claude Code, Cursor and Windsurf — the data decides what ships next.
In-process budget guards for Bedrock AgentCore, Lambda, K8s, Cloudflare Workers, Strands, LangChain, Vertex — same breaker, no HTTP proxy.
Optional centralized dashboard: multi-agent budgets, team view, alerting. Local-first stays the default.
First-party plugins for more agent runtimes, so exact tracking spreads beyond OpenCode.