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

122 lines
12 KiB
Markdown

---
status: resolved
claimed-by: "019fc02c-3d48-71aa-b1b4-0ea51138ad4a"
claimed-at: "2026-08-01T21:52:36-04:00"
resolved-at: "2026-08-01T21:54:08-04:00"
parent: "[[084-pi-subagent-result-delivery-product-semantics-grill|Pi subagent result delivery product semantics]]"
blocked-by: []
tags:
- 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`.[^tui-overlays]
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.[^interactive-custom]
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.[^tui-overlay-source]
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.[^tui-component]
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.[^tui-render]
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.[^tui-lifecycle]
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.[^component-exports]
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.[^interactive-replay]
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.[^interactive-private]
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`.[^subagent-runner]
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.[^subagent-types]
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.[^subagent-ui]
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.[^rpc-events]
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.[^rpc-state]
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.[^rpc-message-update]
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.[^subagent-runner]
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.[^sdk-interactive]
`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.[^interactive-init]
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.[^rpc-ui-limit]
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
[^tui-overlays]: `/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/tui.md`, "Overlays" and "Overlay Focus".
[^interactive-custom]: `/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/dist/modes/interactive/interactive-mode.js`, `showExtensionCustom()`.
[^tui-overlay-source]: `/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()`.
[^tui-component]: `/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/tui.md`, "Component Interface".
[^tui-render]: `/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/tui.md`, "Key Rules".
[^tui-lifecycle]: `/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/tui.md`, "Overlay Lifecycle".
[^component-exports]: `/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/dist/index.d.ts`, interactive component and theme exports.
[^interactive-replay]: `/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/dist/modes/interactive/interactive-mode.js`, `addMessageToChat()` and `renderSessionItems()`.
[^interactive-private]: `/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`.
[^subagent-runner]: `/home/alexion/wrk/dotfiles/modules/agents/pi/extensions/subagents/runner.ts`, `SubprocessRpcRunner.start()` and `RpcChildHandle.onLine()`.
[^subagent-types]: `/home/alexion/wrk/dotfiles/modules/agents/pi/extensions/subagents/types.ts`, `SubagentStatus`, `RunnerEvents`, and `ChildRunner`.
[^subagent-ui]: `/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.
[^rpc-events]: `/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/rpc.md`, "Events" and "Event Types".
[^rpc-state]: `/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/rpc.md`, `get_state` and `get_entries`.
[^rpc-message-update]: `/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/rpc.md`, `message_update (Streaming)`.
[^sdk-interactive]: `/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/sdk.md`, "Run Modes" and `InteractiveMode`.
[^interactive-init]: `/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/dist/modes/interactive/interactive-mode.js`, `init()` layout setup.
[^rpc-ui-limit]: `/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/rpc.md`, "Extension UI Protocol" limitations.