Skip to content

Session Hierarchies Guide

What Are Session Hierarchies?

Session hierarchies capture the parent-child relationships created when you delegate work to subagents. Every time an orchestrator spawns a Task(), HtmlGraph automatically creates a session hierarchy that shows:

  • Who did the work - Orchestrator agent, subagent types
  • When work happened - Timeline of session creation and completion
  • What was delegated - Prompts, task scope, constraints
  • How results link back - Parent-child session relationships

This creates a complete development trace that survives agent crashes, context switches, and multi-day workflows.


Why Session Hierarchies Matter

Problem: Context Loss in Complex Workflows

Without session tracking:

Day 1 (Claude):
- Explore codebase
- Find 20 issues
- Document findings
- (Context lost when session ends)

Day 2 (Different agent):
- "What did Claude find yesterday?"
- (No record - have to re-explore)

Solution: Session Hierarchies

With HtmlGraph tracking:

Orchestrator Session (Claude)
├── Child Session 1 (Subagent-Gemini): Explore codebase
│   └── Found 20 issues, documented in session summary
├── Child Session 2 (Subagent-Claude): Analyze issues
│   └── Prioritized issues by severity
└── Child Session 3 (Subagent-Test): Validate fixes
    └── All tests passed

Day 2: Pick up where you left off
├── Query: sdk.get_feature_sessions("feature-001")
├── View: All 4 sessions with full context
└── Continue: From the last session's findings

Benefits

  • Complete lineage - Full development history
  • Context recovery - No information loss
  • Debugging aid - Trace exactly what happened
  • Team visibility - See other agents' work
  • Cost tracking - Understand work distribution
  • Analytics - Measure productivity per agent/session

How Parent-Child Sessions Work

Session Creation Flow

1. Orchestrator starts session
   └── session_id = "sess-abc123"
       agent = "orchestrator"
       status = "in-progress"

2. Orchestrator calls Task()
   └── HtmlGraph creates child session
       parent_session_id = "sess-abc123"
       subagent_type = "general-purpose"
       delegated_prompt = "..."
       status = "pending" → "in-progress"

3. Subagent executes work
   └── Child session captures all tool calls
       tools_used = ["Bash", "Read", "Edit", "Grep"]
       events = [...]  # All activities logged
       status = "in-progress"

4. Subagent completes
   └── Child session summary created
       result = "..."
       status = "completed"
       total_time = "2m 34s"

5. Results available to orchestrator
   └── Parent can query child:
       child_session = sdk.sessions.get("sess-child-xyz")
       print(child_session.result)

Event Capture

HtmlGraph captures everything that happens in a session:

{
  "session_id": "sess-child-xyz",
  "parent_session_id": "sess-abc123",
  "agent": "subagent-gemini",
  "status": "completed",
  "events": [
    {
      "type": "ToolUse",
      "tool": "Bash",
      "command": "uv run pytest tests/unit/",
      "result": "45 passed, 2 failed"
    },
    {
      "type": "ToolUse",
      "tool": "Grep",
      "pattern": "TODO:",
      "result": "Found 12 TODOs"
    }
  ],
  "summary": "Ran tests and found issues",
  "total_time_seconds": 154
}

Viewing Session Hierarchies

Using the Python SDK

Get Parent Session Info

from htmlgraph import SDK

sdk = SDK(agent="orchestrator")

# Get current session
current_session = sdk.sessions.get(session_id)

# View children
print(f"Child sessions: {current_session.child_session_ids}")
# → ["sess-child-1", "sess-child-2", "sess-child-3"]

# Iterate over children
for child_id in current_session.child_session_ids:
    child = sdk.sessions.get(child_id)
    print(f"{child.agent}: {child.status} - {child.summary}")

Get Feature's All Sessions

# Find all sessions that worked on a feature
sessions = sdk.get_feature_sessions("feature-001")

for session in sessions:
    print(f"Agent: {session.agent}")
    print(f"Status: {session.status}")
    print(f"Parent: {session.parent_session_id}")
    print(f"Duration: {session.total_time_seconds}s")
    print(f"Tools used: {session.tools_used}")
    print("---")
# Start from a child session
child_session = sdk.sessions.get("sess-child-xyz")

# Get parent
parent = sdk.sessions.get(child_session.parent_session_id)
print(f"Parent agent: {parent.agent}")

# Get siblings
siblings = [
    sdk.sessions.get(sibling_id)
    for sibling_id in parent.child_session_ids
    if sibling_id != child_session.id
]
print(f"Siblings: {[s.agent for s in siblings]}")

Using the Dashboard

The HtmlGraph dashboard has an Orchestration tab that visualizes hierarchies:

┌─────────────────────────────────────────┐
│ Orchestrator Session (Claude)           │
│ Duration: 5m 23s | Status: completed   │
├─────────────────────────────────────────┤
│                                         │
│  ├─ Child: Test Runner (Haiku)         │
│  │  ├─ Tests: 145 passed, 0 failed     │
│  │  └─ Time: 1m 45s                    │
│  │                                     │
│  ├─ Child: Code Analyzer (Gemini)      │
│  │  ├─ Issues found: 3                 │
│  │  └─ Time: 2m 10s                    │
│  │                                     │
│  └─ Child: Documenter (Haiku)          │
│     ├─ Files updated: 5                │
│     └─ Time: 1m 28s                    │
│                                         │
└─────────────────────────────────────────┘

Click on any session to see: - Full event log - Tool calls executed - Results and output - Timing breakdown - Agent details

Using the CLI

# List all sessions
uv run htmlgraph session list

# Get session details
uv run htmlgraph session show sess-abc123

# View session hierarchy
uv run htmlgraph session tree sess-abc123  # Show parent-child tree

# Find sessions for a feature
uv run htmlgraph session find-feature feature-001

Understanding Session Events

Event Types

Each session captures different event types:

ToolUse Event

{
    "type": "ToolUse",
    "timestamp": "2025-01-10T15:30:45Z",
    "tool": "Bash",
    "input": "uv run pytest tests/",
    "result": "45 passed, 2 failed",
    "duration_ms": 3245
}

FileEdit Event

{
    "type": "FileEdit",
    "timestamp": "2025-01-10T15:31:20Z",
    "file": "src/auth/login.py",
    "old_content": "...",
    "new_content": "...",
    "reason": "Fix token validation"
}

SessionStart Event

{
    "type": "SessionStart",
    "timestamp": "2025-01-10T15:30:00Z",
    "agent": "subagent-gemini",
    "parent_session_id": "sess-abc123",
    "delegated_prompt": "Run tests and report failures"
}

SessionEnd Event

{
    "type": "SessionEnd",
    "timestamp": "2025-01-10T15:34:30Z",
    "status": "completed",
    "result": "All tests passed",
    "total_events": 47,
    "tools_used": ["Bash", "Read", "Grep"]
}

Analyzing Events

# Get all events from a session
session = sdk.sessions.get("sess-child-xyz")

# Count tool usage
tool_counts = {}
for event in session.events:
    if event['type'] == 'ToolUse':
        tool = event['tool']
        tool_counts[tool] = tool_counts.get(tool, 0) + 1

print(f"Tools used: {tool_counts}")
# → {"Bash": 3, "Read": 5, "Grep": 4}

# Find long-running tools
slow_tools = [
    e for e in session.events
    if e['type'] == 'ToolUse' and e['duration_ms'] > 1000
]
print(f"Slow operations: {len(slow_tools)}")

# Extract all file edits
edits = [
    e for e in session.events
    if e['type'] == 'FileEdit'
]
print(f"Files modified: {len(edits)}")
for edit in edits:
    print(f"  - {edit['file']}")

Debugging with Session Traces

Scenario 1: "What did the subagent do?"

# Find the delegated task result
parent_session = sdk.sessions.get("sess-abc123")

# Get first child
child_id = parent_session.child_session_ids[0]
child = sdk.sessions.get(child_id)

# See what it executed
print(f"Prompt: {child.delegated_prompt}")
print(f"Status: {child.status}")
print(f"Result: {child.result}")
print(f"Tool calls: {len([e for e in child.events if e['type'] == 'ToolUse'])}")

# If failed, see the error
if child.status == "failed":
    print(f"Error: {child.error_message}")
    print(f"Last event: {child.events[-1]}")

Scenario 2: "Did the orchestrator make the right decision?"

# Trace the orchestrator's decisions
orchestrator_session = sdk.sessions.get("sess-orchestrator-id")

# What was delegated?
for i, child_id in enumerate(orchestrator_session.child_session_ids):
    child = sdk.sessions.get(child_id)
    print(f"Task {i+1}: {child.delegated_prompt[:100]}...")
    print(f"Result: {child.status}")

# Was it cost-effective?
total_cost = sum(
    sdk.sessions.get(child_id).estimated_cost
    for child_id in orchestrator_session.child_session_ids
)
print(f"Total estimated cost: ${total_cost:.2f}")

# Did it complete on time?
print(f"Total time: {orchestrator_session.total_time_seconds}s")

Scenario 3: "Why did feature X take so long?"

# Get all sessions for a feature
sessions = sdk.get_feature_sessions("feature-001")

# Calculate total time
total_time = sum(s.total_time_seconds for s in sessions)
print(f"Total development time: {total_time}s")

# Break down by agent
time_by_agent = {}
for session in sessions:
    agent = session.agent
    time_by_agent[agent] = time_by_agent.get(agent, 0) + session.total_time_seconds

for agent, time in sorted(time_by_agent.items(), key=lambda x: -x[1]):
    print(f"{agent}: {time}s ({time/total_time*100:.0f}%)")

# Find bottlenecks
for session in sessions:
    slowest_tool = max(
        [e for e in session.events if e['type'] == 'ToolUse'],
        key=lambda e: e['duration_ms'],
        default=None
    )
    if slowest_tool and slowest_tool['duration_ms'] > 5000:
        print(f"Slow operation in {session.agent}: {slowest_tool['tool']} took {slowest_tool['duration_ms']}ms")

Scenario 4: "Trace a specific issue across sessions"

# Find all sessions that touched a file
target_file = "src/auth/login.py"
sessions = sdk.get_feature_sessions("feature-001")

file_edits_timeline = []
for session in sessions:
    for event in session.events:
        if event['type'] == 'FileEdit' and event['file'] == target_file:
            file_edits_timeline.append({
                'session': session.id,
                'agent': session.agent,
                'timestamp': event['timestamp'],
                'reason': event.get('reason', 'no reason given')
            })

# Sort by time
file_edits_timeline.sort(key=lambda x: x['timestamp'])

# Show evolution
print(f"Evolution of {target_file}:")
for edit in file_edits_timeline:
    print(f"  {edit['agent']} ({edit['session'][:8]}...): {edit['reason']}")

Session Hierarchy Patterns

Pattern 1: Orchestrator + Parallel Subagents

Orchestrator (Claude)
├── Subagent 1 (Gemini): Explore codebase
├── Subagent 2 (Copilot): Check GitHub issues
└── Subagent 3 (Claude): Analyze security

All run in parallel
Results: Available immediately after all complete

Query pattern:

orchestrator = sdk.sessions.get("sess-orchestrator")
child_sessions = [
    sdk.sessions.get(child_id)
    for child_id in orchestrator.child_session_ids
]
# All children have completed
results = [child.result for child in child_sessions]

Pattern 2: Sequential Handoff

Task 1 (Exploration)
└── Finds issues

Task 2 (Analysis - uses Task 1 results)
└── Prioritizes issues

Task 3 (Implementation - uses Task 2 results)
└── Fixes issues

Query pattern:

# Task 1 completes first
task1_result = task1.result

# Task 2 uses Task 1 results
task2 = Task(prompt=f"Based on these issues: {task1_result}, prioritize them...")

# Task 3 uses Task 2 results
task3 = Task(prompt=f"Based on priority: {task2.result}, implement fixes...")

Pattern 3: Hierarchical Delegation (Nested)

Orchestrator (Level 0)
└── Subagent 1 (Level 1): Complex task
    ├── Grandchild 1 (Level 2): Sub-task A
    ├── Grandchild 2 (Level 2): Sub-task B
    └── Grandchild 3 (Level 2): Sub-task C

Query pattern:

# Get top-level orchestrator
orchestrator = sdk.sessions.get("sess-level0")

# Get level 1 subagent
subagent_l1 = sdk.sessions.get(orchestrator.child_session_ids[0])

# Get level 2 grandchildren
grandchildren = [
    sdk.sessions.get(child_id)
    for child_id in subagent_l1.child_session_ids
]

# Full lineage: orchestrator → subagent → grandchildren


Best Practices

1. Use Meaningful Feature Linking

Link sessions to features so you can trace all work:

from htmlgraph import Task

# Good: Feature context included in prompt
Task(
    subagent_type="general-purpose",
    prompt=f"""
    Feature: feature-001 - User Authentication

    Task: Implement login endpoint
    ...
    """
)

# Later: Query all work on feature
sessions = sdk.get_feature_sessions("feature-001")

2. Document Key Decisions

When delegating critical work, document the decision:

sdk.spikes.create(
    title="Delegated auth refactoring to parallel subagents"
).set_findings("""
Decision: Why we delegated?
- 3 independent tasks (explore, implement, test)
- Can run in parallel (faster than sequential)
- Subagents: Gemini (exploration), Claude (implementation), Test-Runner (validation)

Expected cost: $0.12 vs $0.25 for sequential
Expected time: 2 min vs 6 min for sequential
""").save()

3. Monitor Session Health

Periodically review session patterns:

# Session duration tracking
feature_sessions = sdk.get_feature_sessions("feature-001")

print("Session Performance:")
for session in feature_sessions:
    print(f"{session.agent}: {session.total_time_seconds}s ({session.status})")

# Find anomalies
slow_sessions = [
    s for s in feature_sessions
    if s.total_time_seconds > 300  # > 5 minutes
]

if slow_sessions:
    print("Slow sessions - consider optimization:")
    for s in slow_sessions:
        print(f"  {s.id}: {s.total_time_seconds}s")

4. Use Hierarchy for Cost Attribution

Track which agent spent resources:

feature_sessions = sdk.get_feature_sessions("feature-001")

# Cost by agent
cost_by_agent = {}
for session in feature_sessions:
    agent = session.agent
    cost = session.estimated_cost
    cost_by_agent[agent] = cost_by_agent.get(agent, 0) + cost

# Total cost
total = sum(cost_by_agent.values())
print(f"Total feature cost: ${total:.2f}")
for agent, cost in cost_by_agent.items():
    pct = cost / total * 100
    print(f"  {agent}: ${cost:.2f} ({pct:.0f}%)")

5. Preserve Context in Handoffs

When handing off between agents, reference the session:

# Finishing agent documents findings
current_session = sdk.sessions.get(session_id)

sdk.spikes.create(
    title=f"Handoff context from {current_session.agent}"
).set_findings(f"""
Session ID: {session_id}
Agent: {current_session.agent}
Status: {current_session.status}

Key findings:
{current_session.summary}

For next agent:
- See event log in session for details
- All tool outputs captured in session.events
- Feature context: feature-001
""").save()

FAQ

Q: Are parent-child sessions created automatically?

A: Yes! HtmlGraph automatically creates child sessions when you call Task(). You don't need to manually create them.

Q: Can I query sessions from completed features?

A: Yes. Sessions are stored permanently in .htmlgraph/sessions/. You can query them anytime, even if the feature is complete.

Q: What's the deepest hierarchy I should create?

A: Usually 2-3 levels (orchestrator → subagent → grandchild). Beyond that, complexity outweighs benefits.

Q: Do I need to know session IDs?

A: No. Use sdk.get_feature_sessions(feature_id) to get all sessions for a feature without knowing IDs.

Q: Can different agents see the same session hierarchy?

A: Yes. Sessions are shared in .htmlgraph/sessions/. Any agent with access to the directory can see all sessions.

Q: How long are session records kept?

A: Indefinitely. Sessions are stored in the .htmlgraph/ directory which should be committed to git. They're part of your project history.

Q: Can I manually create a session hierarchy?

A: Usually not needed. Use Task() for automatic creation. For manual needs, see the SDK reference.