You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
OpenCode's cost tracking has multiple interrelated gaps that cause inaccurate cost display for users with multi-agent and multi-model workflows. This RFC proposes a unified architecture to address:
PR #7763 addresses the immediate bug where the sidebar only sums the last 100 messages in memory. This is a necessary fix, but not sufficient for accurate cost tracking.
The Subagent Problem (Critical)
When using Task tool to spawn subagents (explore, librarian, oracle, etc.), each creates a child session with parentID linking to the parent. However:
// task.ts - creates child sessionconstsession=awaitSession.create({parentID: ctx.sessionID,// Links to parent// ...})// session/index.ts - addCost only updates current sessionexportasyncfunctionaddCost(sessionID: string,amount: number){awaitupdate(sessionID,(draft)=>{draft.cost=(draft.cost??0)+amount})}// Parent's cost is NEVER updated!
Result: Users see only their main session's cost, missing all subagent spend.
// processor.ts - cost calculated with CURRENT modelconstusage=Session.getUsage({model: input.model,// Current model, not the model used for THAT callusage: value.usage,// ...})
When switching from expensive (opus) to cheap (haiku) model, ALL previous costs are recalculated with the cheaper model's pricing.
Correct behavior: Each LLM call should lock its cost at call time with the actual model/pricing used.
Proposed Solution
Core Principle
"Append-only cost events as source-of-truth + denormalized rollups on sessions"
Schema Changes
// Session schema (enhanced)interfaceSession{id: stringparentID?: string// ...existing fields...// NEW: Split cost trackingown_cost_micros: number// This session's direct LLM spend (immutable accumulator)total_cost_micros: number// own + all descendants (cached rollup)}// NEW: Cost event record (optional but recommended for audit)interfaceCostEvent{id: stringsession_id: stringmessage_id?: stringtool_call_id?: string// Immutable snapshot at call timeprovider_id: stringmodel_id: stringtokens: {input: numberoutput: numbercache_read?: numbercache_write?: number}pricing_version: string// Snapshot of rates at call timecost_micros: number// Calculated cost in microdollars (never recalculated)created_at: number}
Track all LLM calls that don't go through normal message flow
Effort: ~1-2 hours | Risk: Low
Phase 4 (Optional): Full Audit Trail
Add CostEvent table for complete cost history
Add reconciliation command to verify session costs match events
Support cost breakdown by model in UI
Effort: ~4-6 hours | Risk: Medium (new storage requirements)
Migration Strategy
// migration.ts - run on startup after PR #7763asyncfunctionmigrateCostFields(){constsessions=awaitSession.list()// Sort by depth (children first) for correct rollupconstsorted=sortByDepth(sessions)for(constsessionofsorted){// Calculate own_cost from message sumsconstownCost=session.messages?.reduce((sum,m)=>sum+(m.role==="assistant" ? m.cost : 0),0)??session.cost??0// Calculate total_cost = own + children's totalconstchildrenTotal=sessions.filter(s=>s.parentID===session.id).reduce((sum,s)=>sum+(s.total_cost_micros??0),0)awaitSession.update(session.id,(draft)=>{draft.own_cost_micros=ownCostdraft.total_cost_micros=ownCost+childrenTotal})}}
Summary
OpenCode's cost tracking has multiple interrelated gaps that cause inaccurate cost display for users with multi-agent and multi-model workflows. This RFC proposes a unified architecture to address:
costfield can't distinguish "own" vs "total"Related Issues: #11027, #7387, #7175, #7767, #6989, #485
Related PRs: #7763 (necessary foundation, but insufficient alone)
Problem Statement
The 100-Message Bug (Being Fixed)
PR #7763 addresses the immediate bug where the sidebar only sums the last 100 messages in memory. This is a necessary fix, but not sufficient for accurate cost tracking.
The Subagent Problem (Critical)
When using Task tool to spawn subagents (explore, librarian, oracle, etc.), each creates a child session with
parentIDlinking to the parent. However:Result: Users see only their main session's cost, missing all subagent spend.
The Multi-Model Problem
Issue #7387 reports costs DECREASING when switching models mid-session. Root cause:
When switching from expensive (opus) to cheap (haiku) model, ALL previous costs are recalculated with the cheaper model's pricing.
Correct behavior: Each LLM call should lock its cost at call time with the actual model/pricing used.
Proposed Solution
Core Principle
"Append-only cost events as source-of-truth + denormalized rollups on sessions"
Schema Changes
Updated
addCost()FunctionSidebar Display Changes
Implementation Plan
Phase 1: Foundation (Builds on PR #7763)
costtoown_cost_microsfor semantic claritytotal_cost_microsfield to Session schemaaddCost()to propagate to parent chainEffort: ~2-3 hours | Risk: Low (additive, backward compatible with fallback)
Phase 2: Multi-Model Correctness
addCost()for audit trailEffort: ~2-3 hours | Risk: Low
Phase 3: Hidden Costs
Effort: ~1-2 hours | Risk: Low
Phase 4 (Optional): Full Audit Trail
Effort: ~4-6 hours | Risk: Medium (new storage requirements)
Migration Strategy
UX Recommendations
$1.10 (incl. subagents)total_cost$0.50own_cost$0.00with lineage noteLong-Term Vision
Breaking Changes
None. All changes are additive with backward-compatible fallbacks:
0or calculated from existing dataAcceptance Criteria
Questions for Maintainers
own_cost_micros/total_cost_microsorownCost/totalCost?Happy to contribute a PR for Phase 1 once PR #7763 is merged. Would love feedback on this architecture proposal.
/cc @fwang @rekram1-node