docs
Guides

Understand what a run cost, and why

Follow one expensive run from the dollar figure all the way down to the exact bytes the model was sent — stats, the Observatory, and stella inspect. Entirely local, no API key.

A run cost more than you expected. Most tools can tell you that. Stella can tell you which model call, what it was sent, and what changed between that call and the one before it — from local records, with no network access and no API key.

This guide walks that chain top to bottom. Each step narrows the question.

The chain

stella stats                              # 1. which model, and how much
stella observe --open                     # 2. which run, and where the time went
stella inspect                            # 3. which call inside that run
stella inspect 42 --step 3                # 4. what that call was actually sent
stella inspect 42 --step 3 --diff --only system   # 5. what changed since last time

Everything below reads <workspace>/.stella/private/store.db and never writes.

1. Which model, and how much — stella stats

stella stats

Aggregates the local store per provider and model: total tokens, total cost, resolve rate, and a TOTAL row. It is the "where is the money going" view.

The three columns people miss are the cache-economics ones, and they are usually where a surprising bill is explained:

HIT%

Cache-read tokens over total input tokens. Prompt caching is 4–10× cheaper than fresh input, so this number is most of your bill's variance.

SAVED ($)

Estimated dollars saved by caching, at today's catalog rates. Signed — a negative value means the provider's cache-write premium outran the reads it bought, which is a real incident and not a rounding artifact.

TTL REWRITES

Calls whose cached prefix went cold because the gap since the session's previous call exceeded the provider's cache TTL, and got rewritten instead of read back. A session parked between turns pays the write premium again for nothing.

Two diagnoses worth knowing by heart:

  • HIT% near 0 with CACHE WR also 0, on an opt-in provider (Anthropic, Bedrock, Claude-via-OpenRouter) — the cache marker probably never reached the wire. Check the adapter config before concluding the model isn't cacheable.
  • CACHE WR nonzero but HIT% low — the prefix is being rewritten between turns instead of reused. Something upstream of the cache breakpoint is changing per turn. Step 5 finds out what.
# Everything with a suspiciously cold cache, as JSON.
stella stats --format json |
  jq '.[] | select(.cache_hit_rate < 0.2) | {provider, model, cache_hit_rate, cache_expired_rewrites}'

stella stats is this workspace. For the same numbers across every project on this machine, stella usage report reads the local hub at ~/.stella/usage.db. Local-only — nothing is uploaded.

2. Which run — the Observatory

stella observe --open

A local web dashboard over the same stores, served on 127.0.0.1 only, embedded in the binary — no install and no build step. Its executions tab gives you per-run drill-down, which is how you get from "this month cost $40" to "this run cost $4".

Take the execution id from the run you care about; the rest of this guide uses it.

The Observatory opens the stores strictly read-only and never exports anything. See the dashboard tour for what each tab shows.

3. Which call inside the run — stella inspect

stella inspect          # executions that have receipts
stella inspect 42       # that execution's model calls, with roles and seq numbers

Every model call records a receipt: the ordered list of context blocks it sent, plus the bytes of the blocks the event journal cannot otherwise resolve. stella inspect 42 lists them, so you rarely have to guess.

A single step can hold several calls, and the numbering is fixed:

--call-seq 0

The engine's own worker call. Always 0.

Default default

--call-seq 1

The overflow summarizer that may run during that step's compaction.

--call-seq 2+

The pipeline's management roles — triage, judge, plan, guidance, conversational.

Those auxiliary calls assemble prompts that exist nowhere else, so their receipt is the only record of what they sent. If a run's cost is concentrated in something other than seq 0, that is itself the finding.

4. What the call was sent

stella inspect 42 --step 3            # role-delimited transcript
stella inspect 42 --step 3 --full     # …with nothing elided

This reconstructs the exact Vec<CompletionMessage> the model received — system prompt included — and re-checks it against the digests taken at emission. The banner reports the verdict, and the two failure modes are kept separate because they mean very different things:

! N block(s) could not be resolved

A documented coverage gap — budget-abort synthetic tool results, discarded speculation, attachments. The rest of the transcript is still faithful.

!! N block(s) did NOT re-hash

The journal is torn or was altered. Treat the reconstruction as untrustworthy.

5. What changed since the previous call — --diff

This is the step that actually explains a cache regression, and it is the one capability here with no real equivalent elsewhere.

A transcript shows the sum. It never shows the delta. A system prompt is hundreds of stable lines, and finding the paragraph that moved by reading two of them side by side is not something a person does reliably.

stella inspect 42 --step 0 --diff --only system
--- execution 41 · turn 0 · step 12 · seq 0 (worker)
+++ turn 0 · step 0 · seq 0 (worker)
+1 added, -0 removed  (system messages)
@@ -14,3 +14,4 @@
 - Always read a file before editing it — never edit blind.
+- Prefer `apply_edits` for a change touching several files.
 - Make minimal, surgical edits.

That single added line is a plausible cause of a cold cache: anything that changes above the cache breakpoint invalidates the prefix for the whole turn.

Three baselines, all same-role (diffing a worker prompt against a judge prompt produces noise, not a delta):

prev

Whatever ran immediately before in the same role — the previous step, or the last call of the previous turn when this is a turn's first call. Searches the whole session, because a system prompt is byte-stable within an execution by design, so drift can only appear across turns.

Default default

first

The first call of this role in the session.

prompt

Your prompt exactly as you submitted it, before any context was added.

Why prompt is a baseline at all

The words you type are all you know exist when you submit them. The system prefix, recalled memories, rules, and skills are added afterwards, and no other view shows that mutation.

stella inspect 42 --step 0 --diff
--- prompt as submitted
+++ turn 0 · step 0 · seq 0 (worker)
+8729 added, -0 removed  (all messages)

Three words in, 8,730 lines out. That ratio is the honest answer to "why did a one-sentence prompt cost that much".

"no change" is a result, not a failure. --only system reporting the prompt byte-identical to the previous turn is a direct confirmation that the prompt-cache stability invariant is holding — which is exactly what you want to see when HIT% is healthy.

Making it a check instead of an investigation

Everything above has a JSON form, so the forensics can run unattended:

# Did the system prompt drift between turns? `base` reports which baseline
# actually resolved, since `prev` becomes `prompt` when nothing preceded the call.
stella inspect 42 --step 0 --diff --only system --format json |
  jq '{base, changed, added, removed}'

# Is the reconstruction trustworthy at all?
stella inspect 42 --step 3 --format json |
  jq '{verified, unresolved, digest_mismatches}'

The other cost question: was it worth it?

Dollars are half the story. stella scoreboard answers the other half without letting a model grade its own homework — model calls, characters a human had to type, follow-ups, and a verdict read from a pull request somebody actually merged or closed. An open PR is deliberately not a verdict.

stella scoreboard

The number worth steering on is $/resolved, not sticker price: a cheap model that needs three attempts is not cheap. See Examples & recipes for the profiles that trade those off.

Keeping the store from growing forever

store.db grows monotonically — every run appends executions, events, tool calls, context blocks, and receipts.

stella stats prune --older-than 90d --dry-run   # look first; deletion cascades
stella stats prune --older-than 90d --vacuum    # then keep a quarter of history

Prune is replication-safe by default: an execution whose telemetry has not yet reached the usage hub is never dropped, so pruning before a sync cannot silently destroy cost data you are still billing against. --force overrides that and is irreversible.

Next

On this page