Skip to content

reader: pair Task subagents via toolUseResult.agentId; surface orphans as UnattachedGroup #435

Description

@willwashburn

Context

Claude Code's Task tool spawns subagents whose transcripts land in sidecar .jsonl files at:

~/.claude/projects/<slug>/<sessionId>/subagents/agent-<agentId>.jsonl

The parent session's tool_result row for the Task tool_use carries a toolUseResult.agentId that references the sidecar's filename. Burn's current ingest may drop these or attribute them imprecisely; we need a structural pairing pass plus a first-class "orphan" bucket for sidecars that don't pair (e.g., crash mid-dispatch, slash-command synthetic dispatches).

Prior art: agent-profiler handles both cases. Source:

  • lib/claude-code/sessions.js — discovery walks <sessionId>/subagents/agent-*.jsonl.
  • lib/claude-code/traces.jstoolUseResult.agentId linkage and UnattachedGroup synthesis for orphans.
  • lib/traces/types.d.tsUnattachedGroup type: "skill-dispatched subagent subtree with no paired tool_use in the main transcript".

Proposal

  1. Discovery: extend the Claude reader to enumerate <sessionId>/subagents/agent-*.jsonl alongside the main <sessionId>.jsonl.
  2. Pairing: for each subagent transcript, find the parent session's tool_result row whose toolUseResult.agentId matches. Attach the subagent's records under that tool_use's span subtree.
  3. Orphan bucket: any subagent transcript that doesn't pair becomes an UnattachedGroup — a top-level span with no parent ToolUse. Surface it in span tree output and in the inference-flow DAG with an unattached edge style.
  4. Subagent metadata: read the companion agent-<id>.meta.json if present for agentType and other harness-emitted attributes.

Implementation sketch

// crates/relayburn-sdk/src/reader/claude/subagents.rs (new)

pub struct SubagentTranscript {
    pub agent_id: String,
    pub agent_type: Option<String>,        // from .meta.json
    pub records: Vec<Record>,
    pub paired_tool_use_id: Option<String>, // None => UnattachedGroup
}

pub fn discover_subagents(session_dir: &Path, session_id: &str) -> Vec<SubagentTranscript>;
pub fn pair_to_main(main: &[Record], subs: Vec<SubagentTranscript>) -> Vec<SubagentTranscript>;

Wire into the span-tree builder so subagent records become a nested Subagent subtree at the correct ToolUse node, or a top-level UnattachedGroup when unpaired.

Open questions

  1. agentId extraction. Is it always on toolUseResult.agentId of the Task tool_result, or also embedded elsewhere (assistant tool_use input)? Audit a few real sessions.
  2. Slash-command synthetic dispatches. Some subagents are spawned by slash commands, not Task. These are expected to be unpaired in the main transcript. Don't treat as errors — treat as UnattachedGroup and let the slash-command triad pass (separate issue) optionally nest them under a synthesized Skill span.
  3. Performance. Subagent sidecar count per session can reach hundreds in agent-heavy workflows. Discovery should be lazy — only walk the subagents directory when something asks for it.
  4. Schema. Should subagent transcripts ingest into the same turns table with a subagent_id column, or a separate subagent_turns table? Recommend same table with subagent_id nullable — keeps queries uniform.

Acceptance

  • Reader discovers <sessionId>/subagents/agent-*.jsonl automatically.
  • tool_result rows with toolUseResult.agentId pair to the matching sidecar.
  • Unpaired sidecars surface as UnattachedGroup in the span tree output.
  • agent-<id>.meta.json is read when present and agentType is exposed as a span attribute.
  • Fixture: session with two paired Task dispatches and one orphan subagent — span tree shows two nested + one unattached.
  • burn summary (or new burn subagents) counts pairings and orphans.

References

  • agent-profiler: lib/claude-code/sessions.js, lib/claude-code/traces.js subagent pairing logic, lib/traces/types.d.ts UnattachedGroup.
  • Related: span-tree foundation (provides the Subagent node level), inference-flow DAG (renders the dispatch/return/unattached edges).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions