Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

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.

How-to guideLast updated

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.