How cost is counted
Every Claude session in spwn keeps a running account of what it has spent. After each turn, spwn reads the agent’s transcript, counts the tokens, prices them, and writes the result into the session’s PR doc, a file committed on the session’s own branch. The spend lands in the same commit as the work it paid for.
You get three things from this:
- A number on every session. It shows in the sidebar, on the Fleet, in Compare, and in the session header, so you can see what each approach is costing while it runs.
- Figures you can add up. Forking copies a conversation, so naive totals double-count. Each session also records its own share, and adding those up across a fork family gives the exact spend.
- Cost that travels with the branch. It’s plain TOML in the repo. A teammate, a CI job, or a script with no spwn installed can read it. See Reading cost outside spwn.
Where you see it
Section titled “Where you see it”| Where | What it shows |
|---|---|
| Session header (the chip in the agent bar) | The whole conversation’s usd, or N req when the model has no price. Hover for requests and output tokens. Click it to open the PR tab. |
| Sidebar (a chip on each session row) | The session’s own spend. Hovering the ↑N ahead chip also shows the cost summary. |
| Fleet rows | The session’s own spend, with the total and own figures in the tooltip. |
| Fleet peek (Space) | The same summary as text, in the facts line under the last message. |
| Compare lanes | Each lane’s own spend in its header, so forks compare on what they did after the split. |
| Inspector → PR | The full breakdown: dollars, requests, the model, transcript count, input, output, cache read and cache write, a per-model table, and the own/inherited split for a fork. Spend across this problem expands to a group rollup. |
| The pull request on GitHub | When spwn opens or updates a PR, the body’s spwn section ends with a Cost to produce line. |
Rows that show the own figure fall back to a request count (42 req) when there’s
no dollar figure to show.
What gets counted
Section titled “What gets counted”spwn scans the transcript files for the session’s agent session id:
~/.claude/projects/…/<id>.jsonl, plus any sibling agent-*.jsonl files that belong to
it. Those siblings are where Claude Code writes subagent work. Skipping them would
miss a large share of a session that delegates.
For every request in those files it records five token counts: input, output, cache read, and cache writes at the 5-minute and 1-hour TTLs. The two cache-write TTLs stay separate because they bill at different rates. Two details keep the counts honest:
- Each API request counts once. Claude Code writes one response as several
transcript entries, so spwn deduplicates by
requestId. Where the entries disagree (subagent files record streaming partials), it keeps the largest value of each field. - Rewinds don’t lose spend. A rewind or resume can move a session to a fresh agent
session id. spwn remembers every id the session has spent under, in the
sessionslist, and scans all of them together. Anything they share is still counted once.
Total, own and inherited
Section titled “Total, own and inherited”A fork starts from a copy of its parent’s conversation, and the child’s transcript carries that shared history too. So the doc records two figures:
tokensis the conversation end to end, inherited prefix included. Use it when you’re looking at one branch on its own.ownis the part that first appeared in this session. Use it when you’re adding sessions up.
Say a parent makes 10 requests and forks. The parent then makes 5 more, and the child makes 3 of its own:
tokens.requests |
own.requests |
|
|---|---|---|
| Parent | 15 | 15 |
| Child | 13 | 3 |
| Sum | 28, with the shared 10 counted twice | 18, the real number |
Summing own is exact because of how spwn measures the inherited part. On the child’s
first turn after the fork, it intersects the child’s request ids with the parent’s, and
the tokens of those shared requests become the child’s inherited baseline. That
baseline is written into the doc once and then frozen. From then on,
own = tokens − inherited.
Measuring by request id rather than by the parent’s total has two benefits:
- Rewind-then-fork is exact. When you fork from an earlier turn, the child copies only the transcript up to that turn, which can end well before the parent’s head. The intersection finds exactly the requests that were copied, however far back that is.
- The number doesn’t move afterwards. Freezing means the parent can keep working, or be deleted, without changing the child’s figures. No turn has to walk the ancestry again.
A root session inherited nothing, so its doc leaves own out. An absent own means
it equals tokens.
When spwn can’t tell
Section titled “When spwn can’t tell”If spwn can’t measure the inherited prefix, the doc says ownUnknown = true and records
no own at all. That happens when the parent’s transcript is gone before the child’s
first turn, or when the child shares no request ids with it. tokens is still
recorded. A sum should leave such a session out rather than count its whole
conversation, because part of that conversation is already counted on the parent.
Own dollars
Section titled “Own dollars”ownUsd is the session’s usd multiplied by its share of the requests
(own.requests / tokens.requests). The frozen baseline keeps tokens, not a per-model
split, so dollars are divided by request count. Own tokens sum exactly. Own
dollars are a close estimate, and so are the dollars themselves.
One model, or several
Section titled “One model, or several”Most sessions use one model. The doc then names it with model = "…" and lets tokens
stand for it.
A session that used more than one model, such as an Opus main thread plus Haiku
subagents, gets a byModel table instead of model. It has one entry per model id,
each with the same token fields and its own usd when the model has a rate. Fast mode
shows up as a separate id with a #fast suffix, because it bills at its own rate (see
Model prices).
When it updates
Section titled “When it updates”spwn recomputes cost once per completed turn. It runs before the session-turn
hooks, so the bundled per-turn commit picks up the new figures in that turn’s commit
rather than a turn later. Each commit on a session branch carries the cost as of that
commit, and git log -p .spwn/pr/ gives you the spend history.
The update is idempotent. The cost value has no timestamp, so if nothing new was
spent the file isn’t touched and the diff is empty. The same step also tidies stale PR
docs out of the worktree (see
after landing), and that removal rides the
same commit.
cost belongs to spwn. The PR tab leaves it out of the front-matter editor, and a hand
edit would be overwritten on the next turn. Every other key in the file is yours.
The cost block
Section titled “The cost block”Here is a real-shaped PR doc for a fork that used Opus and Haiku. The front matter sits
between +++ fences and is TOML. problem is a key you add yourself (see
rollups), and cost is written by spwn:
+++problem = "checkout-latency"
[cost]files = 3ownUsd = 24.6861sessions = ["0f3c9a52-7d1e-4b6a-9e2f-5c8d1a7b4e90"]usd = 37.3339
[cost.byModel.claude-haiku-4-5]cacheRead = 0cacheWrite = 0cacheWrite1h = 0cacheWrite5m = 0input = 38912output = 6120requests = 31usd = 0.0695
[cost.byModel.claude-opus-5]cacheRead = 41882905cacheWrite = 1242631cacheWrite1h = 1030587cacheWrite5m = 212044input = 1204output = 187431requests = 214usd = 37.2644
[cost.inherited]cacheRead = 12410773cacheWrite = 490780cacheWrite1h = 402660cacheWrite5m = 88120input = 540output = 61208requests = 83
[cost.own]cacheRead = 29472132cacheWrite = 751851cacheWrite1h = 627927cacheWrite5m = 123924input = 39576output = 132343requests = 162
[cost.tokens]cacheRead = 41882905cacheWrite = 1242631cacheWrite1h = 1030587cacheWrite5m = 212044input = 40116output = 193551requests = 245+++
# Cut checkout latency by caching the tax lookup…A root session with one model is shorter. It has model instead of byModel, and no
own, ownUsd or inherited:
[cost]files = 1model = "claude-opus-5"sessions = ["6b254ae6-c327-43f6-a071-e19fc5baad4d"]usd = 5.9289
[cost.tokens]cacheRead = 6120455cacheWrite = 188920cacheWrite1h = 170512cacheWrite5m = 18408input = 310output = 41877requests = 58Keys come out in alphabetical order. A small value is written inline
(cost = { usd = 1.5 }), and anything wider than 120 characters becomes a
[section], which a real cost almost always is. To a TOML parser both forms are the
same, so read them with a parser rather than a regex.
| Key | Present | Meaning |
|---|---|---|
usd |
when at least one model had a rate | Estimated dollars for the whole conversation, to four decimals. |
tokens |
always | The whole conversation: input, output, cacheRead, cacheWrite (= cacheWrite5m + cacheWrite1h), cacheWrite5m, cacheWrite1h, requests. |
own |
forks whose prefix was measured | The part that first appeared in this session. Same fields as tokens. Absent means equal to tokens. |
ownUsd |
with own, when usd exists |
usd apportioned by request share. |
inherited |
with own |
The frozen baseline copied from the parent. |
ownUnknown |
forks spwn couldn’t measure | true. Leave this session out of a sum. |
model |
exactly one model used | Its id. |
byModel |
more than one model used | Per-model token fields, plus usd for priced models. |
unpriced |
when a model had no rate | Model ids whose tokens are counted but not priced. |
sessions |
always | Every agent session id this doc has spent under. More than one after a rewind. |
files |
always | How many transcript files the scan read: the main one, plus one per subagent. A sudden drop means lost subagent transcripts, not a cheaper session. |
Limits
Section titled “Limits”- Claude only. Cost comes from Claude Code’s transcript format. Codex and Gemini
sessions get no
costkey at all. A missing key means “not measured”, not a zero. - Nothing until the first turn. A session that hasn’t finished a turn has no transcript to read, so the PR tab says No cost recorded yet.
- Some pricing modifiers aren’t applied. spwn prices fast mode separately, but it
doesn’t apply batch or regional (
inference_geo) rates. Neither shows up in Claude Code’s interactive sessions. - Rate changes apply going forward. The next turn reprices the whole session at the current table. Figures already committed stay as they were.