pi
gmux has built-in support for pi. No configuration is needed — launch pi through gmux and everything works automatically.
What you get
Section titled “What you get”Live status
Section titled “Live status”The sidebar shows when pi is actively working. gmux loads a small extension into pi at launch which reports each turn boundary, so the agent shows as active (pulsing cyan dot) while it is processing and clears when the turn completes — no PTY scraping or log parsing.
Session titles from conversations
Section titled “Session titles from conversations”Instead of showing “pi” for every session, the extension reports pi’s session name — which pi auto-generates from the conversation (and you can change with pi’s /name command):
▼ ~/dev/myapp ● Fix the auth bug in login.go ● Add pagination to the API ○ Refactor database layerRenaming with pi’s /name command updates the sidebar live.
Resumable sessions
Section titled “Resumable sessions”When a pi session exits, it remains in the sidebar as a resumable entry. Click it to resume exactly where you left off — gmux launches pi --session <path> -c with the right conversation file.
Resumable sessions are deduplicated: if you’re already running a session that matches a resumable entry, only the live one appears.
Launch from the UI
Section titled “Launch from the UI”Pi appears in the launch menu only when it is available on the current machine. gmuxd checks this at startup by looking for a pi binary on PATH; if none is found, the pi launcher is omitted from the UI. (Restart the daemon after installing pi.)
How it works
Section titled “How it works”Detection
Section titled “Detection”There are two separate pi checks:
- availability discovery in
gmuxd: look uppionPATHat daemon startup to decide whether the pi launcher should be shown - runtime matching in
gmux: scan the launched command for apiorpi-coding-agentbinary name
The runtime matching works with direct invocation, full paths, npx, nix run, and other wrappers:
gmux -- pi # ✓ matchedgmux -- /home/user/.local/bin/pi # ✓ matchedgmux -- npx pi # ✓ matchedgmux -- pi update # ✓ matched, but runs directly (passthrough)gmux -- echo "not pi" # ✗ not matchedDetection is validated: GMUX_ADAPTER=pi only takes effect if the command actually invokes a pi / pi-coding-agent binary, so it can’t force the pi adapter onto an arbitrary wrapper script. If you wrap pi, keep the binary name visible in the command (e.g. gmux -- env FOO=1 pi), or name your wrapper pi/pi-coding-agent.
Session files
Section titled “Session files”Pi stores conversations as JSONL files in ~/.pi/agent/sessions/. Each working directory gets its own subfolder with an encoded name:
~/.pi/agent/sessions/ --home-mg-dev-myapp--/ 2026-03-15T10-00-00-000Z_abc123.jsonl 2026-03-15T11-30-00-000Z_def456.jsonlgmuxd watches this directory to keep its conversation index (URL resolution and search) current and to notice when a conversation file is deleted — a deleted conversation lets gmux retire the corresponding dead session. Watching never creates sidebar sessions on its own. gmux honors pi’s own PI_CODING_AGENT_DIR override: if you point pi at an isolated data directory, gmux looks for conversations under $PI_CODING_AGENT_DIR/sessions. Live session state — attribution, title, and status — comes from the extension, not from parsing these files. The first line of each file is a session header with a UUID and timestamp.
The gmux extension
Section titled “The gmux extension”When gmux owns the launch, it injects the gmux session extension into pi (pi -e <materialized-extension>; extensions accumulate, so it coexists with your own). The extension subscribes to pi’s own lifecycle and reports state to the runner authoritatively — no inference:
session_start(fires on startup and on every/new,/resume, and/fork) reports the active conversation file, id, and name. This is what binds a session to its file, and it’s the only signal that survives selecting an already-loaded session from pi’s/resumepicker — pi serves that from memory without touching disk, so there is nothing for an external heuristic to observe. (This replaces the old scrollback content-matching; see ADR 0011.)agent_start/agent_settledbound each span of work (pi may emit severalagent_ends per run when it retries;agent_settledfires exactly once), so gmux drives status without watching the file. Each completed assistant response is also reported as an iteration, which is whatgmux waitandgmux agent logscount in their exchange reports.
Status
Section titled “Status”The extension reports each turn with a normalized, agent-agnostic outcome; gmux maps it to the sidebar:
- turn start → active (pulsing cyan dot)
- completed → idle, marked unread
- interrupted (pi’s
abortedstop reason — you pressed Esc) → idle, recorded as an intentional stop: no “finished” notification, and a synchronous wait reports the stop rather than a completion - error (pi exhausted its auto-retries, or the turn was cut off by a length/tool-use limit) → red dot; clears when you view the session or send a new message
Disabling the extension
Section titled “Disabling the extension”If a pi release ever breaks the extension, set GMUX_NO_AGENT_HOOK=1 to launch pi without it. Pi runs normally; gmux just won’t show hook-driven title/status/attribution until you unset it (or a fix ships). For sessions launched from the web UI or resumed by the daemon, set the variable in the daemon’s environment (it’s read by the runner, which the daemon spawns).
One-shot pi commands are never extended or wrapped in a session — gmux execs them directly: the subcommands auth, install, remove, uninstall, update, list, config (immediately after the binary) and the info flags --help/-h/--version.
Limitations
Section titled “Limitations”- Title appears after the first turn. While the first turn is running, the session shows a generic title. Once the turn ends, gmux titles it with your first message; when pi generates or you set a session name, that name takes over.
- The extension only loads when gmux controls the launch. A shell-wrapped invocation (e.g.
bash -lc "pi …") doesn’t receive the-eflag, so that session won’t report hook-driven state.