Skip to content

feat: trace Task/Agent subagents as nested generations#17

Open
noelschmitt wants to merge 1 commit into
langfuse:mainfrom
31M-Agentur:feat/subagent-tracing
Open

feat: trace Task/Agent subagents as nested generations#17
noelschmitt wants to merge 1 commit into
langfuse:mainfrom
31M-Agentur:feat/subagent-tracing

Conversation

@noelschmitt

Copy link
Copy Markdown

Problem

The Stop/SessionEnd hook only processes the main session transcript. Dispatched subagents (Task/Agent, e.g. Explore) are therefore emitted as an opaque Tool: <name> span with no model and totalUsage: 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's toolUseResult carries that agentId. This wires the two together: when a tool span is Task/Agent, the hook loads the subagent transcript, merges its assistant messages by message.id (so streamed chunks aren't double-counted) and emits each as a generation nested under the tool span — with the real model and usage_details.

Note: toolUseResult.usage only 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_SUBAGENTS option (default on).

Verification

Verified offline against a real session: a 17-message Explore subagent surfaces as Haiku generations with 5,634 previously-untracked output tokens; the terminal generation's usage reconciles exactly with the value Claude Code records in toolUseResult. 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_backdated helpers.

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).
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

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.

3 participants