Skip to content

[FEATURE] Expose Agent Context in Hook Event Payloads for Multi-Agent Observability #16424

@odoldotol

Description

@odoldotol

Preflight Checklist

Problem Statement

The Core Issue

When hook scripts execute for events like PreToolUse, PostToolUse, Notification, SubagentStop, or PreCompact, there is no way to determine which agent (main or subagent) triggered the event. All events share the same session_id regardless of their origin, making it impossible to build reliable observability, debugging, or orchestration tooling for multi-agent workflows.

Why Existing Workarounds Fail

Approach Why It Fails
Process/Environment differentiation Main agent and subagents run asynchronously in the same process. No PID, PPID, or environment variable distinguishes them.
Parsing transcript_path JSONL Unreliable: (1) requires complex async file parsing, (2) transcript structure is undocumented and subject to change, (3) race conditions with concurrent subagents, (4) significant performance overhead.
Session-to-agent mapping Breaks immediately with parallel subagents—mapping gets overwritten and produces incorrect attribution (detailed in #7881).
Using tool_use_id correlation Only works for PreToolUsePostToolUse pairs; doesn't help identify the originating agent for other events.

Affected Events

The following hook events can be triggered by subagents but provide no agent identification:

Event Can Fire from Subagent Agent Info Available
PreToolUse ✅ Yes ❌ No
PostToolUse ✅ Yes ❌ No
PostToolUseFailure ✅ Yes ❌ No
Notification ✅ Yes ❌ No
SubagentStart N/A (announces subagent) ⚠️ Partial*
SubagentStop N/A (announces subagent) ⚠️ Partial*
PreCompact ✅ Yes ❌ No

*SubagentStart and SubagentStop events exist but currently lack consistent agent identification fields in their payloads.

Proposed Solution

Design Principles

  1. Minimal Surface Area: Add only the essential fields needed for agent identification
  2. Consistency: Use the same field names across all affected events
  3. Backward Compatibility: New fields are additive; existing integrations continue to work
  4. Leverage Existing Data: Fields should mirror what's already available in SubagentStart/SubagentStop internal handling

Specification

Add the following three fields to the stdin JSON payload only for events triggered by subagents:

interface SubagentContext {
  /** Unique identifier for this subagent instance (e.g., "a1b2c3d4") */
  agent_id: string;
  
  /** 
   * Subagent type identifier from agent definition
   * (e.g., "code-reviewer", "frontend-developer", "ui-designer")
   */
  agent_type: string;
  
  /** 
   * Path to this subagent's transcript file
   * (e.g., "/home/user/.claude/projects/.../agent-{agent_id}.jsonl")
   */
  agent_transcript_path: string;
}

For main agent events: These fields are omitted (not present in the payload). Only subagents require explicit identification—the main agent is implicitly identified by the absence of these fields.

Example Payloads

Main Agent Event (Unchanged)

For main agent events, no new fields are added. The absence of agent fields implicitly indicates the main agent:

{
  "session_id": "abc-123",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": { "file_path": "/app/component.tsx", "content": "..." },
  "tool_response": { "success": true },
  "transcript_path": "/home/user/.claude/projects/.../abc-123.jsonl"
}

Subagent Event (New Fields Added)

For subagent events, the three identification fields are included:

{
  "session_id": "abc-123",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": { "file_path": "/app/styles.css", "content": "..." },
  "tool_response": { "success": true },
  "transcript_path": "/home/user/.claude/projects/.../abc-123.jsonl",
  "agent_id": "f7e8d9c0",
  "agent_type": "ui-designer",
  "agent_transcript_path": "/home/user/.claude/projects/.../agent-f7e8d9c0.jsonl"
}

Detection Logic in Hook Scripts

# Simple check: presence of agent_id indicates subagent
is_subagent = "agent_id" in event

This design avoids redundant fields for main agent events—only subagents require explicit identification.

Why NOT Include Parent References

Some related proposals suggest adding parent_agent_id or hierarchical fields. I recommend against this for the initial implementation:

  1. Flat Architecture: Claude Code's subagents operate in parallel within the same process, not in a true parent-child hierarchy. All subagents share the same session.
  2. Unnecessary Complexity: For observability and routing purposes, knowing the agent_id and agent_type is sufficient. Parent relationships can be inferred from session scope if needed.
  3. Scope Creep Risk: Hierarchical fields imply nested subagents, which isn't the current execution model and could create confusion.

The session_id already serves as the "parent" concept—all agents within a session share it.

Priority

High — This limitation blocks fundamental observability requirements for multi-agent workflows, which are increasingly common as users adopt custom subagents for specialized tasks.

Feature Category

Other

Use Cases Enabled

1. Agent-Specific Logging & Metrics

#!/usr/bin/env python3
import json, sys

event = json.load(sys.stdin)
tool_name = event.get("tool_name", "unknown")

# Determine agent type
if "agent_id" in event:
    agent_type = event["agent_type"]
else:
    agent_type = "main"

# Route metrics to agent-specific dashboards
send_metric(f"agent.{agent_type}.tool.{tool_name}.count", 1)
send_metric(f"agent.{agent_type}.tool.{tool_name}.latency", event.get("duration_ms", 0))

2. Agent-Specific Tool Policies

#!/usr/bin/env python3
import json, sys

event = json.load(sys.stdin)

# Only allow Write tool for specific agents
if event.get("tool_name") == "Write":
    # Main agent is always allowed
    if "agent_id" in event:
        allowed_subagents = ["frontend-developer", "backend-developer"]
        if event["agent_type"] not in allowed_subagents:
            print(f"Subagent '{event['agent_type']}' is not authorized to write files", file=sys.stderr)
            sys.exit(2)  # Block the tool call

3. Real-Time Multi-Agent Dashboards

#!/usr/bin/env python3
import json, sys
from datetime import datetime

event = json.load(sys.stdin)
is_subagent = "agent_id" in event

# Emit structured event for real-time visualization
dashboard_event = {
    "timestamp": datetime.now().isoformat(),
    "session_id": event["session_id"],
    "agent_id": event.get("agent_id"),  # None for main agent
    "agent_type": event["agent_type"] if is_subagent else None,
    "event_type": event["hook_event_name"],
    "tool": event.get("tool_name"),
    "is_subagent": is_subagent,
}
websocket_broadcast(dashboard_event)

4. Differential Compaction Handling

#!/usr/bin/env python3
import json, sys

event = json.load(sys.stdin)

if event["hook_event_name"] == "PreCompact":
    # Check if this is a subagent compaction
    if "agent_id" in event:
        # Backup subagent transcript separately
        backup_transcript(
            event["agent_transcript_path"],
            f"subagent-{event['agent_type']}-backup.jsonl"
        )
    else:
        # Main agent compaction - use standard backup
        backup_transcript(event["transcript_path"], "main-backup.jsonl")

Relationship to Existing Issues

Issue Focus How This Proposal Differs
#7881 SubagentStop identification This proposal provides a unified solution across all hook events, not just SubagentStop
#14859 Agent hierarchy + new hooks This proposal is minimal and focused—no new hook types, no hierarchical fields, just essential identification

This proposal can be seen as a foundational step that enables the broader observability features discussed in those issues without requiring architectural changes.

Implementation Considerations

Minimal Code Changes Expected

The agent_id, agent_type, and transcript path are already tracked internally for SubagentStart/SubagentStop processing. This proposal simply requests exposing these existing values in other hook event payloads.

Backward Compatibility

  • New fields are additive only
  • Existing hook scripts that don't use these fields continue to work unchanged
  • No changes to exit code semantics or JSON output schemas

Documentation Updates Needed

  • Update Hooks Reference with new fields in the "Hook Input" section
  • Add examples showing agent-aware hook implementations

Summary

What Details
Request Add agent_id, agent_type, and agent_transcript_path to hook event payloads for subagent-triggered events only
Scope PreToolUse, PostToolUse, PostToolUseFailure, Notification, SubagentStop, PreCompact
Main Agent Events Unchanged (no new fields) — absence of fields indicates main agent
Benefit Enables reliable agent identification for observability, policy enforcement, and debugging
Complexity Low — leverages existing internal agent tracking
Breaking Changes None — purely additive

Thank you for considering this proposal. I'm happy to provide additional technical details, test scenarios, or implementation suggestions if helpful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    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