feat: trace Task/Agent subagents as nested generations#17
Open
noelschmitt wants to merge 1 commit into
Open
Conversation
The Stop/SessionEnd hook only processed the main session transcript, so subagent (Task/Agent) runs were emitted as an opaque `Tool: <name>` span with zero usage and no model. Their generations — often on a cheaper tier like Haiku — were invisible and unpriced. Claude Code writes each subagent's transcript to `<dir>/<session>/subagents/agent-<agentId>.jsonl`, and the main transcript's `toolUseResult` carries that `agentId`. This wires the two together: when a tool span is Task/Agent, load the subagent transcript, merge its assistant messages by message.id (so streamed chunks are not double-counted) and emit each as a generation nested under the tool span, with the real model and usage_details. Gated behind CC_LANGFUSE_SUBAGENTS (default on).
|
|
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.
Problem
The Stop/SessionEnd hook only processes the main session transcript. Dispatched subagents (
Task/Agent, e.g.Explore) are therefore emitted as an opaqueTool: <name>span with no model andtotalUsage: 0. Their generations — frequently on a cheaper tier like Haiku — are invisible in Langfuse and unpriced, so session cost is systematically under-reported for anyone using subagents.Fix
Claude Code writes each subagent's transcript to
<dir>/<session>/subagents/agent-<agentId>.jsonl, and the main transcript'stoolUseResultcarries thatagentId. This wires the two together: when a tool span isTask/Agent, the hook loads the subagent transcript, merges its assistant messages bymessage.id(so streamed chunks aren't double-counted) and emits each as a generation nested under the tool span — with the real model andusage_details.Note:
toolUseResult.usageonly records the subagent's final message, not a sum, so reading the subagent transcript is the only way to recover the full usage.Gated behind a new
CC_LANGFUSE_SUBAGENTSoption (default on).Verification
Verified offline against a real session: a 17-message
Exploresubagent surfaces as Haiku generations with 5,634 previously-untracked output tokens; the terminal generation's usage reconciles exactly with the value Claude Code records intoolUseResult. No double-counting (33 transcript rows → 17 message ids → 17 generations).Changes are fail-open (missing/unreadable subagent files yield no generations) and reuse the existing
get_model/get_usage_details_from_row/merge_assistant_rows/_start_backdatedhelpers.