8.3 KiB
status, claimed-by, claimed-at, parent, blocked-by, tags
| status | claimed-by | claimed-at | parent | blocked-by | tags | |
|---|---|---|---|---|---|---|
| resolved | 019fba91-eabf-76ae-b086-a37ac061d6e8 | 2026-07-31T21:01:06-04:00 | 002-pi-subagents-map |
|
Pi runtime model research
Question
Which Pi runtime model should the new neutral subagent extension use for child agents: subprocess JSON/RPC, Pi session APIs, an in-process runner, or a hybrid, given isolation, startup latency, cancellation, extension state, session persistence, and context cleanliness requirements?
Resolution
Use a hybrid runtime boundary, with subprocess RPC as the initial implementation path and an in-process SDK runner as an explicit later optimization.
Do not use extension command session APIs such as ctx.newSession, ctx.fork, or ctx.switchSession as the child runtime, because those replace the current interactive session rather than running independent child agents in parallel.
The implementation spec should define a ChildRunner boundary with at least two planned implementations: subprocess-rpc and in-process-sdk.
Version one should implement subprocess-rpc first because it gives the strongest isolation, per-child environment control, compatibility with Pi's real CLI behavior, persistent child session support, process-tree cancellation, timeout enforcement, and clean separation of extension module state.
The in-process SDK runner should remain a design-compatible optimization for lower latency once the subprocess contract is stable.
Prefer RPC mode over print JSON mode for the subprocess runner.
RPC mode is Pi's headless JSON protocol over stdin/stdout, accepts exact prompt bytes through a JSON prompt command, streams events on stdout, supports abort, exposes state commands, and is intended for embedding.
The mjakl/pi-subagent runner already uses pi --mode rpc, writes the prompt to stdin as JSON, cancels extension UI requests, streams events, and handles settlement, persistent sessions, timeouts, aborts, process groups, and stderr truncation.
Pi's official subagent example and pi-fork prove print/JSON subprocesses work, but RPC is the better implementation target for a durable extension because it avoids argv/stdin prompt reinterpretation and gives a richer control protocol.
Represent context modes through session inputs, not through separate runtime implementations.
A fully independent child should use --no-session or an in-memory SDK session.
An active-branch forked child should serialize the parent session header plus active branch entries to a temporary JSONL session and launch the child with that session.
A persistent child should use a stable child session id and a child session directory.
This preserves context cleanliness because child transcript noise remains in the child session or temp file, while the parent receives only the extension's chosen result summary and details.
Keep subprocess mode as a permanent fallback even after an in-process runner exists.
In-process AgentSession is officially supported by the Pi SDK and avoids process startup and JSON stdout parsing, but it shares process memory, event loop, extension module state, model runtime, and cancellation surface with the parent extension.
It also cannot safely provide per-child process.env overlays or PI_OFFLINE behavior during parallel child runs unless Pi exposes per-session equivalents.
Therefore an eventual auto runtime may prefer in-process only when the requested child has no per-child environment isolation needs and the user accepts shared-process extension behavior.
Cancellation and lifecycle should be runner-specific behind the same contract.
The subprocess runner should use a detached process group on Unix, SIGTERM followed by SIGKILL, timeout timers, and semantic settlement detection.
The in-process runner should use session.abort(), unsubscribe from events, dispose the child session, and clean up temporary session files in finally.
Both runners should normalize results to one result shape containing prompt/task, context mode, session metadata, messages or summarized final output, usage, model, stop reason, error details, lifecycle events, and whether the result came from subprocess or in-process execution.
Findings
Pi's SDK supports direct in-process agent sessions through createAgentSession, SessionManager, and ModelRuntime.
AgentSession exposes prompt, steer, followUp, subscribe, abort, dispose, message state, model and thinking controls, lifecycle events, tool execution events, and compaction.
createAgentSessionRuntime exists for session replacement flows like new session, switch session, fork, clone, and import.
Citation: Pi docs/sdk.md sections createAgentSession, AgentSession, createAgentSessionRuntime, Events, and Session Management.
Pi's RPC mode is a first-class headless embedding protocol.
It runs as pi --mode rpc, accepts JSONL commands over stdin, streams JSON events over stdout, supports exact prompt messages, steering, follow-up messages, abort, get_state, and session replacement commands.
The RPC docs explicitly say Node or TypeScript applications can use AgentSession directly instead of spawning a subprocess, but also document RPC as the subprocess-based embedding surface.
Citation: Pi docs/rpc.md.
Pi sessions are JSONL files whose first line is a session header and whose entries form a tree through id and parentId.
SessionManager exposes getHeader, getBranch, getEntries, getPath, branch, createBranchedSession, open, create, and in-memory sessions.
Citation: Pi docs/session-format.md and Pi docs/sdk.md session-management section.
pi-fork implements active-branch context isolation by serializing ctx.sessionManager.getHeader() and ctx.sessionManager.getBranch() into a temporary JSONL session, then spawning a child Pi process with that session and an appended task prompt.
Its in-process runtime proposal keeps the same exact JSONL snapshot semantics and recommends runtime: "auto" | "in-process" | "subprocess", while preserving subprocess fallback for strict isolation and per-child environment/offline behavior.
Citation: /tmp/pi-fork-research/src/index.ts, /tmp/pi-fork-research/src/runner.ts, and /tmp/pi-fork-research/IN_PROCESS_RUNTIME_PROPOSAL.md.
mjakl/pi-subagent implements named child agents through subprocess RPC.
It builds child Pi CLI args for independent, parent-context, and persistent session modes, uses --mode rpc, passes the prompt as a JSON command over stdin, rewrites the parent snapshot header cwd when needed, propagates depth and cycle-prevention environment variables, forces PI_OFFLINE=1, handles UI requests by cancelling them, tracks settlement, enforces timeouts, and kills the process group on abort.
Citation: /tmp/pi-ext-research/pi-subagent/index.ts and /tmp/pi-ext-research/pi-subagent/runner.ts.
Pi extension command APIs can create or switch sessions, but they are explicitly session-replacement APIs.
The docs warn that withSession receives a fresh replacement-session context and captured old session-bound objects become stale.
This is useful for replacing the current session, not for running many child subagents while the parent session remains active.
Citation: Pi docs/extensions.md, ExtensionCommandContext, ctx.newSession, ctx.fork, ctx.switchSession, and session replacement footguns.
Implications
The spec should not choose between pi-fork and subagent semantics at the runtime layer. Both forked and independent children can be represented by the same child runner with different session inputs.
The first implementation should optimize for correctness and safety over startup latency. Subprocess RPC already matches Pi's real CLI, supports current extension behavior, and isolates child process state. An in-process runner can later reduce spawn overhead if it preserves the same result contract.
The runtime choice should be configuration or capability-driven, not hardwired into named subagent types.
A future auto runtime can prefer in-process for ordinary fast children and fall back to subprocess for per-child environment, strict isolation, recursive delegation risk, or debugging.
Limitations
This research did not build an in-process runner.
It relies on Pi's SDK documentation and pi-fork's proposal for feasibility, so the implementation spec should leave room for a spike before making in-process the default.