Skip to content

pi

gmux has built-in support for pi. No configuration is needed — launch pi through gmux and everything works automatically.

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.

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 layer

Renaming with pi’s /name command updates the sidebar live.

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.

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.)

There are two separate pi checks:

  • availability discovery in gmuxd: look up pi on PATH at daemon startup to decide whether the pi launcher should be shown
  • runtime matching in gmux: scan the launched command for a pi or pi-coding-agent binary name

The runtime matching works with direct invocation, full paths, npx, nix run, and other wrappers:

Terminal window
gmux -- pi # ✓ matched
gmux -- /home/user/.local/bin/pi # ✓ matched
gmux -- npx pi # ✓ matched
gmux -- pi update # ✓ matched, but runs directly (passthrough)
gmux -- echo "not pi" # ✗ not matched

Detection 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.

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.jsonl

gmuxd 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.

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 /resume picker — 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_settled bound each span of work (pi may emit several agent_ends per run when it retries; agent_settled fires exactly once), so gmux drives status without watching the file. Each completed assistant response is also reported as an iteration, which is what gmux wait and gmux agent logs count in their exchange reports.

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 aborted stop 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

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.

  • 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 -e flag, so that session won’t report hook-driven state.