fix(subagents): record sub-agent LLM token usage to daily stats (#1597)#1600
Merged
Aaronontheweb merged 2 commits intoJul 8, 2026
Merged
Conversation
…law-dev#1597) Sub-agent LLM calls never recorded token usage, so every sub-agent's input/output tokens were invisible to `netclaw stats`. This was a pre-existing gap, not a regression: SubAgentActor never had an ISessionMetrics dependency and discarded the ChatResponse.Usage it already receives from StreamingResponseReader. The recent observability PRs (netclaw-dev#1428, netclaw-dev#1468, netclaw-dev#1472/netclaw-dev#1499) only added logs and pruned dead OTel Activities; none ever touched token tracking. Fix records at the source, mirroring the main session: - Inject ISessionMetrics into SubAgentActor (via SubAgentSpawner/CreateProps). DI already registers it as a process-wide singleton, so no Program.cs change. - Record response.Usage on every LlmResponseReceived (one per LLM turn: tool-call turns, retries, the forced-no-tools final turn, repair turns). - Add cumulative input/output token totals to the completion summary log. Recorded in the child, not propagated to the parent: ISessionMetrics is the SAME process-wide singleton both share, so re-recording in the parent would double-count, and folding sub-agent tokens into the parent's UsageOutput would corrupt its context-window percentage (the sub-agent has its own context window). Regression coverage (all four fail if the recording is removed): - SubAgentActor bills usage per LLM call and sums across the turn loop. - Completion summary log carries token totals. - Full spawner->CreateProps->actor wiring bills tokens to the spawner's metrics. Adds a UsageOverride hook to the sub-agent test FakeChatClient and extracts a shared RecordingSessionMetrics test helper.
Aaronontheweb
enabled auto-merge (squash)
July 8, 2026 16:34
Aaronontheweb
added a commit
that referenced
this pull request
Jul 8, 2026
… (#1600) Sub-agent LLM calls never recorded token usage, so every sub-agent's input/output tokens were invisible to `netclaw stats`. This was a pre-existing gap, not a regression: SubAgentActor never had an ISessionMetrics dependency and discarded the ChatResponse.Usage it already receives from StreamingResponseReader. The recent observability PRs (#1428, #1468, #1472/#1499) only added logs and pruned dead OTel Activities; none ever touched token tracking. Fix records at the source, mirroring the main session: - Inject ISessionMetrics into SubAgentActor (via SubAgentSpawner/CreateProps). DI already registers it as a process-wide singleton, so no Program.cs change. - Record response.Usage on every LlmResponseReceived (one per LLM turn: tool-call turns, retries, the forced-no-tools final turn, repair turns). - Add cumulative input/output token totals to the completion summary log. Recorded in the child, not propagated to the parent: ISessionMetrics is the SAME process-wide singleton both share, so re-recording in the parent would double-count, and folding sub-agent tokens into the parent's UsageOutput would corrupt its context-window percentage (the sub-agent has its own context window). Regression coverage (all four fail if the recording is removed): - SubAgentActor bills usage per LLM call and sums across the turn loop. - Completion summary log carries token totals. - Full spawner->CreateProps->actor wiring bills tokens to the spawner's metrics. Adds a UsageOverride hook to the sub-agent test FakeChatClient and extracts a shared RecordingSessionMetrics test helper.
Aaronontheweb
added a commit
to Aaronontheweb/netclaw
that referenced
this pull request
Jul 8, 2026
Reconciles cherry-picked dev commits (version-bump-to-beta.2, Grpc.Tools 2.82.0, SkillServer 0.4.0-beta.3, BOM frontmatter tests, memory-relevance-gate opsx artifacts) plus two commits we lacked: sub-agent LLM token usage daily stats fix (netclaw-dev#1600) and Netclaw.SkillClient 0.4.0-beta.3 -> 0.4.0-beta.4 bump (netclaw-dev#1596). Conflict resolutions: - Directory.Build.props: kept VersionSuffix alpha.onnx.1 (ours); we remain the alpha.onnx.1 experimental prerelease, not beta.2. - Directory.Packages.props: took Netclaw.SkillClient 0.4.0-beta.4 (theirs). - RELEASE_NOTES.md: kept the alpha.onnx.1 section on top, followed by a single 0.25.0-beta.2 section (content was identical on both sides), then the older history unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1597.
Problem
Sub-agent LLM calls never recorded token usage, so every sub-agent's input/output tokens were invisible to
netclaw stats.This is a pre-existing gap, not a regression.
git log -SforISessionMetrics/UsageDetails/SessionMetricsscoped toSubAgentActor.csreturns zero commits across all history — the actor never had anISessionMetricsdependency and silently discarded theChatResponse.Usageit already receives fromStreamingResponseReader. The recent observability PRs (#1428, #1468, #1472/#1499) only added logging and pruned dead OTelActivityobjects; none ever touched token tracking.Fix — record at the source
ISessionMetrics(theDailyStatsPublisher) is a process-wide singleton that the parent session and every sub-agent share. So the correct fix is to record each sub-agent LLM call once, in the child, exactly as the main session records its own calls:SubAgentActor— injectISessionMetrics?; recordresponse.Usageon everyLlmResponseReceived(one per LLM turn: tool-call turns, retries, the forced-no-tools final turn, and repair turns); add cumulative input/output token totals to the completion summary log.SubAgentSpawner— inject the DI singleton and pass it throughCreateProps. NoProgram.cschange: DI already registers the singleton and injects it into the optional parameter, matchingSessionCatalogService/MemoryCurationWorkerService.Why not also aggregate in the parent (the issue's suggestion B)?
Because the parent and child share the same singleton, re-recording propagated totals in the parent would double-count every sub-agent token in
netclaw stats. Folding sub-agent tokens into the parent'sUsageOutputwould also corrupt the parent's context-window percentage — the sub-agent has its own context window. Recording once at the source is both correct and minimal.Tests
Four regression tests, each verified to fail if the recording is removed (neutralizing the call made all four fail, incl. the summary log showing
inputTokens=0, outputTokens=0):Records_token_usage_to_session_metrics_on_text_response— single-call billing.Records_token_usage_for_every_llm_call_across_the_turn_loop— sums usage across a tool-call turn + final-text turn (the crux: internal calls, not just the final output).Completion_summary_reports_cumulative_token_totals— summary log carries token totals.Spawned_sub_agent_bills_its_llm_calls_to_session_metrics— fullSubAgentSpawner → CreateProps → actorwiring, so dropping the pass-through anywhere in that chain fails here.Adds a
UsageOverridehook to the sub-agent testFakeChatClientand extracts a sharedRecordingSessionMetricstest helper.Validation
Netclaw.Actors.Tests: 2551 passed, 0 failed;Netclaw.Daemonbuilds clean.dotnet slopwatch analyze: 0 issues. Copyright headers: all present.Follow-up
Filed #1599 to consolidate the three divergent
FakeChatClientdoubles into a sharedNetclaw.Tests.Utilitieshelper (deliberately kept out of this PR to stay focused).