Skip to content

fix(core): roll up subagent cost into parent session and fix fork double-counting - #43645

Open
AndyS77 wants to merge 5 commits into
anomalyco:devfrom
AndyS77:cost-subagent-rollup
Open

fix(core): roll up subagent cost into parent session and fix fork double-counting#43645
AndyS77 wants to merge 5 commits into
anomalyco:devfrom
AndyS77:cost-subagent-rollup

Conversation

@AndyS77

@AndyS77AndyS77 commented Aug 20, 2026

Copy link
Copy Markdown

Issue for this PR

Closes#39740
Closes#31032
Closes#36944

Type of change

  • Bug fix
  • New feature

What does this PR do?

Subagent session costs were not rolled up to the parent session (#39740), and forked sessions double-counted pre-fork costs (#31032, #36944). This PR fixes both issues and exposes the aggregated cost through the protocol layer so the TUI, stats, and ACP can display it.

Fork double-counting fix: When a session is forked, the cloned step-finish parts now have their cost and tokens zeroed out. The forked session starts with zero cost and only accumulates cost from its own LLM calls, not the parent's pre-fork history.

Cost rollup: Added children() and totalCost() to both V1 Session.Service and V2 SessionV2.Interface. totalCost() does a BFS walk across all descendant sessions, summing cost and tokens.

Protocol endpoint: Added session.cost and session.children to the protocol session group (/api/session/:sessionID/cost, /api/session/:sessionID/children) with handlers in both the protocol handler (packages/server) and the server handler (packages/opencode). Both SDKs were regenerated.

TUI:refreshCost effect in stream.transport.ts calls sdk.session.cost() on message.updated and bootstrap. SessionData.totalCost field; footer formatUsage uses totalCost when > 0, falls back to info.cost.

Stats + ACP: Stats uses totalCost() for top-level sessions. ACP sendUpdate uses sdk.session.cost() with totalSessionCost(messages) fallback.

How did you verify your code works?

  • 9 service-level tests (cost-rollup.test.ts): fork zeroing, own cost, child cost, nested children, multiple children
  • 10 ACP usage tests (usage.test.ts): mock updated with cost method
  • 2 HTTP E2E tests (httpapi-session.test.ts): aggregated cost across nested children (parent -> child -> grandchild, verifying 0.06 = 0.01 + 0.02 + 0.03), 404 for missing session
  • bun typecheck clean (0 source-code errors)
  • Both SDKs regenerated via bun run generate

Related PRs (used as reference)

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

AndyS77and others added 5 commits August 20, 2026 12:50
…ble-counting
- Zero out cost/tokens on cloned step-finish parts in fork() to prevent
double-counting pre-fork spend in forked sessions (anomalyco#31032, anomalyco#36944)
- Add Session.totalCost(sessionID) that BFS-walks descendant sessions via
children() and aggregates cost + tokens (anomalyco#39740)
- Add GET /session/:id/cost endpoint exposing aggregated cost
- Add E2E tests: subagent cost rollup, fork with subagent history,
nested child sessions, multiple child sessions
Closesanomalyco#39740, anomalyco#31032, anomalyco#36944
Supersedes anomalyco#25712
Co-Authored-By: zai-glm-52 <noreply@ai.local>
Agent: @bug-fix
Scope: cost-subagent-rollup
- Add totalCost field to SessionData, updated via refreshCost effect
- Footer formatUsage uses totalCost when available, falls back to info.cost
- Context % remains per-session (info.tokens) — correct for context window
- refreshCost fires on message.updated for parent session and on bootstrap
- Calls GET /session/:id/cost endpoint (requires SDK regeneration)
Co-Authored-By: zai-glm-52 <noreply@ai.local>
Agent: @bug-fix
Scope: cost-subagent-rollup
- stats: use totalCost() for top-level sessions (no parentID), skip
child sessions to avoid double-counting
- acp: sendUpdate fetches aggregated cost via session.cost endpoint,
falls back to totalSessionCost(messages) if unavailable
- messageLoaderFromSDK: add cost() method to MessageLoaderInterface
Co-Authored-By: zai-glm-52 <noreply@ai.local>
Agent: @bug-fix
Scope: cost-subagent-rollup
…tent SDK cost method
- Rename totalCost to subagentCost in SessionData (clarity: this is
the aggregated child session cost, not the parent's own cost)
- Footer cost = info.cost (parent) + data.subagentCost (children)
- refreshCost uses existing session.children SDK endpoint instead of
non-existent session.cost (requires Protocol registration for SDK gen)
- stats: catch NotFoundError from totalCost
- acp: revert to totalSessionCost(messages) until SDK supports cost
- handler: map NotFoundError to ApiNotFoundError
Co-Authored-By: zai-glm-52 <noreply@ai.local>
Agent: @bug-fix
Scope: cost-subagent-rollup
… layer
- Add session.cost and session.children endpoints to protocol session group
- Add children() and totalCost() to SessionV2 Interface + implementation
- Add handlers in protocol SessionHandler
- Regenerate both SDKs (client + legacy js)
- Switch TUI refreshCost to SDK session.cost() endpoint
- Switch ACP sendUpdate to SDK session.cost() with fallback
- Rename SessionData.subagentCost to totalCost (holds server-aggregated total)
- Footer uses totalCost when > 0, falls back to info.cost
- Add E2E tests: aggregated cost across nested children, 404 for missing session
- Update ACP usage test mock with cost method
Co-Authored-By: zai-glm-52 <noreply@ai.local>
Agent: @bug-fix
Scope: cost-subagent-rollup
@github-actionsgithub-actionsBot added the needs:compliance This means the issue will auto-close after 2 hours. label Aug 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Found Potential Duplicates/Related PRs

#25712 - #25712

#32301 - #32301

  • Title: feat: nested sub-agent spawning (up to 5 levels) + fixes for #23091 / #13715
  • Related to subagent session handling and may have overlapping cost tracking concerns.

These are the most relevant matches. PR #25712 is particularly important as it appears to be the companion UI PR that would consume the cost aggregation API being implemented in #43645.

@github-actionsgithub-actionsBot removed the needs:compliance This means the issue will auto-close after 2 hours. label Aug 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

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

Labels

None yet

Projects

None yet

1 participant

@AndyS77