Files
ai-artifacts/projects/dotfiles/093-pi-subagent-read-only-attach-feasibility-research.md

12 KiB

status, claimed-by, claimed-at, resolved-at, parent, blocked-by, tags
status claimed-by claimed-at resolved-at parent blocked-by tags
resolved 019fc02c-3d48-71aa-b1b4-0ea51138ad4a 2026-08-01T21:52:36-04:00 2026-08-01T21:54:08-04:00 084-pi-subagent-result-delivery-product-semantics-grill
ticket/research

Pi subagent read-only attach feasibility research

Question

Can Pi support a read-only attached child session view that looks like a normal Pi session without the text prompt, using an overlay or temporary replacement rather than split panes, and what implementation boundary should the result-delivery effort assume?

Answer

Pi can support the product shape, but not as a pure current subagent-extension UI tweak. The implementation boundary should assume a Pi-native child-session viewer component owned by the subagent/result-delivery extension, fed by child RPC or session-entry events, and rendered through ctx.ui.custom() as either a focused overlay or a temporary editor-area replacement. The boundary should not assume Pi can simply attach an existing live InteractiveMode instance, hide its prompt, and embed that normal session view inside the parent TUI.

Findings

Pi's extension TUI surface already supports the two requested placement modes. ctx.ui.custom() can show a custom component in place of the editor when overlay is absent, and it can render an overlay when { overlay: true } is passed, with width, max-height, anchor, row, column, margin, focus, hiding, and lifecycle handles exposed through overlayOptions and onHandle.1 The implementation source confirms the non-overlay path clears editorContainer, adds the custom component there, focuses it, and restores the editor when closed, while the overlay path calls this.ui.showOverlay(component, resolveOptions()) without replacing the editor container.2 The TUI implementation confirms overlays are composited over the visible viewport and clipped by maxHeight, and focused overlays receive input through the normal component handleInput path.3

Pi's public TUI component contract is sufficient for a read-only attached viewer component. A component renders lines for a width, may handle keyboard input, and invalidates caches on theme changes, so an attach view can implement detach plus scroll keys without accepting text input.4 Pi's own docs explicitly tell extension authors to call tui.requestRender() after state changes, which is enough for a live view that updates as child events arrive.5 Pi's overlay lifecycle documentation says overlays are disposed when closed and should be recreated rather than reused, so attach/detach should be treated as opening and closing a viewer instance rather than keeping a permanently mounted component around.6

Pi already exposes the rendering pieces needed to make the viewer look Pi-like, but not a single exported complete transcript widget. The main package exports AssistantMessageComponent, UserMessageComponent, ToolExecutionComponent, CustomMessageComponent, DynamicBorder, getMarkdownTheme, and other interactive components from @earendil-works/pi-coding-agent, which means extension code can reuse Pi's message and tool rendering styles rather than reimplement every visual primitive.7 The interactive mode's own replay path constructs UserMessageComponent, AssistantMessageComponent, ToolExecutionComponent, and related components from session messages and tool events, showing the composition pattern that a child viewer would need to mirror.8 However, InteractiveMode keeps chatContainer, editorContainer, defaultEditor, editor, footerContainer, event subscriptions, and render state as private internals, so there is no public API that exposes a ready-made promptless transcript pane for an extension to mount directly.9

The current subagent runner is not yet feeding enough structured data to an attach view. It spawns child Pi as --mode rpc --no-extensions --extension <subagents/index.ts> --name "subagent <id>", communicates over JSONL stdin/stdout, and currently records coarse lifecycle strings such as agent_started, arbitrary event types, agent_settled, and final get_last_assistant_text.10 The subagent SubagentStatus type has an optional childSession field, but the current runner never populates it from RPC get_state, and RunnerEvents.accepted(childSession?: string) is called without a child session path.11 Therefore the existing extension can show statuses and final results, but it cannot render a live Pi-like child transcript without extending the runner/supervisor data path to retain child events, retrieve child entries or messages, or learn and load the child's session file.12

RPC mode is a viable feed for a child-session viewer. Pi's RPC docs state that events are streamed as JSON lines while the agent operates, including agent_start, agent_settled, turn_start, turn_end, message_start, message_update, message_end, and tool execution lifecycle events.13 RPC also exposes get_state, whose response includes sessionFile and sessionId, and get_entries, which returns append-only session entries with stable ids and a leafId cursor.14 RPC message_update includes both the partial assistant message and the assistant streaming delta, so a viewer can support a live transcript rather than waiting only for terminal results.15 The current subagent runner already parses the child stdout stream line by line, so preserving these event payloads and publishing them to an attach viewer is an extension-boundary change rather than a new operating-system or terminal integration.10

A second full InteractiveMode inside the parent is not the right boundary for this effort. The SDK documents InteractiveMode as a full TUI mode with editor, chat history, built-in commands, session runtime, and an own run() loop.16 InteractiveMode.init() builds a root FlexSpacerBottomLayout containing header, loaded resources, chat, pending messages, status, widgets, editor, and footer, then starts a TUI and sets focus to the editor.17 Embedding that whole mode inside the parent overlay would mean nested terminal/TUI ownership, duplicate editor/footer/runtime management, and prompt input that must then be suppressed. Primary docs and exports show reusable components and SDK sessions, but they do not provide an attach API that mounts another mode's transcript inside the current TUI.

RPC child mode has an important limitation for extension UI fidelity. The RPC documentation says ctx.ui.custom() returns undefined in RPC mode and setFooter(), setHeader(), setEditorComponent(), setWorkingIndicator(), and related direct-TUI methods are no-ops.18 That means an attached view of a child running in RPC mode can faithfully show normal agent messages, thinking, tool calls, and tool outputs from events, but it cannot display arbitrary child-side custom TUI overlays/components as if the child had its own terminal. For the subagent result-delivery effort, this limitation is acceptable if the promised attach view is a normal Pi-like transcript without the text prompt, not a full nested interactive Pi process.

Implementation boundary

Build an attach-view feature inside the subagent/result-delivery extension using Pi's extension UI and reusable rendering components. Extend the child runner/supervisor to retain a per-child event stream, final session metadata, and enough message/tool state to reconstruct a transcript. Prefer an RPC-event-fed live model, optionally backfilled from get_entries or the child session file once get_state.sessionFile is captured. Render the selected child in a focused ctx.ui.custom() overlay for the approved overlay path, with a fallback non-overlay custom component that temporarily replaces the editor area. Implement read-only controls in that component only: detach and scroll up/down. Do not implement split panes. Do not try to embed a nested InteractiveMode, duplicate a child terminal, or depend on child-side direct TUI extension UI being visible. If exact visual parity is required later, the Pi core boundary would be a new exported transcript/viewer abstraction extracted from InteractiveMode, not a change to Wayfinder or to the status/result tool API alone.

Limitations

No primary source documents a supported promptless child-session attach API. The feasibility conclusion is therefore based on Pi's documented extension UI contract, exported components, RPC event protocol, and current source shape rather than an existing first-class feature.

The research did not run an end-to-end prototype that renders a live child transcript in an overlay. The source evidence establishes the API boundary and feasibility, but implementation will still need tests for rendering, scroll behavior, event ordering, and terminal resize behavior.

The current child process is launched with --no-extensions plus only the subagents extension explicitly loaded. That is good for avoiding recursive/project extension effects, but it also means the attached child view should not promise to show project/global child extension UI unless that launch policy changes.

Citations


  1. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/tui.md, "Overlays" and "Overlay Focus". ↩︎

  2. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/dist/modes/interactive/interactive-mode.js, showExtensionCustom(). ↩︎

  3. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/node_modules/@earendil-works/pi-tui/dist/tui.js, showOverlay(), handleInput(), and compositeOverlays(). ↩︎

  4. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/tui.md, "Component Interface". ↩︎

  5. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/tui.md, "Key Rules". ↩︎

  6. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/tui.md, "Overlay Lifecycle". ↩︎

  7. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/dist/index.d.ts, interactive component and theme exports. ↩︎

  8. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/dist/modes/interactive/interactive-mode.js, addMessageToChat() and renderSessionItems(). ↩︎

  9. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/dist/modes/interactive/interactive-mode.d.ts, private fields on InteractiveMode. ↩︎

  10. /home/alexion/wrk/dotfiles/modules/agents/pi/extensions/subagents/runner.ts, SubprocessRpcRunner.start() and RpcChildHandle.onLine(). ↩︎

  11. /home/alexion/wrk/dotfiles/modules/agents/pi/extensions/subagents/types.ts, SubagentStatus, RunnerEvents, and ChildRunner. ↩︎

  12. /home/alexion/wrk/dotfiles/modules/agents/pi/extensions/subagents/ui.ts and /home/alexion/wrk/dotfiles/modules/agents/pi/extensions/subagents/index.ts, current widget and registered tool/command behavior. ↩︎

  13. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/rpc.md, "Events" and "Event Types". ↩︎

  14. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/rpc.md, get_state and get_entries. ↩︎

  15. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/rpc.md, message_update (Streaming). ↩︎

  16. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/sdk.md, "Run Modes" and InteractiveMode. ↩︎

  17. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/dist/modes/interactive/interactive-mode.js, init() layout setup. ↩︎

  18. /nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/rpc.md, "Extension UI Protocol" limitations. ↩︎