Working remotely

On this page

The distribution is a set of pi extensions. Whatever runs pi runs them; the question for any remote setup is only where pi runs and how its dialogs reach you. Three families, in order of how much of the distribution they carry.

Supported: pi stays in a terminal, the terminal is remote

Everything ships and works unchanged — guards, judge, plan pane, seeding, the cold review — because pi is the TUI process on the box where the code is.

VS Code Remote-SSH or code tunnel. The extension host and the integrated terminal run on the remote box; open a terminal, run pi. Marketplace extensions that wrap pi in a panel (CodePi embeds the TUI in a webview; EthanChow.pi-coding gives a sessions side bar with terminal editors) work the same way. This is the “VS Code remote mode” workflow and it costs the distribution nothing.

A terminal from a phone. ssh (Termius, Blink Shell) over the agency VPN or Tailscale into a tmux session running pi. The real TUI, every dialog included; the approval pane refuses when the screen is too short and says so — landscape helps. Nothing new is installed in pi and SSH is the authentication. This is the one phone path that keeps the transcript inside the agency’s network.

Partial: a messenger or app bridge beside the TUI

Extensions such as badlogic/pi-telegram, @zylab/pirelay, the Slack and Discord bridges, and remote-pi’s app mode attach to a running terminal session and carry prompts in and streamed answers out. The distribution runs untouched — but its dialogs stay at the desk: pi gives no hook for one extension to mirror another’s dialog, and none of these bridges implements the distribution’s own ask broker, so a judge’s ask or a plan approval waits in the terminal until someone is there (the pivot client is the one that does). Two cautions before using one on agency work: every bridge routes the transcript — code excerpts, tool output, file contents the agent reads — through a third party’s servers (Telegram’s Bot API, Slack, Discord, a relay), which is a data-handling question, not a tooling one; and remote-pi additionally ships an agent-to-agent mesh and unattended daemons that are out of policy (security). Treat bridges as personal-use tools where policy allows.

Headless hosts: pi --mode rpc

(The pivot client no longer runs pi this way; see below. This section is for the other hosts.)

IDE plugins, pi-web-ui, supervisors and the pivot client run pi as a child in RPC mode and receive its dialogs as extension_ui_request events. From @gadhs/pi-modes 0.23.0 the distribution behaves correctly there (what changes): every dialog is bounded, silence is deny or refusal, a deferred permission ask is put to the host with a wait, plan approval is a plain select. A host that forwards dialogs gets a working approval flow; one that does not gets a turn that ends with a reason instead of hanging.

First-class: the pivot client and /remote-control

pivot (gadhs/standard/package/pi-pivot) is the agency’s own remote client: a web app served from a DHS site and installed to the home screen on any device (no app stores), a DHS-operated relay that is an authenticated dumb pipe routing end-to-end-encrypted frames, and — on the box — an extension, not a daemon: @gadhs/pi-remote, a package of the distribution (#142). The phone follows the session you are in live, sends prompts, stops turns, and answers its gate asks, plan approvals, ask questions and memory picks through the ask broker. The transcript never leaves the pair in the clear: pivot’s wire (@gadhs/pivot-wire, one Noise IK implementation compiled to wasm for both ends) encrypts every frame, and the relay sees only ids and ciphertext.

Set up once per box

  1. Point the box at the relay: ~/.pi/agent/gadhs-pi-remote.json with { "relay": "wss://<relay>/ws", "boxName": "my laptop" } (or GADHS_PIVOT_RELAY). wss:// is required; plain ws:// is accepted only for localhost. See tuning.

  2. Run /remote-control status: on a fresh box this mints the identity and prints the box id, which must be on the relay’s allow list (PIVOT_BOXES) before the box can connect; ask whoever runs the relay. The id is the public half of the identity kept at ~/.pi/agent/gadhs-pi-remote/identity.key (64 bytes, mode 0600; a key, not a setting).

Every time

  1. In the session you want to take with you: /remote-control. With no device trusted yet it pairs: a pane shows a QR and the same URL as text, and closes when a device pairs, when the two-minute window runs out, or on Esc. Once a device is trusted, a bare /remote-control only brings the link up — trusted devices reconnect on their own, no QR — and /remote-control pair adds another.

  2. On the phone: open the pivot app, scan. The device is trusted from then on: /remote-control devices lists them, /remote-control forget picks one to drop (off the disk, off the relay’s vouch list, off the live link).

  3. /remote-control stop ends it: every device is told the session ended. Closing pi does the same.

What the phone can answer is exactly what the ask broker carries; what it cannot (the permission system’s own prompts on the excluded surfaces, a third-party extension’s dialog, pi’s built-ins) it shows as a “waiting at the desk” nudge. In the terminal a phone adds an answerer and no deadline; under pi --mode rpc the headless waits above still apply. What this repository proves: the whole box side against an in-memory relay and a real wasm device (pairing, admission, both directions, the answerer, loss and reconnect); what it cannot prove here and pivot’s own end-to-end run does: TCP, TLS and a real phone.

Edit this page · latest