[FEATURE] 1. Agent Hierarchy in Hook Events, 2. Intermediate Text Output Hook, 3. SubagentStart Hook

Status Fixed / completed
Maintainer reply None cached
Activity 13 comments · opened Dec 20, 2025 · closed Aug 17, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

When Claude Code spawns sub-agents via the Task tool, all hook events share the same session_id regardless of which agent produced them. This makes it impossible to:

  1. Identify which agent generated an event - A session with 3 parallel agents produces 50+ events with no way to attribute them
  2. Track when sub-agents start - Only SubagentStop exists, not SubagentStart
  3. Capture intermediate text output - Claude's explanations between tool calls are not exposed to hooks

This limitation prevents building observability tools, debuggers, or dashboards for multi-agent workflows.

Proposed Solution

Proposed Solution

  1. Add agent hierarchy fields to all hook events

interface HookEvent {
// Existing fields
session_id: string;
hook_event_type: string;
// ...

// New fields
agent_id?: string; // Unique ID of the agent that produced this event
parent_agent_id?: string; // ID of the agent that spawned this one
parent_session_id?: string; // Session ID of the parent (if different)
agent_slug?: string; // Human-readable agent type (e.g., "Explore", "Plan")
}

  1. Add SubagentStart hook

Fires when a sub-agent is spawned via the Task tool:
{
"hook_event_type": "SubagentStart",
"agent_id": "new-agent-id",
"parent_agent_id": "parent-id",
"subagent_type": "Explore",
"description": "Search for auth patterns"
}

  1. Add AssistantOutput hook (optional)

Fires when Claude outputs text between tool calls:
{
"hook_event_type": "AssistantOutput",
"text": "I'll search for existing authentication patterns first...",
"is_final": false
}

Alternative Solutions

_No response_

Priority

Critical - Blocking my work

Feature Category

Configuration and settings

Use Case Example

<img width="1310" height="719" alt="Image" src="https://github.com/user-attachments/assets/e40d0382-eb1d-424c-9873-18451a5c922a" />

Context

I'm building an observability dashboard that visualizes Claude Code sessions in real-time. The dashboard shows a canvas with nodes representing sessions, event timelines, and a detail panel for inspecting activity.

---
Scenario: User Asks Claude to Plan and Implement a Feature

Step 1: User submits prompt
User: "Plan how to add authentication to my app, then implement it"

Step 2: Claude spawns sub-agents
Claude decides to use the Task tool to spawn parallel agents:

  • Plan agent - designs the authentication architecture
  • Explore agent - searches codebase for existing auth patterns

Step 3: Sub-agents execute
Both agents run simultaneously, each making multiple tool calls (Read, Grep, Write, etc.)

---
Current Behavior (The Problem)

My hooks receive events, but all events have the same session_id:

{"session_id": "abc-123", "hook_event_type": "PostToolUse", "tool_name": "Read", ...}
{"session_id": "abc-123", "hook_event_type": "PostToolUse", "tool_name": "Grep", ...}
{"session_id": "abc-123", "hook_event_type": "PostToolUse", "tool_name": "Read", ...}
{"session_id": "abc-123", "hook_event_type": "PostToolUse", "tool_name": "Write", ...}

Result in my dashboard:

  • ❌ Cannot show separate nodes for each agent on canvas
  • ❌ Cannot group events by which agent produced them
  • ❌ Cannot draw parent→child relationship edges
  • ❌ All 50+ events appear in one flat timeline
  • ❌ User cannot tell which agent did what

Additionally, Claude's explanatory text like "I'll search for existing auth patterns first..." never reaches my dashboard because there's no hook for intermediate output.

---
Desired Behavior (With Proposed Features)

Hook events would include agent hierarchy:

{"session_id": "abc-123", "agent_id": "abc-123", "hook_event_type": "PostToolUse", "tool_name": "Task", ...}
{"session_id": "abc-123", "agent_id": "plan-456", "parent_agent_id": "abc-123", "hook_event_type": "SubagentStart", ...}
{"session_id": "abc-123", "agent_id": "explore-789", "parent_agent_id": "abc-123", "hook_event_type": "SubagentStart", ...}
{"session_id": "abc-123", "agent_id": "plan-456", "hook_event_type": "PostToolUse", "tool_name": "Read", ...}
{"session_id": "abc-123", "agent_id": "explore-789", "hook_event_type": "PostToolUse", "tool_name": "Grep", ...}

Result in my dashboard:

  • ✅ Canvas shows 3 nodes: Parent → Plan agent, Parent → Explore agent
  • ✅ Events grouped by agent in timeline
  • ✅ Purple edges connect parent to children
  • ✅ User can expand each agent to see its specific activity
  • ✅ Clear visualization of parallel execution

---
Summary

| Feature | Enables |
|----------------------|---------------------------------|
| agent_id in hooks | Grouping events by agent |
| parent_agent_id | Drawing hierarchy edges |
| SubagentStart hook | Knowing when agents spawn |
| AssistantOutput hook | Capturing Claude's explanations |

These features would enable building proper observability tooling for complex multi-agent workflows, which is increasingly important as users leverage Claude Code's Task tool for parallel execution.

Additional Context

_No response_

View original on GitHub ↗

12 Comments

kitaekatt · 7 months ago

This issue is affecting my ability to develop gating hooks for sub-agent that depend on session context. Hooks can't distinguish between main agent and sub-agents preventing session-behavior based hooks for sub-agents.

alperta · 7 months ago

+1

aparente · 7 months ago

+1

usedhonda · 7 months ago

+1 for this feature!

I'm building a macOS menu bar app that monitors Claude Code sessions:
https://github.com/usedhonda/cc-status-bar

When parallel subagents run, I can't tell which one needs attention because they all share the same session_id. With agent_id, I could show a proper hierarchy in the menu.

Happy to help test if this gets implemented.

tuanardouin · 6 months ago

+1 - Subagent are great but we can't easily follow them.

Butanium · 6 months ago

+1!!

Butanium · 6 months ago

Related workaround for distinguishing main agent vs subagent in hook events (without agent hierarchy fields): https://github.com/anthropics/claude-code/issues/16424#issuecomment-3880522301

TL;DR: grep the main transcript for tool_use_id — main agent tool calls appear there, subagent calls don't. Works but fragile (requires backoff for transcript flush, undocumented structure).

🤖 Generated with Claude Code

mr-lee · 6 months ago

The Agent SDK already passes tool_use_id as the 2nd callback argument to SubagentStop handlers and references parent_tool_use_id in the troubleshooting section. The runtime clearly tracks the Task↔subagent association - it just needs to be surfaced in CLI hook stdin too.

aps-b2tech · 6 months ago

+1

yokoya1006 · 5 months ago

+1 — Role-based access control in PreToolUse hooks is impossible due to missing agent identity information

Without agent identity information in hook events, role-based access control — such as restricting the main agent's file operations while allowing sub-agents to operate freely — is impossible; we have tried nine user-side detection approaches and all have failed.

All patterns that need to be distinguished, and the gaps in each approach:

Within a single session alone, there are three types of agents that hooks need to distinguish: the main agent, sub-agents spawned via the Agent tool (same process), and sub-agents spawned via the Task tool (separate process). When running multiple Claude Code instances across multiple terminals, each session contains these three types, requiring identification of "which agent, in which session."

However, current hook events provide no information to make these distinctions:

  • session_id: Sub-agents spawned via the Agent tool share the same session_id as the main agent, making them indistinguishable.

Meanwhile, sub-agents spawned via the Task tool have a different session_id — an asymmetric behavior

  • Environment variables (CLAUDE_CODE_AGENT_NAME, etc.): Not set for sub-agents spawned via the Task tool
  • File-based state management: State files conflict across parallel sessions, causing a main agent in one session to be misidentified as a sub-agent of another

Due to this combination of asymmetry and cross-session conflicts, no user-side detection method can reliably cover all cases.

<details>
<summary>Details of detection approaches we tried (3 categories, 9 methods total)</summary>

  • Environment variable–based (CLAUDE_CODE_AGENT_NAME, hook-managed custom env vars, SSE_PORT): Some agent types never receive the variable; unavailable on first invocation; cannot distinguish agents within the same session
  • File/registry-based (JSON session registry, marker files, session_id file persistence): File lock contention, TTL management failures, overwrite conflicts during parallel sessions
  • Heuristic-based (PID comparison, transcript_path pattern analysis, 7-signal weighted scorer): Environment-dependent and timing-dependent, producing frequent false positives/negatives; aggregating unreliable signals does not improve reliability

</details>

The fail-open / fail-safe dilemma:

If detection is uncertain and we fail-open (assume sub-agent → skip guard), a misidentified main agent causes the entire guard to collapse. If we fail-safe (assume main agent → apply guard), legitimate sub-agent operations get blocked. Without authoritative identity information from the platform, neither design can be made reliable.

How each proposed field addresses these problems:

  • agent_id: Uniquely identifies the three agent types within a session — main, Agent tool sub-agent, and Task tool sub-agent
  • parent_agent_id: Enables deterministic main-vs-sub identification (null = main agent)
  • parent_session_id: Resolves cross-session conflicts by identifying which session an agent belongs to

These fields would enable role-based tool access control, per-agent resource metering, and observability dashboards for multi-agent workflows — all with deterministic reliability.

one1zero1one · 5 months ago

We run an autonomous agent pipeline with OTel traces exported to Honeycomb. We've built proper span hierarchy through SDK hooks, but subagents break it. What we get:

agent.reasoning
├── tool.Agent (launches subagent A)
├── tool.Agent (launches subagent B)
├── tool.Read   ← which subagent? unknown
├── tool.Bash   ← which subagent? unknown
└── ... 80 more flat siblings

What standard OTel parent-child semantics would give us with agent_id on hook events:

agent.reasoning
├── tool.Agent (subagent A)
│   └── tool.Read, tool.Bash...
├── tool.Agent (subagent B)
│   └── tool.Glob, tool.Write...

This isn't a special-case request — it's standard OpenTelemetry span parenting. The data to make it work (which agent triggered the hook) just isn't in the hook payload today. We've exhausted every workaround and evaluated 20 alternative frameworks — none solve it. Adding agent_id to hook events is the only path to correct OTel traces for multi-agent runs.

kcarriedo · 3 months ago

+1, and the urgency on this is higher than it looks if you're trying to build any kind of cross-agent dashboard / debugger / cost-attribution tool. The current session_id-only payload effectively means hook-based observability is a single-process abstraction in a world where the actual unit of work is the agent.

A few notes from trying to build this exact dashboard externally:

The SubagentStart hook is the load-bearing one. Without it, you can observe results but never causal chains — you see a PostToolUse happen and you can't tell whether it was a parent re-reading state to verify a child's work, or a child doing the work. Spawn-time emission of {agent_id, parent_agent_id, subagent_type, description} is what makes the event stream into an actual tree instead of a flat log.

The agent_slug field matters more than agent_id. UUIDs are great for joins but useless for human-readable dashboards. Knowing that something is an Explore vs Plan vs general-purpose agent is what lets you build policy ("only Plan agents may write to spec/", "Explore agents are read-only against the source tree") and budget allocation ("don't burn more than 30% of weekly tokens on general-purpose"). The slug should be stable across a single spawn lifetime even if the agent_id rotates.

Implicit-identification-via-absence works, with one caveat. Letting "no agent_id field" mean "this is the main agent" is a clean design, but it means hook scripts have to do event.get('agent_id', 'main') everywhere — which is fine if it's documented as the contract. Worth being explicit in the docs that absence is meaningful, not just "field happens to be missing in this version."

Related primitive worth bundling: parent_session_id for the cross-process case (CLI parent launches CLI child via SDK, two different sessions). Cross-references #28300, which is asking for cross-machine A2A — same family of problems, same payload shape needed.

We ended up building an external supervisor that wraps Claude Code as a subprocess and intercepts MCP traffic to reconstruct agent identity heuristically (repo: https://github.com/kcarriedo/claudeverse-runner). It works but is exactly the brittle filename/transcript-parsing pattern this issue is asking to retire. Native hook fields make the whole external supervisor layer 5x simpler.

Strong support for shipping all three (SubagentStart + hierarchy fields + AssistantOutput) as one cohesive change rather than piecemeal — the value is in the joins between them.

Showing cached comments. Read the full discussion on GitHub ↗