# How Roscoe accounts for AI cost

This page covers where the dollar on a run comes from, what it means, and how
far to trust it. Spending caps enforce against these same numbers, so the
accuracy matters beyond display.

## Why an agent costs more than it looks

Billing happens per API call, not per agent. Every turn sends the whole
conversation so far back to the model, and you pay for all of it again. For a
context that grows evenly over `n` turns, billed tokens are about `(n + 1) / 2`
times the final context. A 38-turn agent bills roughly 19 times what its last
turn reads.

Caching discounts the charge but does not remove it. A cached prefix bills at
a fraction of the base input rate on every turn that reads it. Cheap per
token, paid many times over.

## The four token buckets

An API response reports four token counts, each billed at a different multiple
of the model's base input rate.

| Bucket      | Field                         | Rate                                                |
| ----------- | ----------------------------- | --------------------------------------------------- |
| New input   | `input_tokens`                | 1x base input                                       |
| Cache write | `cache_creation_input_tokens` | ~1.25x base, higher for a 1-hour TTL (time to live) |
| Cache read  | `cache_read_input_tokens`     | ~0.1x base, lower on some families                  |
| Output      | `output_tokens`               | ~5x base                                            |

The multiples above are the shape of current published pricing, not fixed
constants. Roscoe ships a rate table of list prices per million tokens, one
entry per model family. Settings → Models shows the input and output rate for
each model and lets you override either one; read the current prices there
rather than from this page. The cache multipliers are built in and are not
editable.

The spread matters more than the multipliers themselves. Cache reads bill at a
small fraction of the base rate while output bills at a multiple of it, so the
same token count can differ by well over an order of magnitude in price on a
single model. A token count alone does not determine a price. You need the
composition.

Two traps in the fields:

- `cache_creation.ephemeral_5m_input_tokens` and `ephemeral_1h_input_tokens`
  are a breakdown of `cache_creation_input_tokens`, never an addition to it.
  Adding them on top doubles the cache-write charge.
- The two TTL buckets bill at different multiples, and the populations that
  use them barely overlap. Subagent turns are almost entirely 5-minute; the
  driver's own turns are almost entirely 1-hour. Pricing every write at the
  5-minute multiple therefore understates driver cost substantially, so Roscoe
  reads the split from the data rather than assuming it.

## Where the token counts come from

The host reports `subagent_tokens` when a subagent finishes. That number is the
**final turn only**, not a cumulative total, so it is roughly 10x low on a
typical multi-turn agent and useless on its own. Roscoe uses it as a checksum,
not as the answer.

The real figures come from the per-turn transcript Claude Code writes:

```text
<config-root>/projects/<encoded-cwd>/<session-uuid>/subagents/agent-<id>.jsonl
<config-root>/projects/<encoded-cwd>/<session-uuid>/subagents/workflows/wf_<id>/agent-<id>.jsonl
```

The config root is `CLAUDE_CONFIG_DIR` when it holds an absolute path,
otherwise `~/.claude`. A relative value is ignored rather than guessed at.

That file holds one line per content block, not per turn, and the early lines
of a message carry a partial `output_tokens`. Summing it naively roughly
doubles the total. The rule that reduces it correctly:

> Group assistant lines by `message.id`. Within each group take the input side
> once and `max(output_tokens)`. Then sum all four fields across groups.

Every clause carries weight. `max` rather than first, because early lines
freeze output at a partial count. Input once rather than summed, because the
input fields never vary within a group. Across groups rather than one group,
because a group is one turn and an agent is many.

**On Claude Code** Roscoe locates and reduces the file itself. **On every other
host** it cannot reach the transcript, so the driver ships the raw lines and
Roscoe reduces them. Drivers are asked to send the lines unreduced, and if
they do reduce, to keep the **last** line of each group. The idiomatic way to
deduplicate keeps the first, which is the partial count.

## Provenance: reading `usage_source`

`node_runs.usage_source` records how a row's usage was obtained.

| Value        | Meaning                                                                                       |
| ------------ | --------------------------------------------------------------------------------------------- |
| `exact`      | Roscoe read and reduced the transcript itself.                                                |
| `reconciled` | Same, but the final turn was topped up or closed in a state worth flagging.                   |
| `reported`   | The driver shipped per-line records. Roscoe reduced them, never saw the file.                 |
| `scalar`     | A total only, with no composition. Falls through to a text-length estimate.                   |
| `estimated`  | No usage at all. Reconstructed from text length, blind to cache.                              |
| `main_loop`  | Answered directly in the driving conversation, so no transcript ever exists to price it from. |

They rank in that order. Rows written before the column existed hold
`legacy`, which is not on the ladder: it reads as `estimated`, as does any
other unrecognised value.

`reconciled` outranks `reported` because custody dominates. A `reconciled`
row's uncertainty is one derived integer in a file Roscoe read. A `reported`
row's uncertainty is every digit a model typed into a tool call.

Read the field as a claim about **tokens**, not dollars. `exact` means the
tokens came from a per-token record. The dollar still passes through the rate
table.

Only the three measured rungs write the cache columns. A `scalar` row leaves
them NULL rather than 0, because 0 would assert that no cache traffic happened
rather than that nobody looked.

## Metered and subscription dollars are different

**Metered.** A server-side [backend](/docs/configuration/llm-backends) ran and
actually charged. The `claude -p` path reports an authoritative
`total_cost_usd` that Roscoe records verbatim, without consulting the rate
table. This is the only truly billed figure Roscoe holds.

**Subscription.** Every [handback](/docs/running/subscription#how-the-consensus-handback-works) node
runs on your own Claude plan. Out of pocket cost is $0. The dollar shown is
an API-equivalent list price computed from token counts and Roscoe's rate
table, answering "what would this have cost on the API".

So a rate-table error moves a subscription figure even when the token counts
are perfect, reconciling it against an Anthropic invoice is not meaningful, and
a spending cap on a handback run is a proxy control rather than a spend limit.
`workflow_runs` keeps them apart: `metered_cost_usd`,
`subscription_equiv_cost_usd`, and `total_cost_usd` as their sum.

## The driver's turns are usually the larger half

The conversation driving a run usually costs more than the run's steps, and it
lands in no node row. Roscoe measures it separately, and the **dollar** cap
binds on steps plus driver. The token cap deliberately stays on steps.

### Every run has its own driver

On Claude Code and Cowork, `start_workflow` asks which conversation will drive
the run before anything starts. The answer is almost always `dedicated`: a
subagent spawned to drive this one run and nothing else. Its transcript opens
and closes with the run, so everything in it is the run's cost.

A run driven from your main conversation (`shared`) gets no driver figure at
all. That conversation is doing other work too, and nothing in it says which
turns belonged to the run. Roscoe records `driver_shared_unattributable` in
`workflow_runs.driver_unavailable_reason` rather than guess.

### Where the figure comes from

On Claude Code Roscoe shares the driver's filesystem. Its hooks report which
transcript is the driver's, and Roscoe reads that file itself.

On Cowork the driver runs in a sandbox Roscoe cannot read. Each paused result
carries the windows still needing a figure and a ready-made command. The
driver runs the command in its sandbox and sends what it prints on the next
handback. The first response of a run also hands over a one-line hook install,
so the command knows which file is the driver's.

A Cowork subagent cannot spawn subagents of its own. So the driver asks your
main conversation to spawn each step's agents and pass back their ids and
answers. Those relay turns are the run's cost too: the command counts the main
conversation's turns from its status read of the paused run to its reply to
the driver, and nothing else it does.

### When a driver declines

A driver can answer `unavailable` with a reason instead of a figure. Roscoe
accepts it and asks again at every later pause while the window is
unmeasured. A window grows, so a driver that could not read the file early may
manage later. A recorded figure is never revised downward.

Roscoe pushes back in two places. A handback that leaves a window out entirely
is refused once. `no_turns_in_window` is accepted at the first handback, when
the turn that started the run has not been written yet, and refused from the
second, because every handback since was one of the driver's turns.

The reason persists on `workflow_runs.driver_unavailable_reason` until a
driver figure is recorded, which clears it. So a finished run that still has a
reason was never measured.

## How fan-out and sub-workflows aggregate

**The parent carries the aggregate.** For a consensus, map, or round-robin
node, the per-agent costs sum into one figure on the parent `node_run`, and the
children are then excluded from the run rollup so cache traffic is not counted
twice.

The exception is a [fan-out](/docs/running/subscription#two-kinds-of-ai-node)
that has re-paused mid-round, which has priced children and an unpriced
parent. That spend is real, so the rollup counts a row when it is top-level
**or** when its parent has no cost yet. Without that, a 15-agent consensus
round is invisible to the spending cap until the parent completes.

A sub-workflow's cost lives in its own run, and the rollup recurses into child
runs. Caps live on the root run, so the same dollars are never charged against
two limits.

The aggregate's provenance is its **weakest** part: one `estimated` agent makes
the whole parent `estimated`, rather than presenting an approximation as a
measurement.

## What stays inexact

- **Caps bind at fan-out boundaries, not node boundaries**, and on handback
  they fire after the money is spent.
- **A price change on a recognised model family is silent.** Roscoe matches a
  model to a family by name, so a superseded generation prices at the current
  rate with no warning. Set an exact rate in Settings → Models to pin one.
- **The rate table is hand-maintained** and carries the date it was last
  checked against published pricing. It is the one remaining source of dollar
  error once the token counts are right.
- **An unrecognised family falls back to the sonnet rate** and warns once.
- **Output tokens are occasionally under-recorded.** A message's completion
  line is sometimes never written, freezing that turn's output at a streaming
  snapshot. This was an upstream Claude Code bug: rates dropped from 50.0% of
  groups in 2.1.246–2.1.267 to 2.3% on 2.1.268 and later. Separately, the
  Cowork sandbox records a placeholder of a few output tokens on some
  subagent turns. Roscoe prices what the transcript says rather than
  estimating over it, so those rows run slightly low.
- **The driver figure runs a little low.** Turns the driver takes after its
  last handback are counted only if it calls Roscoe again afterwards, which
  its instructions tell it to do. Turns it takes before the run starts, such as
  loading its tools, are never counted.
- **A driver that answers inline cannot be measured.** On Claude Code and
  Cowork every `ai_agent` and `ai_judge` step is dispatched as its own
  subagent, and that is what creates the transcript the figure is read from. A
  driver that ignores the instruction and answers from its own context leaves
  the step with no transcript, so it falls back to a text-length estimate.
  Plain desktop chat has no subagents at all and always takes that path.
- **The host a run reports is self-reported**, except that a Cowork
  connection is always recorded as Cowork. Every control here assumes an honest
  driver; none is a security boundary.
- **Web search and fetch (`server_tool_use`) are not priced.**
- **Nested subagents are unmeasured.** Roscoe never nests agent trees itself,
  so this only bites if a node prompt tells a subagent to delegate.

## If you are debugging a cost figure

1. **Read `usage_source` first.** `estimated` is approximated from text length, blind to
   cache, measured 30–125x low on short agents and far worse on long ones.
   `scalar` is a final-turn total, roughly 10x low. Only then is the number
   worth comparing to anything.
2. **Check `billing_mode`.** A `subscription` row is an API-equivalent price,
   $0 out of pocket. A `metered` row from `claude -p` is a real charge that
   never touched the rate table.
3. **Check `priced_at_rates_version`** against the table's `lastVerified`. Rows
   priced under different tables are not comparable. A null stamp means the
   host supplied the dollar.
4. **Check `model` resolves** to the family you expect.
5. **Recompute by hand**, every term divided by 1,000,000:

   ```text
   input        * rate.input
   + output     * rate.output
   + write5m    * rate.input * cacheWriteMultiplier
   + write1h    * rate.input * cacheWrite1hMultiplier
   + cacheRead  * rate.input * cacheReadMultiplier
   ```

   Keep the two cache-write terms apart: folding them into the 5-minute
   multiple prices a driver row low. A mismatch points at a per-model rate
   override in `roscoe.yaml`, not at the table.

6. **Check whether the cache columns are null or zero.** Null means nobody
   looked. Zero asserts no cache traffic, which for a multi-turn agent is
   almost certainly wrong.
7. **If a figure looks ~10x low**, suspect a final-turn number used as a total.
   Orders of magnitude low means a text-length estimate. Roughly 2x high means
   a transcript summed per line instead of per `message.id`.
8. **For a fan-out parent**, confirm whether its children should be counted: if
   the parent has a `cost_usd` they must be excluded, if not they must be
   included.
9. **For a run total, the driver's turns are counted but the steps alone are
   not the whole story.** Check both.

## Where to next

- [Running on your Claude subscription](/docs/running/subscription) — which
  nodes run where, and what the handback does.
- [LLM backends](/docs/configuration/llm-backends) — the metered paths and
  where an authoritative cost comes from.
- [roscoe.yaml](/docs/configuration/roscoe-yaml) — per-model rate overrides.
