Files
ai-artifacts/projects/dotfiles/020-pi-subagents-pi-runtime-research.md
2026-08-01 14:05:09 -04:00

92 lines
8.3 KiB
Markdown

---
status: resolved
claimed-by: "019fba91-eabf-76ae-b086-a37ac061d6e8"
claimed-at: "2026-07-31T21:01:06-04:00"
parent: "[[002-pi-subagents-map]]"
blocked-by: []
tags:
- ticket/research
---
# 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.