Problem
packages/reader/src/claude.ts:184-198 (captureSubagentFromToolResult) reads user turns only to index subagent markers, then discards the content. To attribute spend back to individual tool calls we need the size of each tool_result that entered context between assistant turn N and N+1.
Why this unlocks everything else
Anthropic's API reports usage at the message level — never per tool_use. But per-tool-call cost is recoverable as a delta from the session JSONL:
context(N+1) input-side tokens
= output(N) tokens
+ sum(tool_results added between N and N+1)
+ any user free-text
Every assistant turn already has exact usage.{input, output, cacheRead, cacheCreate5m, cacheCreate1h} (captured at claude.ts:200-211). The missing piece is the size of each block the user turn contributed. Without it we cannot allocate the delta across the tool calls that caused it.
No other usage tracker does this (confirmed against TokenTracker, ccusage) — they all stop at message-level usage. This is burn's unique opening.
Plan
- Replace
captureSubagentFromToolResult with a captureUserTurn that records per-user-turn block info: { toolUseId?, kind: 'tool_result' | 'text', approxTokens, byteLen, isError? }.
- Token estimate via a cheap heuristic first (bytes/4 works for ASCII-heavy content); upgrade to
@dqbd/tiktoken cl100k if accuracy matters. Measure before adding a runtime dep.
- For object content,
JSON.stringify before measuring.
- Extend
TurnRecord with an optional sibling record or a priorUserTurn field on each assistant turn — pick whichever keeps the append-only JSONL ledger clean. Leaning toward a separate UserTurnRecord so assistant turns stay immutable once written.
- Preserve order. The reader already maintains turn order via
order: string[]; user turns need to slot between assistant turns by parentUuid chain.
Acceptance
- Reader emits ordered per-user-turn block-size info alongside
TurnRecord[].
- On a real session, delta reconciles:
context(N+1) - cacheRead(N+1) within ±5% of output(N) + sum(new user-turn block sizes). Add a reconciliation assert as a test helper.
- Test coverage in
packages/reader/src/claude.test.ts with at least one multi-turn session that exercises tool_result of varying sizes (tiny Bash output, large Read result, error results).
- No change to how existing
TurnRecord consumers read data — additive only.
Depends on
Nothing. This is the prerequisite for #2.
Problem
packages/reader/src/claude.ts:184-198(captureSubagentFromToolResult) reads user turns only to index subagent markers, then discards the content. To attribute spend back to individual tool calls we need the size of eachtool_resultthat entered context between assistant turn N and N+1.Why this unlocks everything else
Anthropic's API reports
usageat the message level — never pertool_use. But per-tool-call cost is recoverable as a delta from the session JSONL:Every assistant turn already has exact
usage.{input, output, cacheRead, cacheCreate5m, cacheCreate1h}(captured atclaude.ts:200-211). The missing piece is the size of each block the user turn contributed. Without it we cannot allocate the delta across the tool calls that caused it.No other usage tracker does this (confirmed against TokenTracker, ccusage) — they all stop at message-level usage. This is burn's unique opening.
Plan
captureSubagentFromToolResultwith acaptureUserTurnthat records per-user-turn block info:{ toolUseId?, kind: 'tool_result' | 'text', approxTokens, byteLen, isError? }.@dqbd/tiktokencl100k if accuracy matters. Measure before adding a runtime dep.JSON.stringifybefore measuring.TurnRecordwith an optional sibling record or apriorUserTurnfield on each assistant turn — pick whichever keeps the append-only JSONL ledger clean. Leaning toward a separateUserTurnRecordso assistant turns stay immutable once written.order: string[]; user turns need to slot between assistant turns byparentUuidchain.Acceptance
TurnRecord[].context(N+1) - cacheRead(N+1)within ±5% ofoutput(N) + sum(new user-turn block sizes). Add a reconciliation assert as a test helper.packages/reader/src/claude.test.tswith at least one multi-turn session that exercisestool_resultof varying sizes (tiny Bash output, large Read result, error results).TurnRecordconsumers read data — additive only.Depends on
Nothing. This is the prerequisite for #2.