Skip to content

Session cost excludes subagent cost (TUI sidebar, opencode stats, /export) #45417

Closed as not planned

Description

@stephenmathieson

Summary: The reported session cost does not include the cost of subagents the session spawned. For any workflow that fans out subagents (e.g. a multi-subagent review command), the displayed cost is far below what was actually billed.

Repro:

  1. Run a session that dispatches several subagents (Task/subagent calls).
  2. Note the cost shown in the TUI sidebar/footer (also opencode stats and /export).
  3. Sum the per-message cost of the root session and every subagent (walk parent_id in the SQLite history via opencode db).
  4. The true total is much higher than the reported figure.

Actual: Only the root session cost is reported; subagent cost is excluded.
Expected: Session cost aggregates the root session plus all subagents, recursively.
Impact: Cost attribution/comparison is wrong for any subagent-heavy workflow.

Context: Appears to be the same limitation as #11027 (auto-closed for inactivity, not fixed) and RFC #12377 (closed); related to #31032. Opening a fresh issue per the stale-bot suggestion on #11027.

Workaround: Sum per-message cost from the SQLite history with a recursive walk over parent_id.

Environment: opencode 1.18.x, macOS.

Activity

  1. github-actions commented on Aug 27, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    You also referenced #11027 and #31032 in your issue; the maintainers may want to consolidate all of these.

  2. greedyivan commented on Aug 31, 2026

    @greedyivan

    Confirming with a billing-level reconciliation (opencode 1.18.25, Linux; deepseek-v4-pro behind a custom provider config).

    I ran a session that fanned out 3 subagents via the Task tool, then walked parent_id in the SQLite history and cross-checked the whole tree against the provider's billing export for that day:

    API calls cost (opencode rates)
    Root session — the only part the sidebar shows 143 $0.168
    Subagent 1 21 $0.0417
    Subagent 2 16 $0.0395
    Subagent 3 23 $0.0396
    True total 203 $0.2887

    Subagents were 42% of the session's real cost — none of it visible in the sidebar, opencode stats, or /export. Same as reported.

    The provider export also lets us verify the tree itself is complete: billed cache-hit tokens for the day are 19,509,632 — matching root + all 3 subagents to the exact token. Two side observations from the same reconciliation:

    Happy to share the exact SQL/queries used for the reconciliation if that helps.

  3. chenhz01 commented on Sep 17, 2026

    @chenhz01

    +1 — and noting this is a live re-surface of @bluet's RFC #12377, which proposed exactly the right data model for this (own_cost vs total_cost with parent-chain propagation, immutable call-time snapshots) before it was closed as completed. #45417, plus #40114 and #48548, suggest the aggregation gap is still live in practice.

    From running our own session-level cost ledger across multi-agent runs: the two biggest sources of under-reporting we see match that RFC's analysis — subagent spend that never rolls up to the parent, and hidden LLM calls (summaries, titles) that bypass the normal message flow entirely. Both are invisible in the UI today, so users can't even tell their numbers are incomplete.

    If it helps the implementation: the RFC's append-only CostEvent approach also makes reconciliation possible (events vs. session rollups), which would catch silent drift like #7387's "costs decreasing" bug class at audit time instead of user-report time.

  4. JLegends commented on Sep 17, 2026

    @JLegends

    Concrete case plus a workaround.

    I run subagent-heavy sessions. On one session the built-in sidebar showed $1.21 spent while its 81 child sessions had spent $6.41 on top of that, so roughly 84% of the real cost was invisible. That matches the reconciliation posted above.

    Instead of waiting for the core fix I wrote a TUI plugin that walks the child tree with client.session.children and renders the rollup below Context (additive with it, not inclusive): https://github.com/JLegends/opencode-subagent-usage

    Two notes for whoever implements the real fix:

    • The plugin API has no children accessor on api.state.session, so a plugin has to go through the SDK client. A first-class accessor would help.
    • session.cost moved between reads while I was testing, so a rollup derived at read time will age better than a snapshot taken once.

    Still want the core change (#47822, #48548) — this is a workaround, not a substitute.

  5. chenhz01 commented on Sep 18, 2026

    @chenhz01

    One more data point, since you asked for exactly this kind of reconciliation evidence: we run a session-level cost ledger (honest accounting — pointer overhead and indirect calls included) across multi-agent work, and the rollup gap you measured (parent shows $1.21 while 81 children burned $6.41) is exactly the failure mode our reconciliation rules target. Three rules from that ledger, offered for whoever implements the core fix:

    1. Meter at the call boundary, not the session boundary. Every LLM call writes an append-only cost event at the moment it happens — including the hidden ones (summaries, title generation). Anything reconstructed after the fact from session state will miss the calls that never appear in the message flow.
    2. Store own_cost, treat total_cost as a cached rollup. Parent-chain rollups that are written once become wrong the moment a child session continues running — which is the drift you observed in session.cost between reads. Your "rollup ages better than a snapshot" is the right call; recomputing from events at read/audit time is what makes it stay right.
    3. Reconciliation is events ⊕ cached rollups, and drift is signal. A periodic pass recomputing the subtree cost from the event log and comparing it against what the store reports catches silent accounting drift (the Multi Model Cost Calculations are Bugged #7387 "costs decreasing" class) at audit time instead of user-report time. The core pass is a few lines (both sides cover the same cost universe — the subtree of S — so consistency shows up as OK, not false positives):
    events  = sum(e.tokens * e.price for e in cost_events if e.session in subtree(S))
    rollup  = session_store.get(S).total_cost      # what the UI currently shows
    if abs(events - rollup) > epsilon:             # drift = investigate, don't auto-correct
        flag(S, events, rollup)
    

    We validated this shape against a simulated 3-session tree (hidden summary call, stale cached rollup, and a "costs decreasing" store) — drift caught in all three classes, clean OK when consistent. Happy to share the runnable demo.

    One honest scoping note: on our side the metering layer is running; the reconciliation pass is designed and validated on a simulated session tree, but not yet battle-tested against production data — so treat the three rules as field-tested methodology, and the snippet as the shape we're building toward. Which is why your plugin interests me: a child-tree walk plus an event log would make a real reconciliation pass testable against your 81-session case. Happy to share the fuller method, and if the plugin API grows the first-class children accessor you mentioned, a reconcile module would slot in cleanly. Would that be a direction you'd want to co-explore, or is the missing accessor the blocker you'd fix first?

  6. roy-tong commented on Sep 19, 2026

    @roy-tong

    Your two reconciliations here are the clearest evidence I have seen that subagent spend is invisible in the UI: two independent methods landing at 42% and 84%.

    I have been standardising how delegation boundaries get counted, and your case is the exact failure mode I am trying to pin down: child spend that never rolls up to the parent. The draft checks are three vectors in conformance/vectors/delegation-001-003.json (DELEGATION-001 is the no-flattening one).

    Would you read those three and tell me whether DELEGATION-001 actually captures your case, or whether the real invariant is narrower than what I wrote?

    Useful feedback would be a field the vectors are missing, a case they classify wrongly, or the shape of your two reconciliation queries if you are willing to share them.

    Roy Tong, AgentMeasure (github.com/roy-tong/AgentMeasure)

  7. chenhz01 commented on Sep 20, 2026

    @chenhz01

    Thanks for walking through the two reconciliation runs — the independent methods landing at 42% and 84% was the useful signal, since they fail independently. On the structural fix, I'd use two-level attribution on the session record — own_cost (tokens the root agent billed directly) and total_cost (own plus descendants, resolved by one parent_id walk). The sidebar shows total_cost, per-message views own_cost, and total - own = sum(children own_cost) becomes a checkable invariant, not a spreadsheet. A recursive walk over existing history keeps it cheap and makes the gap self-evident in opencode stats. On your ask: DELEGATION-001 captures the rollup omission but not the hidden-call bypass (summaries/titles never reach the message flow); the reconciliation shape was an append-only event log cross-checked against cached rollups — happy to share both query shapes.

  8. opencodejr commented on Oct 28, 2026

    @opencodejr

    Thanks for the detailed report and the SQLite workaround, @stephenmathieson.

    You're on opencode 1.18.x, which is OpenCode v1. v1 now only gets fixes for critical bugs (data loss, security problems, crashes or breakage with no workaround). A session cost that leaves out subagent spend is a real problem, but it doesn't fall into those categories, so it's unlikely to be fixed in v1.

    We recommend upgrading to OpenCode v2 (the @opencode/cli package, opencode command), which is where development happens. If the session cost in v2 also leaves out subagent sessions, please reply here or open a new issue saying it's v2. Please also reply if you think this counts as critical for v1.

    Related issues:

  9. opencodejr commented on Oct 29, 2026

    @opencodejr

    Closing this as not planned. It was reported on opencode 1.18.x (v1), and v1 only gets fixes for critical bugs. A session cost that leaves out subagent spend doesn't fall into that category, especially with your SQLite workaround available. Thanks again for the clear write-up, @stephenmathieson.

    If you upgrade to v2 (@opencode/cli) and the session cost there also leaves out subagent sessions, please open a new issue that says it's v2 and includes the version. Related v2 issues you may want to follow: #48548, #47822 and #46371.

  10. roy-tong commented on Oct 30, 2026

    @roy-tong

    Leaving a post-closure note for anyone who finds this via search: the accounting gap itself was never disputed — two independent reconciliations in this thread (42% and 84% of real spend invisible, failing independently) plus the SQLite walk via parent_id all agree. "Not planned for v1" is a maintenance decision, not a contradiction of the measurement.

    For teams that need the number to be right today: the workaround in this thread is the honest path, and the shape it computes is now frozen as a conformance vector on our side — a parent's reported total vs. the sum of its children's own costs, with the roll-up verdict kept separate from the per-session verdict ( conformance/vectors/delegation-008.json ). If v2 changes how session aggregates work, that vector is ready to score the change the day it ships.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions