13 KiB
status, claimed-by, claimed-at, resolved-at, parent, blocked-by, tags
| status | claimed-by | claimed-at | resolved-at | parent | blocked-by | tags | |
|---|---|---|---|---|---|---|---|
| resolved | 019fbffd-327f-77cb-be80-827c31407567 | 2026-08-01T21:01:15-04:00 | 2026-08-01T21:13:30-04:00 | 083-pi-subagent-result-delivery-map |
|
Pi subagent result delivery runtime API research
Question
What can Pi extensions and custom tools currently observe, persist, expose, and return for asynchronous subagent lifecycle and result delivery, and what constraints does that place on non-blocking retrieval, notifications, or event-style coordination?
Answer
Pi already exposes enough extension, custom tool, SDK, RPC, session, and UI primitives to implement non-blocking subagent result retrieval inside the current subagent extension.
The strongest path is extension-owned supervision with custom tools for spawn, status, list, wait, result, and cancel, plus extension UI notifications or widgets for awareness, plus session custom entries for durable milestone state.
Pi does not expose a built-in cross-session background job registry or a parent-session event stream for external child sessions, so the subagent extension must own child process supervision, correlation ids, retention, and any durable recovery semantics.
Ordinary message injection into the parent conversation is possible through pi.sendMessage() and pi.sendUserMessage(), but the documented delivery modes constrain it to steer, follow-up, or next-turn delivery and make passive retrieval a safer default for avoiding HITL workflow disruption.
Findings
Extension and tool observation surface
Pi extensions can subscribe to lifecycle, session, agent, message, turn, tool, input, model, provider, and user bash events through pi.on(...).
The extension docs list agent_settled as the event that fires when Pi will not continue automatically through retry, compaction retry, or queued continuation, which makes it the right parent-side event for clearing or reconciling status after a parent run fully settles.
Custom tools receive ExtensionContext, so they can read session state, current mode, UI availability, cwd, model, thinking level, abort signal, idle state, queued-message state, and context usage, but they do not receive command-only session replacement helpers such as waitForIdle(), newSession(), fork(), or switchSession().
Command handlers receive ExtensionCommandContext, which adds waitForIdle() and session replacement helpers, but the docs state those helpers are command-only because they can deadlock if called from event handlers.
Custom tool execution returns an AgentToolResult whose content is sent to the LLM, whose details are persisted on the tool result and available for rendering or state reconstruction, and whose optional usage contributes to session token and cost totals.
Tool progress can be streamed through the onUpdate callback, and Pi emits tool_execution_start, tool_execution_update, tool_result, and tool_execution_end around tool execution.
These primitives mean subagent tools can return immediately from spawn with accepted ids, can return a still-running shape from result retrieval, and can expose current status without blocking the parent turn.
Extension persistence and exposure surface
Pi documents pi.appendEntry(customType, data?) as durable extension state that does not participate in LLM context and can be reconstructed on session_start by reading ctx.sessionManager.getEntries().
The session format documents CustomEntry as an extension state entry with customType and data that does not participate in LLM context, while interactive mode can render it with an entry renderer.
Pi also documents pi.sendMessage() as custom message injection that participates in LLM context, and recommends pi.appendEntry() with pi.registerEntryRenderer() for durable TUI-only content that should not be sent to the LLM.
The current subagent extension already uses pi.appendEntry("subagent_milestone", { event, status }) on lifecycle milestones, which is the right built-in persistence primitive for terminal awareness and future recovery.
However, the current supervisor keeps live child records in process memory only, and session_shutdown cancels all non-terminal children before discarding the supervisor.
The current list() returns active children and recent terminal children subject to a TTL, and result(id) requires the id to still be present in memory.
Therefore, durable post-restart result delivery would require intentionally reconstructing status and terminal result records from session custom entries or from a separate extension-owned store.
Pi provides the primitives for this, but the current extension implementation does not yet persist the final result body through appendEntry().
UI and notification surface
In TUI mode, extensions can call ctx.ui.notify(), setStatus(), setWidget(), setTitle(), custom components, custom footer, custom header, custom working indicators, and custom renderers.
The TUI docs show setWidget() as a persistent above-or-below-editor surface and notify() as a non-blocking user notification.
The RPC docs state that extension UI fire-and-forget methods such as notify, setStatus, setWidget, setTitle, and set_editor_text are emitted as extension_ui_request events and do not expect a response.
The RPC docs also state that some TUI-specific methods degrade in RPC mode, with custom() returning undefined, several working and footer methods becoming no-ops, and setWidget() supporting only string arrays.
Because ctx.hasUI is true in TUI and RPC modes, while ctx.mode === "tui" is required for terminal-only UI, a subagent result delivery design should use notify, setStatus, and string-array widgets for mode-portable awareness, and reserve custom TUI components for a TUI-only enhancement.
The current subagent extension already sets a widget with a compact or expanded status summary when status changes.
Async child lifecycle evidence
Pi SDK AgentSession exposes prompt(), steer(), followUp(), subscribe(), abort(), waitForIdle(), and event streaming.
The SDK docs state that prompt() resolves after the full accepted run finishes, including retries, while steer() and followUp() enqueue messages during streaming.
The RPC docs state that the RPC prompt command response is emitted after acceptance, queuing, or immediate handling, and that events continue streaming asynchronously after acceptance.
The RPC docs expose agent_settled, message_update, tool_execution_*, queue_update, and get_last_assistant_text over JSONL, which is enough for the current subprocess runner to observe child progress and final text.
The current subagent runner starts pi --mode rpc --no-extensions --extension <subagents index> --name "subagent <id>", sends a prompt, observes JSONL events, treats agent_settled as completion, calls get_last_assistant_text, and then terminates the child process.
This aligns with the documented RPC protocol, where agent_settled means no automatic retry, compaction retry, or queued continuation remains, and get_last_assistant_text returns the text content of the last assistant message.
Coordination and event-style options
Pi exposes a shared extension event bus as pi.events for communication between extensions.
The SDK docs also say a shared eventBus can be passed to DefaultResourceLoader to emit or listen from outside the session factory.
This can support in-process extension-to-extension notifications, but it is not documented as a persisted queue, a cross-process bus, or a child-session supervision registry.
For parent-facing async coordination, the reliable primitives are therefore extension-owned state plus tool polling or waiting, visible UI notifications, and optional in-process events for cooperating extensions in the same runtime.
A controlled push into the parent conversation is possible with pi.sendMessage() or pi.sendUserMessage().
pi.sendMessage() can use deliverAs: "steer", "followUp", or "nextTurn", with triggerTurn: true only applying to steer and follow-up while idle.
pi.sendUserMessage() always triggers a turn and requires deliverAs while streaming.
These semantics are useful for an explicit opt-in completion injection, but they are too disruptive as a default delivery path when a parent session is in a HITL grill or another active workflow.
Current constraints for design
- Non-blocking spawn and retrieval are feasible today because custom tools can return immediately and later expose status or result from extension-owned supervision state.
- Notifications are feasible today through
ctx.ui.notify()and status widgets, and these work in TUI and RPC in degraded but documented forms. - Durable terminal awareness is feasible through
pi.appendEntry()custom entries, but durable final result retrieval is not present unless the extension persists terminal result bodies or an external store. - Event-style coordination is feasible only within the current process through
pi.events, or through RPC event streams owned by a client, not through a built-in persistent background job bus. - Parent conversation injection is feasible but should be explicit and controlled because
sendMessage()andsendUserMessage()participate in LLM context or trigger turns. - Command-only session replacement and
waitForIdle()are not available to custom tools, so subagent tools should not depend on command-only APIs for result delivery. - TUI custom components are not portable to RPC mode, so the minimum design should not require them.
Limitations and evidence gaps
No primary Pi documentation found a built-in async job registry, persistent notification center, or background task inbox for extensions.
No primary Pi documentation found an extension API that observes lifecycle events from a different Pi process or a child RPC session unless the extension or SDK client starts and subscribes to that child itself.
The current subagent extension source persists lifecycle milestones but not terminal result bodies, so post-restart retrieval of completed results is an implementation gap rather than a Pi platform impossibility.
The source inspected was the installed Pi package at version 0.82.1 and the deployed dotfiles subagent extension, so upstream behavior can change after that package version.
Citations
- Pi extension capabilities, event lifecycle, custom tools,
ExtensionContext, command-onlyExtensionCommandContext,pi.sendMessage(),pi.sendUserMessage(),pi.appendEntry(), UI methods, andpi.events:/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/extensions.md. - Pi SDK
AgentSession, runtime replacement, event subscription, prompt queueing, custom tools, extensions, session management, and shared event bus:/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/sdk.md. - Pi RPC prompt acceptance, asynchronous event streaming,
agent_settled,get_last_assistant_text,get_entries, and extension UI protocol degradation:/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/rpc.md. - Pi session custom entries, custom messages, message entries, and
SessionManagerappend and read APIs:/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/session-format.md. - Pi TUI extension component and widget capabilities:
/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/tui.md. - Pi exported type definitions for
ExtensionUIContext,ExtensionContext,ExtensionCommandContext,ToolDefinition, andExtensionAPI:/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/dist/core/extensions/types.d.ts. - Pi runtime binding for
sendMessage,sendUserMessage, andappendEntry, including the custom entry append andentry_appendedemission:/nix/store/p2xh13r9f890af4b8jdp60rnld1kbwsl-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/dist/core/agent-session.js. - Current subagent extension tool surface and milestone persistence:
/home/alexion/wrk/dotfiles/modules/agents/pi/extensions/subagents/index.ts. - Current subagent supervisor lifecycle, in-memory child records, result access, wait behavior, TTL retention, milestone callbacks, and shutdown cancellation:
/home/alexion/wrk/dotfiles/modules/agents/pi/extensions/subagents/supervisor.ts. - Current subagent RPC child runner behavior using
agent_settledandget_last_assistant_text:/home/alexion/wrk/dotfiles/modules/agents/pi/extensions/subagents/runner.ts. - Current subagent data shapes for status, result, wait, child records, and runner callbacks:
/home/alexion/wrk/dotfiles/modules/agents/pi/extensions/subagents/types.ts. - Current subagent widget rendering:
/home/alexion/wrk/dotfiles/modules/agents/pi/extensions/subagents/ui.ts.