Skip to content

Sub-agent LLM calls never record or emit token usage data (invisible to 'netclaw stats') #1597

Description

@petabridge-netclaw

Summary

When a sub-agent runs, none of its LLM calls are recorded in token metrics. Sub-agents are completely invisible to netclaw stats — all their internal token expenditures (input, output, thinking, cached) are lost.

This is not a regression; the sub-agent path has never implemented token tracking.

What Works (Main Session)

LlmSessionActor.FireLlmCall()
  → StreamingResponseReader.ReadAsync() returns ChatResponse with Usage
  → LlmResponseReceived handler:
    HandleToolCallResponse(lastMessage, ..., response.Usage)      // ← passes Usage
    HandleTextResponse(lastMessage, response.Usage, ...)         // ← passes Usage
    → _sessionMetrics.RecordTokenUsage(input, output)            // line 3516
    → EmitUsageOutput(usage) → UsageOutput event to Seq          // line 3532

What is Broken (Sub-Agent)

SubAgentActor.InvokeLlmAsync()
  → StreamingResponseReader.ReadAsync() returns ChatResponse with Usage ← data is HERE
  → LlmResponseReceived handler:
    var response = msg.Response  // ← has .Usage but never read!
    HandleToolCalls(lastMessage, toolCalls)   // ← no response.Usage passed
    // or
    Complete(true, text, ...)  // ← no response.Usage extracted
  → SubAgentResult returned with {Success, Output, AgentName, Outcome, Findings}
    // ← NO token fields at all

Three Data Loss Points

1. Sub-agent handler never reads response.Usage

The ChatResponse from StreamingResponseReader has token usage, but the sub-agent handler in SubAgentActor.cs:317-424 ignores it entirely. No RecordTokenUsage call, no EmitUsageOutput.

2. No _sessionMetrics injected into SubAgentActor

SubAgentActor has no ISessionMetrics dependency. It cannot record tokens even if it wanted to.

3. SubAgentResult carries no token fields

When the sub-agent finishes, it returns SubAgentResult with only {Success, Output, AgentName, Outcome, Findings, FindingsCount}. No token data on the wire. Parent receives it via CompletedSubAgentRun (in LlmMessages.cs) which also has no token fields.

Impact

  • netclaw stats shows parent session token counts but zero sub-agent contribution
  • Parent only sees its own single LLM call (which includes the sub-agents final output text but not the sub-agents internal token expenditures)
  • No way to see how many tokens a sub-agent consumed or how many LLM calls it made
  • Cannot correlate sub-agent cost with its duration or tool usage

Required Fixes

A. SubAgentActor must record token usage per LLM call

  • Inject ISessionMetrics into SubAgentActor constructor
  • In the LlmResponseReceived handler, after getting the response, call _sessionMetrics?.RecordTokenUsage(response.Usage?.InputTokenCount ?? 0, response.Usage?.OutputTokenCount ?? 0) for each LLM call
  • Optionally emit a UsageOutput event to Seq for sub-agent-level observability

B. Propagate token data back to parent

  • Add token fields to SubAgentResult record (SubAgentProtocol.cs)
  • Add token fields to CompletedSubAgentRun record (LlmMessages.cs)
  • Parent session should aggregate sub-agent tokens and record them in its own ISessionMetrics

C. Logging enhancement

  • Log token counts in the sub-agent summary line at SubAgentActor.cs:757-762 (currently only logs tool counts and duration)

Files Involved

File Change
SubAgentActor.cs Inject ISessionMetrics, extract response.Usage in handler, record per-call
SubAgentProtocol.cs Add InputTokens/OutputTokens to SubAgentResult
LlmMessages.cs Add InputTokens/OutputTokens to CompletedSubAgentRun
SubAgentSpawner.cs Pass ISessionMetrics through ToolExecutionContext or resolve in actor factory
LlmSessionActor.cs Aggregate sub-agent tokens when receiving CompletedSubAgentRun

Test Coverage Needed

  • Unit test: sub-agent LLM response handler extracts and records token counts
  • Integration test: parent session aggregates sub-agent token data in stats
  • Verify netclaw stats output includes sub-agent tokens

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions