Skip to content

feat(usage): separate auxiliary model calls from a Session's cost and cache hit rate #5691

Closed

Description

@liugddx

Problem

A Session's usage figures in Desktop mix the main agent loop with every auxiliary model call made for that Session, and there is no way to separate them.

  • Auxiliary calls pass telemetrySessionId: sessionId to runHostAuxiliaryModelCall. These are Session title, recap, goal evaluation, memory extraction, WorkHub intent/recall, and next-prompt suggestions once feat(desktop): add opt-in next prompt suggestions #5629 lands.
  • The Session Inspector loads usage:summary with { range: 'all', sessionId } (apps/desktop/src/preload/preload.ts:1367). That summary feeds both estimatedSessionCost and usageCacheHitRate (session-inspector-overview-model.ts:194, :245-254, computed as cacheRead / input).
  • UsageQuery (packages/core/src/usage-stats/types.ts:42-50) can filter by session, connection, provider, model, tool and status, but not by callKind. Consumers therefore cannot exclude auxiliary calls, and cannot break a Session's cost down by kind.

Until now this mattered little, because most auxiliary calls run once or rarely per Session. A per-reply auxiliary call such as #5629 changes that:

  • Cost. Every reply adds an extra call that the user sees as Session cost, with no way to tell it apart from the agent's work.
  • Cache hit rate. Auxiliary prompts do not share the main loop's cached prefix. Example: a main turn of 100k input tokens at 95% cache reads, plus one uncached 10k-token suggestion call, moves the displayed rate from 95% to about 86%. The main loop's caching did not change at all.
  • Daily Review. Its request counts are inflated in the same way.

A second, smaller accuracy gap is on the failure path of runHostAuxiliaryModelCall (execution-model-authority.ts:691-701). When a call is aborted or fails, it is recorded with inputTokens: 0, outputTokens: 0, even though the provider may already have processed, and billed, the request. For example, a reasoning call aborted by a short deadline could have been billed for its input plus any reasoning tokens.

Desired outcome

  • Add a callKind (or callKinds / excludeCallKinds) filter to UsageQuery, so consumers can ask for the main loop only, or for a per-kind breakdown.
  • Base the Session Inspector's cache hit rate on main calls only. Optionally show auxiliary spend as its own line next to the Session cost, instead of folding it in silently.
  • Where the provider or SDK reports partial usage on abort or error, record it. If it can't be known, mark the row as usage-unknown rather than zero.

Alternatives or workarounds

  • Auxiliary calls could omit telemetrySessionId, so they only appear in global usage. That hides them from the Session entirely, which is worse for cost transparency.
  • feat(desktop): add opt-in next prompt suggestions #5629 could state in its copy that suggestion spend is counted in the Session's cost. That documents the mixing but does not fix the hit-rate distortion.

Found while reviewing #5629 (review: #5629 (review)).

AI disclosure: Claude Code (Claude Opus) traced the usage path at 276faf1c9 and drafted this issue; liugddx reviewed it and is the human contributor of record.

Activity

  1. ggbdpq commented on Sep 24, 2026

    @ggbdpq
    Contributor

    take

  2. anbv29 commented on Sep 24, 2026

    @anbv29

    Hi! I’d like to verify which documentation snapshot is authoritative, reproduce the version/links problem, and propose a focused correction. Is there a preferred owning repository for the fix?

  3. ggbdpq commented on Sep 30, 2026

    @ggbdpq
    Contributor

    Picking up part 3 of this issue (the abort-path accuracy gap) — we hold the issue. Design intent before code, per the scoping we did internally:

    Scope (minimal core). runHostAuxiliaryModelCall's failure catch (~745-757 in execution-model-authority.ts at 9b089f58e) writes inputTokens: 0, outputTokens: 0 for aborted/failed calls. Per the issue's desired outcome, we'll record these rows as usage-unknown rather than zero, reusing the existing canonical machinery: ModelCallUsageBasis ('reported'|'partial'|'missing'), the schema CHECK that forbids token counts on missing rows, and the read-side coverage sums. The SDK reports no partial usage on abort (verified in tool-free-model-call.ts), so "record partial" has no room on this path — unknown is the honest value.

    One design decision to flag: auxiliary calls happen outside an agent run, so the canonical writer (run.recordModelCallAttempt) is unreachable; the authority's only write seam today is the legacy usage_llm_calls table. We plan to add a write method to InteractiveUsageStoresWriter.modelCalls (mirroring the existing server/canonical-usage-reader.ts read-seam precedent) and migrate both record paths (failure + zero-usage success) onto it. If maintainers would rather see a different seam shape, say so and we'll adapt — the migration surface is small (~150-250 lines, 4-6 production files).

    Out of scope for this round: exposing unknown-usage counts over the wire to Desktop (that touches protocol/index.ts and the overview payload — happy to add after this lands if wanted), and the createHostPluginModel callKind: 'main' routing (we'll document it in the PR rather than change it here).

    Generated-by: GLM-5.3-Flash (ZCode)

  4. liugddx commented on Oct 8, 2026

    @liugddx
    MemberAuthor

    This closed automatically when #5709 merged, before #5890 had landed. Both have now landed, so it is complete:

    • callKinds filter on UsageQuery: feat(usage): filter usage queries by model call kind #5709 (epoch 208). Without it, queries behave as before; unknown kinds are rejected at the protocol decoder.
    • Session Inspector cache hit rate from main calls only: the Inspector's summary now also reads the Session scoped to callKinds: ['main'] (apps/desktop/src/preload/usage-summary.ts:50), so auxiliary prompts no longer dilute the rate.
    • Aborted or failed auxiliary calls recorded as usage-unknown, not zero: fix(usage): record aborted auxiliary model calls as usage-unknown, not zero #5890 (usage schema 10). They count in ledger-wide coverage, and hosted settlement excludes only these run-less no_run rows, while still checking delegated child Sessions in its window.

    Thanks @ggbdpq for carrying both PRs through many review rounds.

    Not done, and not blocking this close:

    • Showing auxiliary spend as its own line next to the Session cost (marked optional above).
    • Capturing provider-reported partial usage on abort. Today such calls are recorded as missing rather than zero. If an SDK surfaces partial usage on abort later, that's a focused follow-up.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions