# Pi (pi.dev) sessions Codeman can drive [Pi](https://pi.dev) (`@earendil-works/pi-coding-agent`, MIT) as a session backend, alongside Claude Code, OpenCode, Codex, Gemini and Antigravity. `pi` is a sixth **run mode**: its own PTY, its own tmux session, its own tab colour (rose). It is not a location overlay like Docker or remote-SSH cases, and it is not a web tab. Tracking issue: [#206](https://github.com/Ark0N/Codeman/issues/206). The design rationale behind each decision below lives in `docs/pi-integration-plan.md`. ## Install ```bash npm install -g --ignore-scripts @earendil-works/pi-coding-agent # or curl -fsSL https://pi.dev/install.sh | sh ``` Both installers end up going through global npm, so either one uninstalls with `npm uninstall -g @earendil-works/pi-coding-agent`. Codeman finds the binary via `which pi` and then the usual global-bin locations (`~/.local/bin`, `/usr/local/bin`, `~/.bun/bin`, `~/.npm-global/bin`, `~/bin`). **`pi` is a short, generic name**, so unlike the other CLI resolvers Codeman does not trust a `which` hit on its own: it runs `pi --version` once and requires semver-shaped output. Anything else is rejected as "not installed" and the rejected path is logged. Check what it resolved: ```bash curl -s localhost:3000/api/pi/status | jq # { "available": true, "path": "/home/you/.local/bin", "version": "0.84.1" } ``` That endpoint carries `version` on top of the shape the sibling `/api/*/status` endpoints return, precisely so a misresolution is visible rather than presenting as "the mode just doesn't work". ## Authenticate Pi supports 15+ providers. Two ways in: - **OAuth subscription login** — run `/login` inside a pi session. Six providers support it: ChatGPT Plus/Pro, Claude Pro/Max, GitHub Copilot, xAI, OpenRouter and Radius. Credentials land in `~/.pi/agent/auth.json` and pi refreshes them itself. OpenRouter's flow accepts a pasted redirect URL, which is what makes it workable over remote SSH. - **API keys** — exported in the environment of the **Codeman server process**. ⚠️ **Provider API keys cannot be sent as per-session `envOverrides`.** Pi reads about 34 provider variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `DEEPSEEK_API_KEY`, `HF_TOKEN`, `BASETEN_API_KEY`, …) that share no common prefix. Codeman's env allowlist is a single global list applied to every mode at once, so admitting bare provider keys for pi would widen the allowlist for Claude, Codex, Gemini and everything else too. Only the **`PI_*`** prefix was added, which covers every documented pi input: `PI_CODING_AGENT_DIR`, `PI_CODING_AGENT_SESSION_DIR`, `PI_PACKAGE_DIR`, `PI_OFFLINE`, `PI_SKIP_VERSION_CHECK`, `PI_TELEMETRY`, `PI_CACHE_RETENTION`, `PI_SHARE_VIEWER_URL`, `PI_HARDWARE_CURSOR`, `PI_EXPERIMENTAL`. `pi auth check` verifies credentials before you start a long run. Note if you authenticate with a Claude Pro/Max subscription: third-party harness usage bills as Anthropic "extra usage" per token rather than against plan limits. ## What Codeman wires up `PiConfig` (per session, persisted in `state.json`, round-trips through respawn): | Field | Flag | Notes | | --------------------- | -------------------------------------- | ---------------------------------------------------------------- | | `model` | `--model ` | Accepts `provider/id` and a `:` suffix (`sonnet:high`) | | `provider` | `--provider ` | `anthropic`, `openai`, `google`, … | | `thinking` | `--thinking ` | `off`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max` | | `continueSession` | `-c` | Skipped when `resumeSessionId` is set (the two conflict) | | `resumeSessionId` | `--session ` | Ids only, never paths | | `approveProjectTrust` | `--approve` / `--no-approve` / nothing | Tri-state, see below | Every value is regex-validated and **dropped** (not escaped) if it fails, because the result is interpolated into the pane's `bash -c "…"` command. The Run button sends **no `PiConfig` at all**: pi has no permission prompts to bypass, and project trust is a decision the person at the terminal makes. ## What Codeman deliberately does NOT wire up - **`--api-key`.** Never. It would put a provider secret on the spawn command line, visible in `ps`, tmux server state and logs. `PI_*` overrides go through socket-scoped `tmux setenv` for exactly this reason. - **`--tui-mode`.** Pi's default main-screen TUI is the friendly case for a browser terminal. The fullscreen mode (0.84.0) stays your own runtime choice via `/settings`. - **`--name`, `--no-session`, `-p`/`--print`, `--mode json`, `--mode rpc`, `--tools`/`--exclude-tools`, `-e`/`--extension`, `--skill`, `--system-prompt`.** Tracked as follow-ups in the plan doc. ## Permission and trust model — read this **Pi has no permission prompts and no sandbox.** There is no `--dangerously-skip-permissions` analog and none is needed: tools run with the user's own permissions, always. A pi session can read, write and execute anything the Codeman user can. If you need isolation, use a **Docker case** — that is the isolation story, here as everywhere else in Codeman. Pi's "project trust" prompt is **not** a safety boundary (upstream says so too). It gates *loading* repo-local `.pi/` config, extensions and skills, and *installing* missing project packages. It only appears when the cwd or an ancestor contains `.pi/settings.json`, `.pi/extensions|skills|prompts|themes`, `.pi/SYSTEM.md`/`.pi/APPEND_SYSTEM.md`, or `.agents/skills`. A bare `.pi/` directory does not trigger it. `approveProjectTrust: true` answers it with `--approve`, which means pi **loads and executes repository-supplied TypeScript** and runs an npm install for missing project packages. Treat it exactly as seriously as that sounds. **Multi-user mode:** for an owner without the privileged-command grant, Codeman materializes `approveProjectTrust: false` so the pane launches with `--no-approve` and the prompt never appears. Merely *omitting* `--approve` would not be a clamp, since pi's own default is to ask and the session user could just answer yes. Also worth knowing: `pi auth print-api-key` / `print-bearer-token` and `pi auth check` mean a pi session can print its own provider credentials by design. Isolation is Docker. ## tmux extended keys (Shift+Enter) Pi's editor uses `Shift+Enter` / `Ctrl+Enter` for newline-vs-submit. Without extended keys, tmux collapses both into a plain `\r`. Upstream recommends: ```tmux set -g extended-keys on set -g extended-keys-format csi-u ``` `extended-keys-format` needs tmux 3.5+; on 3.2–3.4 `extended-keys on` alone works (pi falls back to xterm `modifyOtherKeys`). Codeman's browser input path sends `\r` for submit, so basic use works unconfigured — what degrades is newline-in-editor, mostly when you attach to the pane directly (`sc`). ⚠️ Upstream notes the setting may need a full `tmux kill-server` to take effect. **Never run `tmux kill-server` on Codeman's socket** — it would kill every live session, `w1`/`w2`/`w3` included. **Measured (tmux 3.4, pi 0.84.1): no `kill-server` is needed.** Setting the option server-scoped on Codeman's own socket takes effect on the ALREADY-RUNNING server; the next pi session starts without the warning. Existing sessions keep the old setting until they respawn. ```bash tmux -L codeman set -s extended-keys on tmux -L codeman set -s extended-keys-format csi-u # tmux 3.5+ only, see below tmux -L codeman show-options -s | grep extended # verify ``` On **tmux 3.4 and older, `extended-keys-format` does not exist** and the second line fails with `invalid option: extended-keys-format`. That is harmless — pi falls back to xterm `modifyOtherKeys` and `extended-keys on` alone silences the warning. Run the two lines independently rather than chained. Pi tells you which state it is in: an unconfigured session prints `Warning: tmux extended-keys is off. Modified Enter keys may not work.` in its startup banner, so you can verify the change by starting a new pi session. ⚠️ Use `-L ` and `-s`, never `-g` on your default socket, and never `kill-server`. Codeman does not set this for you: it is a server-wide tmux option and silently changing key encoding for every session of every backend is not Codeman's call to make. ## Typing from the browser (local echo) On touch devices Codeman buffers typed characters in the `LocalEchoOverlay` and flushes them to the PTY on Enter. Pi gets that `'buffer'` policy, the same as Claude, Gemini and OpenCode. This was an explicit open question, because that policy is exactly what broke Codex (issues #218/#219/#220/#222): Codex's composer reacts per keystroke, so buffer-until-Enter starved it. **Measured against pi 0.84.1: it does not reproduce.** Pi's slash-command picker re-filters on the whole composer content rather than on per-keystroke deltas, so a one-shot flush of `/set` filters the picker down to `settings` identically to typing it character by character, and the delayed `\r` then selects it. Prose prompts flush and submit correctly too. If a future pi release changes that, the cheap fallback is one `'off'` branch in `_updateLocalEchoState` (terminal-ui.js); teaching `PredictiveEchoAddon` pi's composer row is the larger follow-up. ## Docker cases The agent image (`docker/agent.Dockerfile`) installs pi in its own `RUN` step with `--ignore-scripts`, kept out of the shared npm block so the flag cannot change how the other four CLIs install. Rebuild with: ```bash node scripts/build-agent-image.mjs --no-cache # --no-cache is mandatory ``` Credentials are **seeded**, not shared: `~/.pi/agent/auth.json`, `settings.json`, `trust.json`, `models.json` and `models-store.json` are mounted read-only and copied into the container's own `~/.pi/agent`. So an in-container pi never writes refreshed OAuth tokens back to the host, and `docker commit` exports stay secret-free. `models.json` is in the list because it holds user-defined custom providers, which would otherwise silently vanish inside containers. Only those five files are seeded because `~/.pi/agent` also holds `sessions/`, `extensions/`, `skills/` and the installed package trees (`npm/`, `git/`), which on an active host is easily gigabytes. **Trade-off:** in-container pi sessions are invisible host-side, so `pi -c` inside a Docker case only sees that container's own history. ## Remote SSH cases `pi` mode is routed through an interactive login shell (`exec "$SHELL" -i -l -c 'pi'`), because sshd's remote-command PATH does not include npm's global bin on most hosts. Per-session config and `envOverrides` do not cross ssh and are rejected rather than silently ignored; use the per-host command override instead. ## Known gaps - **No idle/completion hook.** Pi has no hook system Codeman can install into, so idle detection falls back to output-stabilization like the other external CLIs. Pi 0.84.0 shipped an `agent_settled` extension event that is a genuine idle signal; a Codeman pi extension using it is the highest-value follow-up. - **No response viewer.** Pi writes JSONL v3 session files under `~/.pi/agent/sessions/`; nothing reads them yet. - **Cron jobs mis-detect readiness.** The cron readiness poll looks for `❯` or a token count, neither of which pi prints, so a pi cron job burns its poll budget and then sends the prompt anyway. It works; it is just slower to start. - **Ralph, respawn heuristics, token/CLI-info parsing and the `❯` readiness probe are off** for pi, as for every external CLI.