Repository navigation
Fix subagent accounting and expose native-agent spend in existing Burn views - #2
Conversation
There was a problem hiding this comment.
Thank you for the contribution. This is a great change. A few things. This moves Claude Code spend to a host transcript reader, separates Codex child counters, counts lifecycle-owned workflow workers, and shows the native-agent subset in the existing popover, KPIs and table. The accounting model is sound. Beyond two code issues, this needs a UI-scope pass: the popover has gained a calculation that belongs in the side panel, and provider names leak into generic UI copy.
I reviewed statically at bf8841d and ran no tests (no dependencies installed in my checkout), and I did not review the screenshots.
Praise: the incremental JSONL reader, the persisted Codex child-turn separation with its reload test, and the fallback that always carries a coverage note instead of an invented zero.
Drafted and reviewed with Shuttle
| const children = new Map<string, number>(); | ||
| for (const batch of chunks(stale, SDK_BATCH)) { | ||
| if (deps.descendantsOf) { | ||
| for (const [id, ids] of await deps.descendantsOf(batch)) children.set(id, ids.length); |
There was a problem hiding this comment.
This now hides a failed descendant listing: descendantsOf catches the error and returns an empty map, threadMeta caches {at, meta: null} for the batch, and spend() drops every thread without meta for 60 seconds. Before this change the same failure failed the request instead of showing lower spend. Could this keep the previous meta or rethrow here, plus a test for the injected branch? The spend tests only cover the SDK path.
| const state = await read(join(root, parent + ".jsonl"), parent, false); | ||
| if (!state) continue; | ||
| found = true; | ||
| for (const key of state.entries.keys()) inherited.add(key); |
There was a problem hiding this comment.
inherited reads only the parent's main transcript, but the report also counts the child's subagents/**/agent-*.jsonl, so copied subagent snapshots are excluded by timestamp only. If Claude copies that directory on fork, those calls are charged in both threads; the fork test covers only the main file. Feeding the parent's subagent trees into inherited, or a test proving they are not copied, would close this.
| <div className="px-3 py-2.5"> | ||
| <div className="flex items-baseline gap-2.5"> | ||
| <span className="text-[26px] leading-tight font-semibold tracking-tight tabular-nums">{usageLabel(report)}</span> | ||
| <CostBreakdown answer={answer!}><span className="text-[26px] leading-tight font-semibold tracking-tight tabular-nums">{usageLabel(report)}</span></CostBreakdown> |
There was a problem hiding this comment.
The popover is meant as a quick overview, and this calculation tooltip turns it into a second details view; let's keep the detailed calculation in the sidepanel. One scope in the added line also looks wrong: native agents are part of this thread and already count in its total, and child threads already have the with-children figure, so the Subagents line on line 287 should read report.agents rather than family.agents, which adds the children's agents on top.
| ); | ||
| return { total, calls, estimated, children }; | ||
| const all = [report, ...reported.map(r => r.report)]; | ||
| const agentRows = all.flatMap((r, i) => r?.agents?.rows.map(a => ({...a, id: i + ":" + a.id})) ?? []); |
There was a problem hiding this comment.
agentRows is uncapped while the child list is capped at 50, and it crosses the usage RPC on every refresh although the client only sums it; hundreds of workflow workers means every worker's rows each time. Sending the family aggregate would bound it. Nit: the positional i + ":" ids change as costs reorder the list.
| report.coverageNote = | ||
| missing || missingParent | ||
| ? "Partial Claude transcript history: some session identities are unavailable." | ||
| : "Claude transcripts include saved subagents. Background calls are grouped by time; deleted or external API logs cannot be recovered."; |
There was a problem hiding this comment.
Burn is provider-agnostic, but this note is rendered in the generic panel (panel.tsx:315) and names Claude. Saying what is missing in plain terms ("some saved subagent history is unavailable") and leaving provider names to the coverage doc would keep the UI neutral; the fallback note at sources.ts:440 ("BB events can omit subagents") has the same issue.
|
|
||
| The details page gains the third KPI after With children, plus expandable native-agent rows under both the root thread and any child with identifiable native usage. Agent numbers are neutral labels; identifiers are available in the row title. | ||
|
|
||
|  |
There was a problem hiding this comment.
Please remove the screenshots and the screenshot references. They are renders from a demo harness rather than the real UI, they add about 215 KB, and the surrounding text already describes the change. If a screenshot is wanted later, it would be better taken from the installed plugin.
|
|
||
| `UsageReport.agents` is optional and backward compatible. It carries per-agent calls, cost parts, estimation flags and coverage. It survives validation, saved reports and RPC boundaries; discounts apply to the agent's actual cost parts. Family aggregation includes every reported descendant before the existing 50-child display cap. | ||
|
|
||
| - Claude: saved recursive subagent files supply native identity; copied fork history and repeated message snapshots are excluded. `available` describes the available session files, not a guarantee about deleted or external history. |
There was a problem hiding this comment.
This duplicates docs/usage-coverage.md, nothing links here, and lines 47-49 are PR validation notes. The provider source details on lines 34-37 also belong in usage-coverage.md so the UI doc stays provider-agnostic; one linked coverage doc with validation left in the PR would be easier to keep true.
|
|
||
| Native subagents can run inside one provider session. BB workflows instead spawn hidden worker threads with `lifecycleOwnerThreadId`; this is distinct from `parentThreadId`. BB's descendants API currently traverses parentage only. Burn extends the returned family over both edges, including nested workers and workers' ordinary children, with deduplication and cycle protection. The global Spend report continues to sum each thread's own report once. Native transcript calls belong to that own report and are not added again as BB worker reports. | ||
|
|
||
| A replayed workflow call does not create another worker or another usage record. Real retry attempts that create additional workers remain separate spend. This patch does not change workflow execution or retry policy. |
There was a problem hiding this comment.
This claim is about BB's workflow execution, which nothing in this repository verifies; citing BB's docs or dropping it avoids a stale statement about billing.
|
|
||
| The test suite covers the new reader, recursive workflow paths, duplicate snapshots, appended partial JSONL, unknown models, one-hour cache writes, copied fork history, rewritten files, host/event fallback, preserving saved history, lifecycle ownership, and interleaved Codex child counters. Existing OpenCode, Pi/API usage, budget, report and frontend suites remain in the full run. | ||
|
|
||
| On Node 26, run browser tests with `NODE_OPTIONS=--no-experimental-webstorage npm test`; otherwise Node's global Web Storage conflicts with jsdom in the existing suite. Typecheck uses the repository's pinned SDK. `bb plugin build` also checks the installed BB build pipeline; existing SDK-version and retired-slot warnings are outside this patch. |
There was a problem hiding this comment.
Node 26 test-runner flags and the validation summary are developer notes in a page linked as user-facing coverage; CONTRIBUTING fits better.
|
Thanks for the review. The accounting/error-handling fixes are in ba5c40d; I've pushed a UI follow-up in 9548857. I'd like to retain a compact, on-demand calculation on the amount in the chip popover. It answers “what makes up this cost, and how much do child threads add?” directly where the number is shown, without opening the side panel and interrupting the current task. It adds no permanently visible calculation rows: it opens only on hover, focus or click, and the trigger disappears entirely when there is nothing to break down. The side panel remains the place for per-agent rows and deeper exploration. The scope is now explicit:
Validation: 45 combinations of own-agent coverage, child presence, child-native agents and calculation scope; full suite passes (586 tests), typecheck and plugin build pass. Installed/reloaded locally with settings preserved. Would you be open to keeping this smaller conditional calculation in the chip popover? |
Burn now counts subagent spend correctly and is hardened across its readers, storage and report endpoint. Subagent accounting is by Artur Karapetyan (@architawr) in #2; OpenCode support remains by Nikolai Berezovskii (@brzvsk) in #1. - Claude Code subagents are counted from host transcripts; Codex child counters stay separate; bb workflow workers count in their origin thread; native-agent spend is shown in the card, details and family table without being counted twice. - Token and cost fields are coerced to finite numbers, each host report is validated on its own, and agent rows fold past 50, so one malformed or oversized report cannot break a batch or bb's KV cap. - Session-log memory is bounded per log and across logs, with a cap on an unterminated line; host reads that a price change overtook are not saved or cached. - A stale stored pi report no longer disables the server-side budget stop; POST /usage requires a per-thread token and rejects a missing one before reading the body. - Price overrides price router aliases and contract models for Claude Code and Codex, and reprice held and saved reports. - New threads retry a provisional session lookup after five seconds instead of waiting up to five minutes, with the retry capped after about a minute. - The Where it went bar normalises parts to shares (it under-filled at costs below a dollar), shows an Unsplit slice for a total with no kinds, and stays hidden until a thread has a priced call. - Burn page storage reads are bounded; OpenCode read failures are logged; the README's local install says to rebuild better-sqlite3 before tests. 610 tests pass in 41 files and typecheck is clean. Checked with a plugin build and a live install from the checkout: a report without a token was rejected while the session-log reader kept cost current, and a reloaded plugin restored its settings. Settings, stored keys and the plugin database's tables are the same as in 0.8.3; Price overrides defaults to empty, and reports gain optional native-agent detail that older versions ignore.
Claude Code's event-only report omits native subagent transcripts. Native Codex child events can reuse the parent's provider session ID, mixing independent cumulative counters. BB workflow workers use lifecycle ownership rather than parentage, so their spend can appear in global Spend while being absent from the origin thread's family total.
This PR fixes those accounting paths and extends the existing popover/details UI so users can see the native-agent subset, including agents inside child BB threads, without adding their spend twice.
Accounting
Existing UI, with additions
+without replaying each old child's last turn.The demo shows $4.82 in this thread, $6.64 with children and $1.80 of that amount in native agents, including $0.40 inside a child.
Popover
Calculation on the large amount
Details and a child's own native agent
Calculation without children
Screenshots render the implemented React components in an isolated host harness with demonstration data; no private chat content is published. UI semantics, source coverage and review notes.
Validation
Limits reviewers should know
Per-turn worker attribution uses the originating ancestor's creation time. Later independent tasks sent to an old worker remain in family totals but cannot safely be assigned to a new parent prompt. Claude native background calls retain timestamp grouping across prompts. The delta is therefore not an exact causal trace for arbitrary reused/concurrent agents.
Old mixed Codex ledger records are not rewritten; child-model/service-tier pricing depends on BB telemetry. Missing native breakdown shows Unavailable or a partial lower bound, never an invented zero. Deleted/external logs cannot be recovered. OpenCode depth/message caps and Pi custom-directory discovery remain unchanged. Direct API calls outside supported telemetry are not intercepted. Existing SDK-version/retired-slot build warnings are outside this change. See docs/usage-coverage.md and docs/native-agent-ui.md.