Skip to content

Include inner interactive-toolkit cost in session cost - #13

Open
josephschorr wants to merge 6 commits into
mainfrom
feat/subagent-cost-accounting
Open

josephschorr wants to merge 6 commits into
mainfrom
feat/subagent-cost-accounting

Conversation

@josephschorr

@josephschorr josephschorr commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Motivation

A session's status.estimatedCost under-reports total spend — potentially by
more than an order of magnitude — because it only prices the orchestrator
model's tokens and ignores the cost of inner interactive toolkits the agent
drives.

A passthrough coding agent that drives Claude Code, for example, can spend the
large majority of its cost inside those sub-runs. That spend is already recorded
per invocation in the tool_session audit Kind's content.costUsd, but it
never reaches the cost reporter — so the stamped total reflects only the
orchestrator model and omits the inner toolkit spend entirely.

Summary of changes

Rolls each interactive toolkit's provider-reported cost into the session total,
itemized so provenance stays visible. Generic — keyed by outer tool name, no
per-toolkit branching.

  • Types (pkg/platform/pipeline, pkg/apis/v1alpha1): new ToolUsage and
    ToolCostBucket; SessionEndInfo.ByTool and EstimatedSessionCost.ByTool.
    Regenerated deepcopy, CRDs, install.yaml, and the CRD reference doc.
  • Accumulation (pkg/agent/runner): Loop.AddToolCost mirrors the existing
    addModelUsage (same usageMu, first-seen ordering, negative-cost guard,
    single round at the USD→micro-USD boundary). Fed in-process from the runner's
    interactive-tool result callback, regardless of the ToolSessionLog persist
    gate. Surfaced through SessionEndInfo.ByTool at fireSessionEnd.
  • Reporting (pkg/agent/postsession/cost): grand total is now
    Σ ByModel + Σ ByTool; tool cost is always provider-reported (never
    table-priced) so top-level PricingKnown is unchanged for existing sessions.
    The AmountMicroUSD/PricingKnown doc contract is updated: when a served
    model is unpriced, the total is now a documented lower bound (the priced
    components) rather than forced to 0. The closing cost notice gains a
    Sub-agent tools line.
  • admind (pkg/web/admind): the session-detail breakdown carries byTool;
    the budget breakdown and the Overview headline spend panel add each session's
    tool cost on top of the token-repriced estimate, over the same session scope
    that feeds the per-model rollup (no double-count).

Report-only: there is no cost-budget enforcement, and no payer/passthrough
marker (the inner spend is billed to the user's own subscription under
passthrough — a follow-up could annotate this).

Alternatives considered

  • Fold inner cost silently into amountMicroUSD — rejected: it breaks the
    "byModel buckets sum to the total" invariant and loses the provenance of a
    non-model cost. Itemizing under byTool keeps one headline number while
    showing where it came from.
  • Two separate top-level totals (operator-metered vs passthrough) — rejected
    for v1 in favor of a single itemized total; the split can be reintroduced as a
    billedTo marker later without a schema break.
  • Re-read tool_session records at SessionEnd instead of in-process
    accumulation — rejected: the result callback fires regardless of the
    ToolSessionLog persist gate, so in-process accumulation is strictly more
    reliable and matches the existing addModelUsage idiom.

Provenance

  • Author (person, or model and version): Joseph Schorr
  • Harness or tooling, with version: local development; mage targets for
    codegen and the test gate
  • Person who read the diff: Joseph Schorr (on review)

Ship gate

  • mage test:unit
  • mage test:integration
  • mage test:e2e

Results:

test:unit          exit 0, 0 failures (re-run at HEAD after the Overview fix)
test:integration   exit 0, 0 failures
test:e2e           exit 0, 0 failures — bronzethread + steelthread both ok

Regeneration

  • Changed a CRD-shaping field in pkg/apis/v1alpha1/*_types.go → ran
    mage gen:api and mage manifests
  • Edited anything under config/** directly → n/a (only via gen:api)
  • Changed a CRD schema → ran mage docs:crd
  • mage fmt:check clean

Coverage

  • User-visible behavior is exercised by tests — unit coverage for the
    accumulator, the SessionEnd wiring, the cost reporter (grand total,
    itemization, unknown-model lower bound, no-tool regression), and the three
    admind surfaces (session-detail, budget, Overview). Not a new agent-facing
    tool call, so no bronzethread bundle is required; the whole e2e + steel
    suites remain green.
  • Touches authorization / SpiceDB schema / approver model → n/a (none
    touched; estimatedCost is controller-owned status)

Before requesting review

  • Single change: session cost now includes inner interactive-toolkit spend.
  • A person reviews the diff on this PR. The generated files
    (zz_generated.deepcopy.go, install.yaml, config/**,
    site/content/docs/crd-agentsession.mdx) are mechanical — re-run
    mage gen:api && mage manifests && mage docs:crd and confirm the output
    is identical rather than reading them line by line.

Known follow-ups (deferred, non-blocking)

  • The byModel budget axis total excludes tool cost (tool spend has no served
    model to attribute under) while the other axes include it — a deliberate
    asymmetry worth a doc note or a synthetic "sub-agent tools" row.
  • An interactive-tool result that reports no billing still records a
    {tool, $0, pricingKnown: true} bucket in status.byTool (the parser
    collapses "reported 0" and "reported nothing"); no displayed total is affected
    since all surfaces gate on > 0. A costUSD > 0 gate or a threaded "reported"
    bool would tidy the raw status.

@vercel

vercel Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
openagentprimitives Ready Ready Preview Oct 2, 2026 10:50pm UTC

Request Review

This branch was successfully deployed

1 active deployment
Preview — e0f36f84 Deployed Oct 2, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant