Pi Runner
Below is a concrete implementation spec for the Pi (pi-coding-agent CLI) runner shipped in Untether (v0.5.0).
Below is a concrete implementation spec for the Pi (pi-coding-agent CLI) runner shipped in Untether (v0.5.0).
Scope
Goal
Provide the pi engine backend so Untether can:
- Run Pi non-interactively via the pi CLI (
pi --print). - Stream progress by parsing
--mode json(newline-delimited JSON). Each line is a JSON object. - Support resumable sessions via
--session <token>(Untether emits a canonical resume line the user can reply with).
Non-goals (v1)
- Interactive TUI flows (session picker, prompts, etc.)
- RPC mode (requires a long-running process and JSON commands)
UX and behavior
Engine selection
- Default:
untether(auto-router usesdefault_enginefrom config) - Override:
untether pi
Resume UX (canonical line)
Untether appends a single backticked resume line at the end of the message, like:
`pi --session ccd569e0`
Notes:
pi --resume/-ropens an interactive session picker, so Untether uses--session <token>instead.- The resume token is the session id (short prefix), derived from the session
header line (
{"type":"session", ...}) emitted to stdout in--mode json. This requires pi-coding-agent >= 0.45.1. - If the path contains spaces, the runner will quote it.
Non-interactive runs
Use --print and --mode json for headless JSONL output.
Pi does not accept -- <prompt> to protect prompts starting with -. Untether prefixes a leading space if the prompt begins with - so it is not parsed as a flag.
Config additions
Untether config lives at ~/.untether/untether.toml.
Add a new optional [pi] section.
Recommended schema:
=== “untether config”
```sh
untether config set default_engine "pi"
untether config set pi.model "..."
untether config set pi.provider "..."
untether config set pi.extra_args "[]"
```
=== “toml”
```toml
# ~/.untether/untether.toml
default_engine = "pi"
[pi]
model = "..." # optional; passed as --model
provider = "..." # optional; passed as --provider
extra_args = [] # optional list of strings, appended verbatim
```
Notes:
extra_argslets you pass new Pi flags without changing Untether.- Session files are stored under Pi’s default session dir:
~/.pi/agent/sessions/--<cwd>--(with path separators replaced by-).
Code changes (by file)
1) New file: src/untether/runners/pi.py
Expose a module-level BACKEND = EngineBackend(...).
Runner invocation
The runner should launch Pi in headless JSON mode:
pi --print --mode json --session <session.jsonl> <prompt>
When resuming, <session.jsonl> is replaced by the resume token extracted from the chat.
Event translation
Pi JSONL output is AgentSessionEvent (from @mariozechner/pi-agent-core).
The runner should translate:
tool_execution_start->action(phase: started)tool_execution_end->action(phase: completed)agent_end->completed
For the final answer, use the most recent assistant message text (from
message_end events). For errors, if the assistant stopReason is error or
aborted, emit completed(ok=false, error=...).
Installation and auth
Install the CLI globally:
npm install -g @mariozechner/pi-coding-agent
Minimum supported pi version: 0.45.1.
Auth is stored under ~/.pi/agent/auth.json. Run pi once interactively to
set up credentials before using Untether.
Known pitfalls
--resumeis interactive; Untether uses--session <path>instead.- Prompts that start with
-are interpreted as flags by the CLI. Untether prefixes a space to make them safe.
If you want, I can also add a sample untether.toml snippet to the README or
include a small quickstart section for Pi in the onboarding panel.