Add subagent tracing — parent-child session tree, tree replay, stats rollup#11
Add subagent tracing — parent-child session tree, tree replay, stats rollup#11Siddhant-K-code merged 4 commits intomainfrom
Conversation
- SessionMeta gains parent_session_id, parent_event_id, depth fields (backward compatible — zero values omitted from JSON) - subagent.py: build_tree, aggregate_stats, format_tree, format_tree_summary - replay --expand-subagents: inline subagent sessions under parent tool_call - replay --tree: compact session hierarchy without full event replay - stats --include-subagents: roll up tool calls, tokens, errors across tree - Depth bounded at MAX_DEPTH=5 to prevent runaway recursion - 16 new tests (188 total passing) Co-authored-by: Ona <no-reply@ona.com>
Co-authored-by: Ona <no-reply@ona.com>
…ion rollup - build_tree: raise KeyError with a clear message instead of letting dict lookup fail silently when root_session_id is not in the store - aggregate_stats: compare node.meta.total_duration_ms (root's own duration) against each child subtree total, not the running accumulated value — the previous code gave correct results only when root duration >= all children Co-authored-by: Ona <no-reply@ona.com>
Review: subagent tracingTwo bugs found and fixed in Bug 1 —
|
| stats.total_tokens += child_stats.total_tokens | ||
| # Keep the root's own duration as the floor; a child subtree longer | ||
| # than the root would indicate clock skew — still take the max. | ||
| stats.total_duration_ms = max( |
There was a problem hiding this comment.
Bug: duration accumulation is wrong for multi-child trees.
The comparison always uses node.meta.total_duration_ms (the root's own value) instead of the running max in stats. With two children of 3000ms and 4000ms and a root of 2000ms, the loop processes child 1: max(2000, 3000) = 3000 → stored in stats. Then child 2: max(2000, 4000) = 4000 → correct by coincidence because the larger child is last. Reverse the order and you get max(2000, 4000) = 4000 then max(2000, 3000) = 3000 — wrong final value.
Fix:
stats.total_duration_ms = max(stats.total_duration_ms, child_stats.total_duration_ms)| events = store.load_events(session_id) | ||
| node = SessionNode(meta=meta, events=events) | ||
|
|
||
| if current_depth < MAX_DEPTH: |
There was a problem hiding this comment.
MAX_DEPTH silently truncates the tree without any indication to the caller. If a session tree is cut off at depth 5, the user sees an incomplete tree with no warning. Consider emitting a sys.stderr.write when the depth cap is hit, or returning a flag on SessionNode indicating truncation.
src/agent_trace/subagent.py
Outdated
| def _indent(depth: int) -> str: | ||
| if depth == 0: | ||
| return "" | ||
| return "│ " * (depth - 1) + "├─ " |
There was a problem hiding this comment.
_indent uses ├─ for every non-root node regardless of position. The last child in a list should use └─ to produce correct tree characters. Not a correctness issue but produces visually malformed output for any tree with more than one child.
Review: PR #11 — Subagent tracingThe data model change is backward-compatible (zero values omitted from JSON, One bug (inline comment on line 111) and two nits (lines 78, 133). The bug is real and will produce wrong duration values for any root with 2+ children where the shorter-duration child is processed last. Test gaps worth addressing before merge:
What's good:
|
…d tests - aggregate_stats: compare against stats.total_duration_ms (running max) instead of node.meta.total_duration_ms (fixed root value) — the old code produced wrong results when a shorter-duration child was processed last - build_tree: emit stderr warning when tree is truncated at MAX_DEPTH - _indent: accept last_child flag; use └─ for last child, ├─ for others - format_tree / format_tree_summary: propagate last_child through recursion - Tests: add cases for child-exceeds-root duration, two-child order independence, MAX_DEPTH truncation, expand=False, last-child connector Co-authored-by: Ona <no-reply@ona.com>
Closes #6
What
Links nested agent sessions into a parent-child tree and adds tree-aware replay and aggregated stats.
Changes
SessionMetagains three optional fields (backward compatible — zero values omitted from JSON):parent_session_id— session ID of the spawning agentparent_event_id—event_idof thetool_callthat spawned this sessiondepth— nesting depth (0 = root)subagent.py(new):build_tree(store, root_session_id)— reconstructs the full session tree by scanning all sessions forparent_session_idlinks. Depth bounded atMAX_DEPTH=5.aggregate_stats(node)— rolls up tool calls, LLM requests, tokens, errors across the tree. Duration usesmax(not sum) since subagents run within parent wall time.format_tree()— inline replay with subagent sessions expanded under their parenttool_callformat_tree_summary()— compact hierarchy viewCLI additions:
agent-strace replay --expand-subagents— inline subagent events under parent tool_callagent-strace replay --tree— compact session hierarchy without full event replayagent-strace stats --include-subagents— aggregated stats across the full treeTests
16 new tests (188 total passing)