Skip to content

feat: agent hierarchy tracking (human -> agent -> sub-agent)#76

Merged
garagon merged 7 commits intomainfrom
feat/agent-hierarchy
Mar 21, 2026
Merged

feat: agent hierarchy tracking (human -> agent -> sub-agent)#76
garagon merged 7 commits intomainfrom
feat/agent-hierarchy

Conversation

@garagon
Copy link
Copy Markdown
Contributor

@garagon garagon commented Mar 21, 2026

Summary

Track parent-child relationships between agents. When Claude Code spawns sub-agents (Explore, Plan, custom), oktsec records the hierarchy.

Data layer

  • 4 new fields on audit Entry: parent_agent, agent_instance_id, root_agent, agent_depth
  • New agent_hierarchy table, UpsertHierarchy, QueryHierarchy, QueryRootAgent
  • auditSelectCols reads all hierarchy fields from DB
  • Schema migration: additive (zero downtime)

Detection

  • Session state tracking in hooks handler (sync.Map per session)
  • Sub-agent detected by comparing resolved agent name with X-Oktsec-Agent header
  • If they differ, event is from sub-agent with parent = header value

Dashboard

  • Agent Tree panel in session trace (above timeline)
  • Sub-agent tag in timeline (SUB-AGENT vs AGENT)
  • Agent detail: shows "Sub-agent of" with link, and "Sub-agents spawned" as cards
  • AI analysis prompt includes hierarchy tree

TUI

  • Sub-agents show with └ indent in live feed
  • Event detail shows Parent and depth for sub-agents
  • No empty line when no parent

Known issues (follow-up)

  • resolveAgent keyword matching can be too aggressive with common words
  • Agent page lists sub-agents as top-level (needs filtering)

Test plan

  • go test ./... all green
  • Verified: explore, plan, general-purpose detected as sub-agents of claude-code
  • Hierarchy table populated correctly
  • CI passes

garagon added 7 commits March 21, 2026 03:26
Data model:
- 4 new fields on audit Entry: parent_agent, agent_instance_id,
  root_agent, agent_depth
- New agent_hierarchy table tracking parent-child relationships
  per session with tool counts and block counts
- Schema migration: additive columns + new table, zero downtime

Hooks handler:
- Session state tracking via sync.Map (session_id -> sessionState)
- getSessionState() resolves parent, root, and depth for each event
- Root agent (depth 0) detected when no agent_type present
- Sub-agents get parent = root agent, depth = parent + 1
- UpsertHierarchy called on each event to maintain the tree

Dashboard:
- Session trace shows agent tree panel above timeline when hierarchy
  data exists. Each node shows role icon, agent name, tool count,
  block count. Indented by depth.
- AI session analysis prompt includes hierarchy tree for better
  attribution of who spawned whom

Template:
- seq() function for generating indent levels
Live feed:
- Sub-agents (depth > 0) display with "└" indent prefix
- Agent column widened to 20 chars for indent space

Event detail:
- Shows "Parent: <agent>" when event is from a sub-agent
- Shows "(sub-agent, depth N)" next to agent name
- Empty lines suppressed when no parent
- TraceStep now carries ParentAgent and AgentDepth from audit entry
- Session trace timeline shows "sub-agent" tag (not "agent") when
  depth > 0, with muted color to indicate lower responsibility
- Builds on hierarchy data populated by hooks handler
Root cause: Claude Code sets X-Oktsec-Agent header to "claude-code"
for ALL events, but resolves agent names in the JSON body (via
agent_type or config matching). The header is always the root agent.

Fix: compare the resolved agent name (ev.Agent) with the header
value. If they differ, the event is from a sub-agent. Set
parentAgent = header value, depth = 1.

Also: only set Agent from header if the JSON body didn't provide
one (prevents header from overwriting body-resolved agent names).
Query: auditSelectCols now includes parent_agent, agent_instance_id,
root_agent, agent_depth. Scan reads all 4 new columns.

Without this, TraceStep.AgentDepth was always 0 because the Query
method never read the fields from the DB, even though they were
written correctly.

TUI: event detail uses dynamic line array instead of JoinVertical
with empty strings, preventing blank line when no parent agent.
Agent detail page:
- Shows "Sub-agent of: claude-code" with link for sub-agents
- Shows "Sub-agents spawned: explore (10 calls), plan (6 calls)"
  as clickable cards for parent agents
- Data from agent_hierarchy table aggregated by agent name

Store:
- Uses existing DB() method for direct hierarchy queries

TUI:
- Event detail uses dynamic line array to prevent empty line
  when no parent agent exists
@garagon garagon merged commit 574cc15 into main Mar 21, 2026
1 check passed
@garagon garagon deleted the feat/agent-hierarchy branch March 21, 2026 09:29
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.

1 participant