Choosing the Boundary: TUI, SDK, RPC, and Remote Services

Pi exposes the same coding-agent machinery through several boundaries, and the right choice depends on who controls the process, how much UI is needed, and where session state should live. This chapter compares the interactive TUI, print and JSON modes, the in-process SDK, and coding-agent RPC, then follows the newer path toward Chord-backed remote services. The useful judgment is conditional: choose the boundary that matches the required control and isolation, rather than treating one interface as the universal form of Pi.

The durable harness makes this question sharper. A named operation and lane can be hosted by different front ends, but an interface still determines how users send prompts, observe progress, handle cancellation, and respond to failures. The local AgentSession and the durable AgentHarness are related layers, not interchangeable objects. The front end should expose the capabilities that its own boundary can honestly support.

Same core, different front doors

Interactive mode is the most direct fit for a person working in a terminal. Pi's TUI provides an editor, chat history, tool rows, status information, model controls, and extension UI. The TUI package underneath it has a shared component interface, interchangeable main-screen and alternate-screen renderers, differential updates, synchronized terminal output, themes, scrolling, and a virtual terminal for tests. Its job is presentation and input, not agent reasoning. The AgentSession still owns the session, model, tools, and lifecycle behind the screen.

That separation is visible when the display changes. A component can render a custom tool result, a widget, or an editor without changing the stored message or the provider request. A TUI renderer can update only changed lines instead of redrawing the entire transcript, which matters during streaming output. The terminal is therefore a front door to the harness, not a second implementation of the harness.

Print mode removes the interactive front door. A one-shot command can send a prompt, wait for the run to settle, print the final assistant text, and exit. This is appropriate for a shell pipeline or a scripted task in which a person does not need to steer the agent while it works. The same session machinery can still be used, or persistence can be disabled. What disappears is the live terminal interaction, not the underlying prompt, provider, tool, and result sequence.

JSON mode keeps more of that sequence but changes its representation. It emits session events as JSON lines so another program can build its own interface or processing pipeline. Streaming message updates are delta-only rather than repeated cumulative snapshots, which keeps the stream smaller. A client assembles text, thinking, or tool-call arguments using content positions and treats the final message_end message as authoritative. The mode is useful when an application wants structured observation without taking on the full SDK or terminal renderer.

RPC mode crosses a process and language boundary. A client sends one JSON command per LF-delimited line; Pi returns correlated responses and streams agent events asynchronously. Prompts can be queued as steering or follow-up messages, and extension UI requests can ask the client to display a dialog or update a widget. This allows an IDE, desktop application, or another language to drive Pi without embedding its JavaScript objects.

The framing rule is a small but important contract. RPC uses LF as the record delimiter, with optional carriage-return handling for input. A generic line reader that splits on additional Unicode separators can break a valid JSON string. A client therefore needs a protocol-compliant reader, request correlation, and backpressure handling rather than a casual “read lines from stdin” loop.

RPC also makes the difference between response acceptance and run completion visible.