Read your spend from the terminal
top and report over the decision log: five groupings, a per-trace cost tree, shadow savings, and the three columns that mislead.
On this page
Two commands read your decision log and print a table. No database, no index, no configuration — they open the JSONL file and aggregate it, so they work on a fresh install, over a log copied off a server, and when everything else is off.
Every transcript below came from running tokentriage demo, whose synthetic ledger is driven
through the actual pricing pipeline against mock upstreams. The dollars are arithmetic over the
mock’s token counts, not anyone’s bill, and the shape is what your own log produces.
top — where the money went
$ tokentriage top --by model
MODEL REQS INPUT OUTPUT CACHE-R CACHE-W REASON CACHE-SAV COST P50 P90 P99
acme/private-model-1 35 97524 42521 7014 0 0 6.7% - - - -
anthropic/claude-haiku-4-5 57 67144 31370 9608 0 0 12.5% $0.224955 $0.003544 $0.009641 $0.012402
anthropic/claude-opus-4-8 61 12293266 43051 11830 0 0 0.1% $62.548520 $0.046033 $0.100000 $10.000000
anthropic/claude-sonnet-4-5 73 179383 74868 17746 0 0 9.0% $1.666493 $0.022888 $0.034886 $0.042655
gpt-4o-mini 12 11328 4200 3072 0 0 21.3% $0.003264 $0.000272 $0.000272 $0.000272
openai/gpt-4o 70 197323 65086 17454 0 0 8.1% $1.165985 $0.016161 $0.024690 $0.031442
openai/gpt-4o-mini 48 105755 41701 9539 0 0 8.3% $0.041599 $0.000994 $0.001615 $0.001854
openai/o3 56 135711 55296 14442 0 0 9.6% $0.721011 $0.013329 $0.018889 $0.025199
TOTAL 412 13087434 358093 90705 0 0 0.7% $66.371826
COST excludes 35 unpriced rows of 412 (no price resolved; the $0 is a missing price, not $0 of spend)
Two rows in that table are worth staring at before anything else.
acme/private-model-1 shows thirty-five requests, genuine token counts, and - in every dollar
column. Not one of its rows could be priced, so there is no figure to print and $0.000000
would be a lie. The line under the table counts them.
anthropic/claude-opus-4-8 shows a P99 of $10.000000 against a P50 of $0.046033. One
request in that group cost more than the other sixty put together, which is the shape of a
runaway job and exactly what percentiles are for.
The groupings
--by takes five values, and each answers a different question.
--by |
Groups on | Ask it when |
|---|---|---|
model |
the model on the request | which model is the bill |
agent |
the x-tt-agent header |
which agent or sub-agent is the bill |
step-class |
user_turn, tool_loop, provider_state |
whether the tool loop is eating the budget |
trace |
the x-tt-trace-id header |
which conversation was expensive |
tool |
MCP tool invocations | how much your tools cost you in tokens |
Rows with no value for the grouping dimension land in a labelled bucket named (none),
(standalone) or (unknown), rather than disappearing.
$ tokentriage top --by step-class
STEP-CLASS REQS INPUT OUTPUT CACHE-R CACHE-W REASON CACHE-SAV COST P50 P90 P99
tool_loop 70 177499 72880 16625 0 0 8.6% $1.060597 $0.010622 $0.033863 $0.065335
user_turn 342 12909935 285213 74080 0 0 0.6% $65.311229 $0.014169 $0.043115 $10.000000
TOTAL 412 13087434 358093 90705 0 0 0.7% $66.371826
COST excludes 35 unpriced rows of 412 (no price resolved; the $0 is a missing price, not $0 of spend)
Windowing
--since takes a duration or an absolute timestamp. Durations accept Go’s units plus day and
week shorthands, so 24h, 90m, 30d and 2w all work, as does any RFC 3339 timestamp.
$ tokentriage top --by model --since 24h
MODEL REQS INPUT OUTPUT CACHE-R CACHE-W REASON CACHE-SAV COST P50 P90 P99
acme/private-model-1 1 4097 614 67 0 0 1.6% - - - -
anthropic/claude-haiku-4-5 1 0 0 0 0 0 0.0% $0.000000 $0.000000 $0.000000 $0.000000
anthropic/claude-opus-4-8 2 5079 1608 886 0 0 14.9% $0.066038 $0.028682 $0.037356 $0.037356
anthropic/claude-sonnet-4-5 1 760 1089 205 0 0 21.2% $0.018676 $0.018676 $0.018676 $0.018676
gpt-4o-mini 12 11328 4200 3072 0 0 21.3% $0.003264 $0.000272 $0.000272 $0.000272
openai/gpt-4o 2 7128 2243 529 0 0 6.9% $0.040911 $0.017421 $0.023490 $0.023490
openai/gpt-4o-mini 1 850 1823 212 0 0 20.0% $0.001237 $0.001237 $0.001237 $0.001237
openai/o3 1 3067 1362 317 0 0 9.4% $0.017188 $0.017188 $0.017188 $0.017188
TOTAL 21 32309 12939 5288 0 0 14.1% $0.147315
COST excludes 1 unpriced row of 21 (no price resolved; the $0 is a missing price, not $0 of spend)
A row whose timestamp cannot be parsed is dropped from a windowed view, because it cannot be placed on the timeline. Unwindowed, it still counts.
One conversation, step by step
--trace renders a single trace as a tree, linked by parent step id, with per-step cost:
$ tokentriage top --trace tt_00000000000000000000000001
trace tt_00000000000000000000000001 (1 steps)
STEP CLASS AGENT MODEL COST IN OUT CACHE-R
tt_00000000000000000000000001 user_turn batch-runner anthropic/claude-opus-4-8 $0.100000 20000 0 0
TOTAL $0.100000 20000 0 0
The demo’s synthetic traces are one step deep. A client that sets x-tt-parent-id on delegated
calls gets an indented tree, and an unpriced step there carries the same - marker and stays
out of the total.
The three columns that mislead
Each of these is doing exactly what it says, and each is routinely read as something else.
CACHE-SAV is a token ratio, not a dollar. It is the cache-read share of input-side tokens
(100 × CACHE-R / (INPUT + CACHE-R)), and you can check it against the row: the gpt-4o-mini
line above shows 3,072 cache reads against 11,328 fresh input, and 3072 ÷ 14400 is 21.3%. It is
a ratio rather than money because this view cannot see the price table, so it reports what it
measured instead of a figure it would have to invent. Cache savings in dollars are a separate
and more careful calculation, in
two kinds of cache savings.
COST under --by tool is not the tokens the tool cost you. It is a flat per-call price for
the tool itself, and this build ships no way to configure one, so it prints $0.000000 for
every tool on every install.
$ tokentriage top --by tool
# tool_result tokens are an INPUT cost of the NEXT call; IN-TOK-ATTR is an ESTIMATE (char/4).
TOOL SERVER REQS CALLS RESULT-BYTES IN-TOK-ATTR COST
code_exec tools-mcp 35 35 108565 19697 $0.000000
web_search tools-mcp 70 108 284598 50213 $0.000000
TOTAL 105 143 393163 69910 $0.000000
The column that carries the signal is IN-TOK-ATTR: the input tokens a tool’s results cost
the next call, because a tool result is fed back into the following request. It is an estimate
(characters over four) and the header says so. For a tool loop’s actual dollars, use
--by step-class and read the tool_loop row.
Tool accounting is per turn. Agentic clients resend the whole conversation each turn, so each invocation is counted once, at the turn that first feeds its result back. An N-turn loop reports N calls rather than roughly N²/2.
report — what routing saved, or would save
report answers a different question: for each row, what would this have cost at the other
tiers, and what did routing actually pick?
$ tokentriage report
=== TokenTriage shadow-savings report ===
verdict: routing sits BELOW the observed-model frontier (Δ -0.0074) — served-model labels (quality source 'oracle') score only the tier that served each row, so routing cannot be shown to beat a blind model mix. Enable shadow duplication (shadow.duplicate) for a counterfactual routing-skill verdict; use calibrate to sweep thresholds.
routed rows: 35 of 412 (rows with a routing decision + would_usd)
not-routing spend: $2.306799 (served on the top tier)
routed spend: $0.720880 (under the routing decision)
savings: $1.585919 (68.7% vs not routing; 35 of 35 rows routed cheaper)
BY STEP-CLASS
STEP-CLASS REQS NO-ROUTE ROUTED SAVINGS SAVINGS%
user_turn 35 $2.306799 $0.720880 $1.585919 68.7%
TOTAL 35 $2.306799 $0.720880 $1.585919 68.7%
BY RULE
RULE REQS NO-ROUTE ROUTED SAVINGS SAVINGS%
default 13 $0.862162 $0.269428 $0.592734 68.7%
override 7 $0.434116 $0.135661 $0.298455 68.8%
pin 8 $0.513757 $0.160551 $0.353206 68.7%
simple-to-cheap 7 $0.496764 $0.155240 $0.341524 68.7%
TOTAL 35 $2.306799 $0.720880 $1.585919 68.7%
Two things about that output are the point of the command.
The denominator is disclosed. 35 of 412. Only rows carrying a routing decision and a
usable counterfactual license a savings claim. Rows on an endpoint that is not shadow-eligible,
or produced by a cross-dialect translation, are left out of the arithmetic and counted rather
than quietly folded in.
The baseline is named, and it is generous. not-routing spend prices every row at the most
expensive tier it could have used, which assumes clients would otherwise have asked for the top
tier. That is the top-tier-would baseline, and it is the right comparison when your clients
really do default to the best model and an overstatement when they do not.
The verdict line at the top is a routing verdict, not a savings verdict: a separate closed vocabulary answering whether routing beat a blind model mix. The two are explained side by side in two verdict vocabularies.
What can go wrong
report says 0 of N rows. Nothing in the log carries a counterfactual. That is the
normal state in ledger mode with no routing rules, because there was no other tier to price.
A warning: skipped N torn/corrupt line(s). The log is appended to by a live process, so a
partially written final line is expected and skipped. A large count on an idle process is worth
investigating.
The numbers disagree with the dashboard. They should not, and if they do, check you are
reading the same log: --log overrides the path, and without it both commands resolve the
configured default.
Where the log lives and how to point at another one
Both commands take --log, which accepts a file or a directory. Without it they resolve the
configured decision-log path, defaulting to ~/.tokentriage/decisions.jsonl.
That makes an incident workflow simple: copy the log off the affected host and run top against
it locally. Nothing else needs to come with it. The file is self-contained, and the costs in it
were computed when the rows were written.