Skip to content

feat: token and cost details on agents view rows - #2003

Merged
sethkarten merged 27 commits into
mainfrom
feat/agents-view-costs
Sep 3, 2026
Merged

sethkarten merged 27 commits into
mainfrom
feat/agents-view-costs

Conversation

@snimu

@snimu snimu commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Every agents-view row can now answer "what did this session cost" at a glance, in one unconditional format: ↑12k ↓1.2k · $0.42 ($1.10 w/ subagents). is input/sent tokens, output/received; $0.42 is the session's own spend and the parenthesized total is the whole subtree. Every row renders the full cell, zeros included — a session that never spent reads ↑0 ↓0 · $0.00 ($0.00 w/ subagents).

The chosen semantic is whole-file spend. A row's own cost is everything the session ever spent — every branch, including forked-away ones, and the summarization calls themselves: compaction (both split-turn slices) and branch-summary responses persist their usage on the entry they produce, and both producers fold it in — with child_usage_attributed entries subtracted so a child's cost is never counted twice. That is the money answer ("what did this session cost in total"), deliberately broader than /usage, which answers the context question for the current branch. Resident and on-disk rows compute the identical number — pinned by a resident-vs-scan equality test on a file with a forked-away branch — so a row never shifts at passivation or revival.

Mechanics: resident rows use a memoized getOwnUsageSummary() over the in-memory entries (memo keyed on entry count and tail id; measured cold cost 0.27 ms per call at 10k entries). On-disk rows accumulate the same total inside the catalog's existing scanSessionInfo pass (already line-parsing every file, cached by size+mtime) — no new IO, no new cache. Recursive cost is own plus all descendants, summed in the view over the unfiltered hierarchy, so search and scope filters never shrink the parenthesized total; the traversal is iterative and pinned overflow-safe at 10k depth. Passivated descendants stay counted across restarts: both list_saved_sessions composition points (supervisor and worker daemon) append live spawn-ledger children the directory scan missed — their transcripts live in session-artifacts, which the scan never visits — through one shared walk (withPassiveRlmDescendantInfos: one readSessionInfo per descendant, parentSessionPath/rlmDepth backfilled from the edge, tombstoned edges excluded), so an inactive parent's subtree total is identical before and after a supervisor restart. On the wire: an optional usage field (inputTokens, outputTokens, cost) on session summaries and saved-session rows, DAEMON_SCHEMA_REVISION 26. Old daemons produce rows without the field; the view renders the same cell with zeros.

Deliberate changes and known limits: the "N turns" message-count detail on inactive rows is removed — the details cell is now usage · age. Entries above the scan's max line length keep their existing skip, so one multi-megabyte assistant message can undercount slightly (giant tool results carry no usage and were always skipped). A failed compaction persists no entry, so its billed tokens are knowingly unrecoverable.

Stacked on fix/agents-view-busy-descendant-indicator (#1986); draft until that merges, then retargeted to main.

Validation: every behavior across the review rounds keeps exactly one pin, on consolidated fixtures. One format test covers all four row shapes (and message-count removal); one forked-file fixture pins scan/resident equality, attribution subtraction, and compaction + branch_summary billing terms as one pair of exact totals; one hierarchy fixture pins the unfiltered rollup with own costs intact plus the 10k deep chain; one producer test covers active and saved rows; one supervisor-restart fixture pins restored catalog rows with ledger-edge topology beating a forked-away transcript header (plus tombstone exclusion); one two-family worker fixture pins restart rows through the worker handler, per-sessions-dir ledger isolation, and broken-ledger degradation; the view keeps the money-across-restart pin (recursive total identical for a live row vs a catalog-only row) and the no-duplicate merge pin; the real-summarizer compaction test pins that own spend grows by exactly what the entry recorded (and memo invalidation). All were proven fail-unfixed against the head that preceded their round — except the no-duplicate pin, whose input cannot exist pre-fix and which is mutation-verified against a broken alias merge; consolidation was re-verified by mutation on three survivors from different rounds (dropped parenthesized total, dropped scan summarization fold, header-first topology — each fails exactly its pin). Catalog-load cost of the ledger merge, measured at 300 saved sessions with 36 passivated descendants, cold ledger: scan 33 ms, merge +9 ms (~0.25 ms per descendant; zero descendants ≈ zero added cost). agents-view suites, rlm-ledger, agent-roster, daemon-agent-roster, subagent-summary-line, daemon-session-list, session-manager file-operations, daemon-protocol (schema sync), daemon-mode, daemon-supervisor, daemon-stop-confirm, daemon-launch/client/routed-client, package-self-update, agent-connection-daemon — green in a sanitized env; root npm run check green.

Net src: +267/−23 (test: +358/−2) — the formatter/producers/rollup plus compaction/branch-summary billing (RES-1258) and the catalog merge with its review-round hardening (RES-1262); all composition over existing scans, folds, and walks; one new exported helper, no new state or caches.

Linear: RES-1258 https://linear.app/primeintellect/issue/RES-1258
Linear: RES-1262 https://linear.app/primeintellect/issue/RES-1262


Note

Medium Risk
Touches session accounting, catalog scanning, and daemon protocol revision 26; incorrect usage rollup or ledger merge could misreport costs or omit passivated subagents from the roster.

Overview
Agents view rows now show input/output tokens, the session’s own dollar cost, and a parenthesized recursive total including subagents (e.g. ↑12k ↓1.2k · $0.42 ($1.10 w/ subagents)), replacing the inactive-row message count. Recursive totals are computed on the unfiltered session tree so search/scope does not shrink parent totals.

Billing semantics treat a row’s “own” cost as whole-file spend: assistant usage on the current branch, plus compaction and branch-summary LLM calls (usage is captured and stored on those entries), with child usage attribution subtracted so subagent spend is not double-counted. Live sessions expose this via memoized getOwnUsageSummary(); catalog scans compute the same totals during the existing line walk.

Daemon / wire: optional usage on session and saved-session summaries; schema revision 26. Saved-session listing merges passivated RLM descendants from the spawn ledger (withPassiveRlmDescendantInfos) so artifact-only child sessions and their costs still appear after restart.

Reviewed by Cursor Bugbot for commit af309b8. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Show token and cost details on Agents View rows

  • Agents View rows now display input tokens, output tokens, direct session cost, and recursive subagent cost, replacing the previous message-count detail
  • Usage tracking flows through the full pipeline: compaction and branch-summary entries now persist their LLM Usage, scanSessionInfo aggregates assistant plus summarization usage minus child attribution into a SessionUsageSummary, and the daemon protocol (revision 26) carries it to clients
  • computeRecursiveCosts walks the unfiltered unified-session hierarchy to produce descendant-inclusive cost totals for each row
  • Daemon list_saved_sessions now merges passive RLM descendants using a per-directory ledger (rlmSpawnLedgerFor) instead of always using the default ledger
  • Behavioral Change: rows without usage data now render zero-valued token and cost fields where the message count previously appeared; daemon schema advances from revision 25 to 26

Macroscope summarized af309b8.

The "N subagents running" indicator only tallied direct children whose
row classified as running, so a busy grandchild under an idle
intermediate child left every ancestor reading "N subagents" with a zero
count. The tally is now a bottom-up pass over the built rows - the live
per-descendant truth - replacing the direct-child increment.

Indicator only, per policy: a session is Running when it works itself;
idle ancestors of busy subtrees stay in Idle. Within Idle they now rank
above plain idle rows, and a collapsed group's summary row renders its
running count in the success color instead of dimmed so the busy subtree
is discoverable without expanding.

Diagnosis credit: Vincent Bailly (VincentBailly#9) traced
the stale hasRunningRlmChildren snapshot and the row-walk mechanism;
this adopts his walk for the indicator while rejecting the section
promotion.

RES-1253
The descendant tally read row sections after
propagateHeartbeatStateToAncestors had promoted idle ancestors to
running, so a heartbeat-active grandchild counted its promoted parent
too. The tally now runs before the promotion pass and counts
intrinsically busy rows only; once that propagation pass is deleted
(#1967) the ordering is a no-op.

RES-1253
The recursive tally threw RangeError on a deep child chain just by
opening the agents view. The nesting loop assigns every row at most one
parent, so the tree is a forest: a reverse breadth-first pass over one
work list computes the same bottom-up counts iteratively, with the
pre-promotion ordering kept.

RES-1253
…dren

Second half of the RES-1253 policy, user-approved: Running means the
session you enter is doing work now. Delegated child work no longer
classifies a session as running - the busy-descendant badge, count, and
idle ranking from the first half carry the delegation signal.

hasRunningRlmChildren loses its section-classification role in both
owners: classifySessionRosterStatus composes busy from the session's own
activity/isSessionActive, and isActiveSessionBusy (the worker's activity
axis) no longer holds a settled parent at "working" for its children.
The "subagents running" status label dies with the section it had to
agree with. The field itself stays on the wire, and isSessionSummaryBusy
keeps it on purpose for its residency and shutdown-safety consumers
(worker eviction snapshots, empty-draft eviction, busy client-owned
session counts, daemon stop confirmation).

RES-1253
isActiveSessionBusy served two meanings after the classification change:
the display activity axis (session's own work only, correct) and worker
recovery plus draft-discard (where a running RLM child is live work that
dies with the worker). The recovery journal recorded busy:false for a
settled parent with a running child, so a worker death skipped its
interruption record and notice.

The shared predicate is deleted and each meaning gets one named owner:
activeActivityForSession reads the session's own isSessionActive
directly, and hasLiveSessionWork (own turn or running RLM child) backs
recordWorkerRecoveryState and isDiscardableDraft. No caller can grab the
wrong meaning blind.

RES-1253
Every agents view row with usage data now reads
`<input>/<output> | $<own> ($<recursive>)` in the details cell, replacing
the message-count detail. Own numbers are the session's whole-file spend
(every branch, forks included, attributed child usage subtracted) - the
money answer, deliberately broader than /usage's current-branch context
answer, and identical for resident and on-disk rows so nothing shifts at
passivation or revival. The recursive total rides the same reverse
breadth-first traversal as the running-subagent tally, summing each
descendant row's own cost exactly once.

Producers: resident summaries compute the total from the already-loaded
entries, memoized until entries change (0.27 ms measured cold at 10k
entries); saved rows accumulate it inside the existing mtime/size-cached
catalog scan, so old files get costs with no new IO and no new cache.
Entries over the scan's max line length keep their existing skip, so a
giant assistant message can undercount slightly. Schema revision 26
publishes the new optional summary and saved-row field.

RES-1258
@snimu
snimu marked this pull request as ready for review September 2, 2026 19:30
Comment thread packages/coding-agent/src/core/session-manager.ts
Comment thread packages/coding-agent/src/modes/agents-view/agents-view-state.ts Outdated
Comment thread packages/coding-agent/src/core/session-manager.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-session-list.ts
Details cell now reads `↑12k ↓1.2k · $0.42 ($1.10 w/ subagents)`; the
parenthesized total renders only when descendants added spend visible at
cent rounding, so leaf rows read `↑500 ↓50 · $0.68`. Arrow glyphs follow
the existing TUI vocabulary (keybinding hints, token status) and the
width pipeline already treats them as single cells.

RES-1258
Review round on the usage feature:

- The catalog scan subtracted attributed child usage from disk values
  that (in append-only files) still carry the assistant's ORIGINAL
  model-response usage, double-subtracting and shifting numbers at
  passivation. Full-file rewrites (migrations, forks) can also persist
  the already-folded aggregates, so raw summation is wrong for those
  files instead. The scan now mirrors the loader exactly: fold each
  attribution's aggregate onto its target, then subtract the child usage
  - both disk representations cancel to the same own spend the resident
  computation reports. The equality pin now goes through a real v3
  flushed-then-attributed fixture (the old fixture had no attribution,
  which is why it never caught this).
- Recursive cost was computed over search-filtered rows, so a matching
  parent showed an incomplete "w/ subagents" total. The rollup now comes
  from the unfiltered record hierarchy (computeRecursiveCosts over the
  unified index) and filtering can no longer change the number.
- Zero-spend sessions published 0/0/$0.00 live and nothing once
  passivated; sessionUsageSummaryFrom now returns undefined for zero
  totals, shared by both producers, so rows stay age-only until real
  spend exists.

RES-1258
Since Running means the session's own work, an idle parent of a running
crew was branching into the delete path: killSubagent chose stop-vs-
delete from the display section and stopAgentForDeletion treated
activity==='working' as the only live work. Destructive actions are
safety consumers of the display/safety split: one row-level hasLiveWork
(own section running, busy descendants, or the wire running-children
flag) now drives the stop-first branch, the deletion flow, and the
confirmation verb.

Also trimmed the tally comment to current behavior.

RES-1253
Comment thread packages/coding-agent/src/modes/agents-view/agents-view-mode.ts
Stopping a subagent row whose own run already settled reported
'Subagent already finished' while a nested descendant kept running:
cancelRlmChildRun only cancelled the targeted run itself, and a fully
released child (run removed, session retained) was never matched by id
at all. Cancellation now descends where the tree lives: a settled target
stops every running or queued run in its retained session's subtree
(cancelRunningRlmDescendants, mirroring hasRunningRlmChildren's walk),
and the returned flag stays truthful so the UI says stopped only when
something stopped.

RES-1253
Every agents view row now renders the full details cell
`↑<up> ↓<down> · $<own> ($<total> w/ subagents)` over four defaulted
numbers - no presence branching, no thresholds, no conditional paren. A
session that never spent reads `↑0 ↓0 · $0.00 ($0.00 w/ subagents)`, and
a zero-usage parent with billed subagents shows its subtree spend
instead of hiding it.

RES-1258
Comment thread packages/coding-agent/src/modes/agents-view/agents-view-mode.ts
Comment pass: multi-line narration cut to one-line invariant guards
(destructive live-work gate, residency-vs-section busy split, worker-
death live work) and code-readable notes deleted. Test pass: the direct-
child indicator pin folded into the grandchild pin (the general case),
which now also carries the idle-label and wire-flag assertions.
Comment pass (>80%): 24 added comment lines down to 4 one-line invariant
guards (loader-fold cancellation in the scan, passivation-invariance on
the resident getter, unfiltered rollup, the revision note). Test pass:
the scan-accumulation and resident-equality pins merged onto one forked-
and-attributed fixture; the descendant-rollup and filtered-total pins
merged with a grandchild; the format pin asserts all four row shapes
from one fixture; redundant zero-case and permutation tests deleted.
Comment thread packages/coding-agent/src/modes/agents-view/agents-view-mode.ts
Comment thread packages/coding-agent/src/modes/agents-view/agents-view-state.ts
Cancelling a live child run aborts it, and abort cascades into the
child's ACTIVE runs - but running work retained under a settled
descendant of that child was the end of the line: neither the cascade
nor the cancel walk visited it. cancelRlmChildRun now descends into the
target's session after cancelling it, and cancelRunningRlmDescendants
descends at every node instead of treating running runs as leaves, so
cancellation is exhaustive over the subtree regardless of each node's
run state. The returned flag stays truthful.

RES-1253
Comment thread packages/coding-agent/src/core/agent-session.ts
An RLM child's transcript lives in session-artifacts, which the saved-
session scan never visits. While something resident remembered the child
its row survived; after a supervisor restart an inactive parent's
passivated descendants silently disappeared from the agents view, and
their spend vanished from the parent's subtree total.

Both list_saved_sessions composition points now append live spawn-ledger
children the scan missed, through one shared walk: readSessionInfo per
descendant, parentSessionPath/rlmDepth backfilled from the edge, deleted
edges excluded. The view already renders saved-only descendants, merges
resident duplicates by session identity, and rolls saved usage into
recursive totals, so restored rows restore the money with no view change.

Measured at 300 sessions with 36 passivated descendants: scan 33ms,
merge +9ms (cold ledger, ~0.25ms per descendant).

RES-1262
RES-1258
Comment thread packages/coding-agent/src/modes/daemon/rlm-ledger.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 03e09d4. Configure here.

Comment thread packages/coding-agent/src/modes/daemon/rlm-ledger.ts Outdated
Summarization was invisible money: the compaction model call(s) - two on
a split turn - and branch-summary calls returned usage that was dropped
on the floor, so a session that compacted often under-reported what it
actually cost. The summarizer responses' usage is now folded (both
split-turn slices) and persisted on the compaction/branch_summary entry,
and both own-spend producers add the same term: own spend =
fold(assistant usage) + fold(summarization usage) - attributions,
identical resident and scanned. Old entries without the field fold as
zero; a failed compaction persists nothing, so failed-call billing is
not recoverable - there is no committed entry to carry it.

RES-1258
Three review findings on the merge, all real: the transcript header's
parentSession can point at a forked-away ancestor, so the ledger edge is
now the authoritative topology (family() semantics) for parent and
depth; ledgers are per sessions-dir family, so each catalog request now
reads the ledger of the directory it lists instead of always the
default one; and a broken ledger (unreadable, over-quota) no longer
fails the whole list_saved_sessions response - the merge logs and
returns the scanned catalog.

RES-1262
The worker-daemon catalog pins from three rounds folded onto one
fixture: restart rows, per-family ledger isolation, and broken-ledger
degradation now share the two-family scaffold. Redundant assertions and
narration comments cut; every behavior keeps exactly one pin.
Base automatically changed from fix/agents-view-busy-descendant-indicator to main September 3, 2026 21:00
Comment thread packages/coding-agent/src/core/agent-session.ts
Resolve the agents-view conflicts by preserving main’s busy-descendant behavior alongside the PR’s usage and recursive-cost reporting.
@sethkarten
sethkarten self-requested a review September 3, 2026 21:44
@sethkarten
sethkarten merged commit d74a75f into main Sep 3, 2026
23 checks passed
@sethkarten
sethkarten deleted the feat/agents-view-costs branch September 3, 2026 22:08
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.

2 participants