Repository navigation
feat: show nested tool executions under their parent - #414
Conversation
Pi 0.99 tools such as codemode start other tools through ctx.executeTool(). Those executions carry parentToolCallId and have no tool_call part, so the Chain of Thought never showed them. Carry the parentage through the event contract and runtime model, render nested executions inside the parent's detail (live and replayed), read Pi's bounded nestedCalls record into the Trajectory inspector, and title MCP tools as server/tool.
There was a problem hiding this comment.
Devin Review found 3 potential issues.
2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
| }: ChatToolStepProps) { | ||
| const { tools } = step; | ||
| const failed = tools.filter((tool) => tool.state === "output-error").length; | ||
| const nestedCount = tools.reduce((sum, tool) => sum + (tool.children?.length ?? 0), 0); |
There was a problem hiding this comment.
🟡 Nested failures hidden in tool summary
When a parent tool succeeds after catching a nested failure, failed ignores the failed child. The collapsed row reports nested calls without showing their failure.
Learn more
A tool can execute another tool and catch its failure before returning successfully. The child then has output-error, while the parent's state is output-available. The summary counts only parents, although nestedItems retains each child's state. The failure remains visible only after expanding the row.
Example: A codemode script calls bash with exit 1, catches the error, and returns done. The collapsed row shows Used codemode · 1 nested call without 1 failed.
Recommended fix: Include failed descendants when computing failed in ChatToolStep, using the same recursive traversal as the nested count; preserve the existing top-level counts.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed in the latest commit: failed descendants now show as a separate N nested failed meta; the top-level failed count is unchanged because the parent itself succeeded.
| }: ChatToolStepProps) { | ||
| const { tools } = step; | ||
| const failed = tools.filter((tool) => tool.state === "output-error").length; | ||
| const nestedCount = tools.reduce((sum, tool) => sum + (tool.children?.length ?? 0), 0); |
There was a problem hiding this comment.
🟡 Deeper nested calls omitted from summary
When a nested tool starts another tool, nestedCount counts only direct children. The settled count excludes deeper calls, and the live label names their ancestor instead.
Learn more
nestedItems builds a recursive children tree whenever a tool executes another tool. This summary counts only one level, and runningChild also stops at one level. The detail displays deeper calls, but the collapsed count and live status do not account for them.
Example: codemode executes tool A, which executes tool B. The row shows 1 nested call instead of two, and while B runs it names A.
Recommended fix: Traverse ChatToolItem.children recursively for the count and the deepest running child, retaining the current top-level tool label.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed: the nested count covers all descendants, and the live label follows the deepest running execution.
| if (!calls.length) { | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
🟡 Empty partial records hide dropped calls
When nestedCalls has complete: false and no retained entries, the parser discards it. The inspector then hides Pi's partial-record warning.
| if (!calls.length) { | |
| return undefined; | |
| } | |
| if (!calls.length && complete !== false) { | |
| return undefined; | |
| } |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed as suggested: { calls: [], complete: false } is kept, so the inspector shows Nested calls (0) with the partial-record note.
Review follow-up: the tool step now counts all descendants, shows failed nested executions separately from top-level failures, names the deepest running execution while live, and keeps empty partial nestedCalls records so the inspector still warns.
Stacked on #412.
Summary
Pi 0.99 tools such as
codemodestart other tools throughctx.executeTool(). Pi emits those Nested Tool Executions astool_execution_*events withparentToolCallId; they have notool_callmessage part, so the Chain of Thought never showed them. In the Pi session log they exist only as a boundednestedCallsrecord (no results) on the parent's toolResult message.toolAgentRuntimeEvent gains optionalparentToolCallId; the normalizer sets it only for nested executions. The journal keeps start/end as before, so replay carries parentage too.cot-view.ts): nested executions attach aschildrenof the parentChatToolItem(recursive, start order, one per-derivation index scoped to the Run) and never become steps of their own. The codemode detail pane shows the script instead of escaped{"code": ...}JSON.N nested calls; the live label readsRunning codemode › probe/echo….nestedCallsfrom the toolResult payload is parsed defensively intoTrajectoryStep.nestedCalls; the inspector Summary tab lists them (status, duration, args or "Arguments omitted (N bytes)", error) and notes when Pi kept a partial record.mcp__<server>__<tool>is shown asserver/tool, like Pi's TUI (display only; data, filters and kind lookups keep the raw name).CONTEXT.mdadds Nested Tool Execution; Design page variants,docs/self-built-ui.md,docs/design/chat.md,docs/design/workspace.mdand the UI intent region table are updated.Test plan
parentToolCallId, top-level events have no key (red → green)cot-view: codemode part with an MCP child and a failing bash child → one step, script args, two ordered children with state/args/output; running child isinput-available(red → green)trajectory-model:nestedCallsparsed, omitted-args kept as bytes, malformed entries dropped,complete: falsepreserved (red → green)chat-tool-step: children list,2 nested calls, liveRunning codemode › probe/echo…,Used probe/echotest:bundled-runtimeagainst the real SDK:mcp__probe__echoend event arrives withparentToolCallIdequal to the codemode call andECHO:bundledin its resultbun run typecheck,bun run lint,bun run buildclean; Design page screenshots checkedbun run test: all green except the pre-existing.cursor/**collection failure, fixed onmainby 1a26a7b