print and rpc Modes
pi has two non-interactive modes: print (one-shot, text or JSON output) and rpc (long-lived connection, JSON-RPC over stdin/stdout). print is for scripted invocations like pi -p "fix this bug"; rpc is for embedding pi into other GUI apps (VS Code extensions, web frontends). Both share AgentSessionRuntime, but their event subscription, output format, and extension UI integration are completely different.
Responsibilities
- print mode:
runPrintModetakesmode: "text" | "json". text mode outputs only the final assistant message text; json mode streams allAgentSessionEvents. Seepackages/coding-agent/src/modes/print-mode.ts:32-66. - rpc mode:
runRpcModeusesattachJsonlLineReaderto read JSON commands from stdin, dispatches 29 command types viahandleCommand, and writes events and responses to stdout. Seepackages/coding-agent/src/modes/rpc/rpc-mode.ts:48-70andpackages/coding-agent/src/modes/rpc/rpc-mode.ts:371-625. - Signal handling: both modes register SIGTERM / SIGHUP handlers that
killTrackedDetachedChildrenbeforedisposeRuntime, so bash subprocesses don't leak. Seepackages/coding-agent/src/modes/print-mode.ts:47-63andpackages/coding-agent/src/modes/rpc/rpc-mode.ts:351-365. - Extension UI bridge: rpc mode implements
createExtensionUIContext, forwarding extension dialog/widget requests to the client viaextension_ui_requestevents, with the client responding viaextension_ui_response. Seepackages/coding-agent/src/modes/rpc/rpc-mode.ts:83-130. - Session replacement hooks: both modes register a rebind callback via
runtimeHost.setRebindSession, which re-runsbindExtensionsand resubscribes to events after a session switch. Seepackages/coding-agent/src/modes/print-mode.ts:67-108andpackages/coding-agent/src/modes/rpc/rpc-mode.ts:306-349.
Design Motivation
Why don't print and rpc share code? Their output contracts differ: print's stdout is one-way (either text or JSON lines), while rpc's stdin/stdout is bidirectional (commands in, events and responses out). rpc also has to manage a pending-Promise map for extension UI requests (waiting on client responses), a mechanism print doesn't need at all. Sharing would introduce unnecessary complexity, so each file implements its own rebindSession and handleEvent.
The 29 rpc command types (prompt, steer, follow_up, abort, new_session, get_state, set_model, cycle_model, bash, fork, clone, export_html, etc.) are a complete mapping of AgentSession's public API. A client can invoke any method AgentSession exposes via RPC, so pi can be embedded into a GUI written in any language, as long as it speaks JSON-RPC.
The print mode: "text" path only looks at the last assistant message: on success it writes content.text to stdout; on failure (stopReason === "error" || "aborted") it writes to stderr and returns exitCode 1. Simple and direct, suited to shell usage like pi -p "..." | jq.
Key Files
packages/coding-agent/src/modes/print-mode.ts:14-26—PrintModeOptions:mode,messages,initialMessage,initialImages.packages/coding-agent/src/modes/print-mode.ts:32-70—runPrintModesignature, disposeRuntime, registerSignalHandlers, rebindSession.packages/coding-agent/src/modes/print-mode.ts:110-145— main flow: send initialMessage, loop over messages, output the last assistant message in text mode.packages/coding-agent/src/modes/rpc/rpc-mode.ts:1-13— file header comment describing the rpc protocol (commands, responses, events, extension UI).packages/coding-agent/src/modes/rpc/rpc-mode.ts:48-70—runRpcModesignature,output/success/errorhelpers.packages/coding-agent/src/modes/rpc/rpc-mode.ts:83-130—createDialogPromise: Promise + timeout + abort management for extension UI requests.packages/coding-agent/src/modes/rpc/rpc-mode.ts:306-349—rebindSession: rebinds extensions andsession.subscribeto output events.packages/coding-agent/src/modes/rpc/rpc-mode.ts:371-625—handleCommandswitch, 29 command branches.packages/coding-agent/src/modes/rpc/rpc-types.ts— RPC protocol type definitions (RpcCommand,RpcResponse,RpcExtensionUIRequest, etc.).
print mode text path, only outputting the text content of the last assistant message:
// packages/coding-agent/src/modes/print-mode.ts:128-145
if (mode === "text") {
const state = session.state;
const lastMessage = state.messages[state.messages.length - 1];
if (lastMessage?.role === "assistant") {
const assistantMsg = lastMessage as AssistantMessage;
if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
console.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);
exitCode = 1;
} else {
for (const content of assistantMsg.content) {
if (content.type === "text") {
writeRawStdout(`${content.text}\n`);
}
}
}
}
}rpc mode prompt command, using preflightResult to send a response right after the prompt is accepted:
// packages/coding-agent/src/modes/rpc/rpc-mode.ts:379-401
case "prompt": {
let preflightSucceeded = false;
void session
.prompt(command.message, {
images: command.images,
streamingBehavior: command.streamingBehavior,
source: "rpc",
preflightResult: (didSucceed) => {
if (didSucceed) {
preflightSucceeded = true;
output(success(id, "prompt"));
}
},
})
.catch((e) => {
if (!preflightSucceeded) {
output(error(id, "prompt", e.message));
}
});Data Flow
Input/output comparison of the two modes:
Edges and Failures
- rpc doesn't support @file args:
main.tschecksparsed.mode === "rpc" && parsed.fileArgs.length > 0before dispatch and fails outright. Seepackages/coding-agent/src/main.ts:475-478. - rpc doesn't support theme switching:
setThemereturns{ success: false, error: "Theme switching not supported in RPC mode" }. Seepackages/coding-agent/src/modes/rpc/rpc-mode.ts:291-294. - rpc doesn't support tool expansion:
getToolsExpandedalways returns false, since the TUI concept doesn't exist in rpc. Seepackages/coding-agent/src/modes/rpc/rpc-mode.ts:296-303. - Extension UI timeout / abort:
createDialogPromisesupportsopts.timeoutandopts.signal; on timeout or abort it resolves with a default value instead of rejecting, so extension logic can continue. Seepackages/coding-agent/src/modes/rpc/rpc-mode.ts:90-130. - print mode stdout takeover:
main.tscallstakeOverStdout()in non-interactive modes; allconsole.logcalls are redirected to an internal buffer and finally flushed viaflushRawStdout, to keep TUI escapes from polluting output. Seepackages/coding-agent/src/modes/print-mode.ts:155-157. - Shutdown signal: the
shutdownRequestedflag inrpc-modeis triggered by the extensionshutdownHandler; the main loop detects it and cleans up before exiting. Seepackages/coding-agent/src/modes/rpc/rpc-mode.ts:79-81andpackages/coding-agent/src/modes/rpc/rpc-mode.ts:337-339.
Summary
print and rpc are pi's non-interactive modes. They share AgentSessionRuntime but have different output contracts: print is one-way text or JSON event stream output, while rpc is a bidirectional JSON-RPC protocol that exposes all AgentSession methods to external clients and bridges extension UI requests. For the assembly entry, see CLI Entry and Dispatch; for runtime replacement, see Session switch/fork/import.