Star on GitHub

Docs

Get started

One binary, one base-URL swap, five minutes to your first costed request. Routing is opt-in and earned — you start by simply seeing your bill.

Install

TokenTriage is a single Apache-2.0 Go binary — no CGO, no sidecar runtime. Build it from source (a prebuilt release binary works too):

git clone https://github.com/imankamyabi/tokentriage.git
cd tokentriage

# One static binary; the pricing table is compiled in via go:embed.
CGO_ENABLED=0 go build -o dist/tokentriage ./cmd/tokentriage

Quickstart

The fastest start is a zero-config ledger proxy. It observes and costs every request and changes nothing about the response:

# Zero-config ledger proxy for Claude Code — pure observation, no routing.
tokentriage run --anthropic

# Listens on 127.0.0.1:8787 (data plane)
#          and 127.0.0.1:9090 (admin: /health, /metrics, /tt/api/status)

TokenTriage speaks the OpenAI (/v1/chat/completions) and Anthropic (/v1/messages) dialects, and the proxied bytes are identical to going direct — streaming included.

See your bill

Point your app’s base URL at the proxy. Nothing else in your code changes:

from openai import OpenAI

# The only change to your app: point the base URL at TokenTriage.
client = OpenAI(base_url="http://localhost:8787/v1")

resp = client.chat.completions.create(
    model="gpt-5",
    messages=[{"role": "user", "content": "Summarize this ticket."}],
)

Or straight over HTTP:

curl http://localhost:8787/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-5",
    "messages": [{"role": "user", "content": "Hello"}]
  }'

For Claude Code specifically, wrap sets ANTHROPIC_BASE_URL and injects a fresh trace id for you:

# Exec a Claude Code session pointed at the proxy, with a trace id injected
# so every request in the session groups into one trace.
tokentriage wrap --agent claude -- claude

Now explore the dashboard on http://localhost:9090/ui, or runtokentriage top for terminal rollups. Every dollar is attributed by model, token type, trace, agent, and tool.

Turn on routing

Routing is opt-in. The honest path is to run in shadow first, read the savings report, and only then enforce. You can start routing with no config file at all:

# Turn on routing with no config file: a cheap tier and a complex tier.
tokentriage run \
  --simple  ollama/llama3.1:8b \
  --complex anthropic/claude-sonnet-4-6

Configuration

For anything real, use a YAML config. Upstreams are named; tiers are ordered cheapest to most capable; secrets are always environment-variable names, never values:

# tokentriage.yaml — secrets are always env-var NAMES, never literals.
upstreams:
  together:
    provider: together_ai
    base_url: https://api.together.xyz/v1
    api_key_env: TOGETHER_API_KEY

# Ordered tiers, cheapest → most capable.
tiers:
  - name: cheap
    upstream: together
    model: meta-llama/Llama-3.1-8B-Instruct-Turbo
  - name: capable
    upstream: anthropic
    model: claude-sonnet-4-6

# Start in 'ledger'. Climb to 'shadow', then 'route', as the evidence allows.
mode: ledger

Validate it strictly before you run — an unknown field is an error, not a silent ignore:

tokentriage validate --config configs/tokentriage.example.yaml

The trust ladder

The mode setting is the whole safety story. You climb one rung at a time:

  • ledger — observe and cost every request. Zero request mutation. No client impact.
  • shadow — also compute the would-route decision and its counterfactual cost, so report shows the savings you’d get — without touching a response.
  • route — enforce decisions: rewrite the model, pick the cheaper upstream, honor cache pins.

Preview the whole thing safely first:

# Boot the whole dashboard against a watermarked synthetic ledger.
tokentriage demo

Providers

Four wire dialects are compiled into the binary — OpenAI, Anthropic, Bedrock, and Gemini. Every OpenAI-compatible upstream (Groq, Together, Fireworks, OpenRouter, a self-hosted vLLM / Ollama / llama.cpp server, and your own) rides the OpenAI driver with zero new code: adding one is N+1-by-config.

Deploy

Container image is FROM scratch, ≈11 MB (estimated), non-root:

docker run --rm \
  -p 8787:8787 -p 9090:9090 \
  -e ANTHROPIC_API_KEY \
  -v "$HOME/.tokentriage:/data" \
  tokentriage:latest

The canonical Kubernetes path is the Helm chart (one StatefulSet, per-pod PVC):

helm install tt deploy/helm/tokentriage \
  --namespace tokentriage --create-namespace \
  -f my-values.yaml

Kustomize, Docker Compose (with a fully self-contained air-gap profile), and a single-VM Terraform module all live under deploy/. The chart renders zero Secret objects — configuration carries references, never values.

CLI reference

CommandWhat it does
runStart the proxy (ledger / shadow / route).
wrapExec an agent pointed at the proxy, with a trace id injected.
topLedger rollups as terminal tables.
reportShadow would-route savings report.
evalThe honesty verdict vs. the zero-router baseline.
calibrateFit routing thresholds from your own logs.
pricesInspect the embedded pricing snapshot.
validateStrictly validate a config file.
demoBoot the dashboard on a synthetic ledger.
keys mintMint a virtual key (governance).

This is the essential path. The full reference — the policy language, calibration, response caching, governance, and the OpenAPI contract — lives in the repository and theAPI reference.