Cost & usage
Attribution#
A total is not an answer. "We spent $840 last month" starts a conversation; "$610 of it was one nightly job re-reading the same repository" ends one.
The axes#
| Axis | Answers |
|---|---|
| Runtime | Which tool is expensive |
| Model | Which model is expensive |
| Session | Which individual runs are expensive |
| Agent | Which of your agents is expensive |
| Skill | Which capability drives spend |
| Tool | Which tools are called most, and cost most |
| Plugin | Per-plugin usage, where a runtime attributes it |
| Team | Rolled up for chargeback |
curl -s localhost:8900/api/token-attribution | jq
curl -s localhost:8900/api/usage/by-plugin | jq
curl -s localhost:8900/api/local/runtimes | jq '.rows'
curl -s localhost:8900/api/local/models | jq '.rows'Team mappings#
Map a key or an agent to a team, and the rollups follow:
curl -s localhost:8900/api/usage/team-mappings -X POST \
-H 'content-type: application/json' \
-d '{"key_type":"agent","key_value":"nightly-refactor","team":"platform"}'
curl -s -X DELETE localhost:8900/api/usage/team-mappings/agent/nightly-refactorThis is what makes an internal chargeback conversation possible without anyone hand-maintaining a spreadsheet.
Sub-agents: the double-counting rules#
Delegation is where attribution gets genuinely hard, and where most tools get it wrong. ClawMetry's rule is that a child's spend belongs to the child, and the parent is not billed for it twice.
How that is achieved differs per runtime, because they persist delegation differently:
Copilot stamps every sub-agent API call with initiator='sub-agent' and the
parent tool-call id, so child spend is split out of the parent rollup rather
than added to it. That is vendor-billed truth.
OpenHands stores a copy of each child's metrics under a delegate:* key on
the parent. Summing every key and also listing the child would count it twice, so
those keys are excluded from the parent row.
Deep Agents children carry their own usage_metadata and the parent's usage
does not include them, so per-child tokens never double-count.
Antigravity children have their own token rows, so nothing is double-counted against the parent.
QM keeps no per-child token split at all — child spend lands in the parent
turn. Children are therefore reported with zero tokens and cost
unavailable, rather than an invented split.
Pi and PicoClaw persist no child transcript. Children are synthesised from the parent's tool call and result; Pi's carry the embedded per-child usage, PicoClaw's carry nothing because nothing exists.
The general rule when you are reading a delegation tree: if a child shows
unavailable, that is the honest state, not a bug. Adding it into the parent
would be the bug.
Branches and abandoned work#
Pi, Devin and Deep Agents store conversations as trees. A /fork or /revert
leaves the abandoned turns in place as sibling branches.
Summing every row bills you for work that was thrown away. ClawMetry walks the active chain — from the session's main chain pointer back through parents to the root — and reports the abandoned nodes as a count.
So a Devin session's cost is what the surviving conversation cost. If you want to know what the abandoned branches cost, that count is in the session's extra data, and it is often the more interesting number.
Cross-runtime comparison#
Cost is comparable across runtimes because everything goes through one derivation path, and vendor dollars are used where they exist.
What is not comparable is a runtime that records nothing. PicoClaw and Cursor-on-a-laptop show as unknown. A ranking that silently treats those as zero would flatter them, so any comparison worth trusting states which runtimes it had data for.
Deriving cost per unit of work#
The question behind most attribution work is not "what did it cost" but "what did we get". ClawMetry does not decide what counts as a durable result, but it gives you the inputs:
- Sessions that ended with no file change — the
no_progressdetector's domain - Sessions that ended in an error
- Sessions that were stopped or killed
- Session outcome, where a runtime records one
curl -s localhost:8900/api/local/query -X POST \
-H 'content-type: application/json' \
-d '{"shape":"rollup_sessions","args":{"limit":200}}' \
| jq '[.rows[] | {title, cost_usd, status, stuck}]'