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
-
Point the box at the relay:
~/.pi/agent/gadhs-pi-remote.jsonwith{ "relay": "wss://<relay>/ws", "boxName": "my laptop" }(orGADHS_PIVOT_RELAY).wss://is required; plainws://is accepted only forlocalhost. See tuning. -
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
-
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-controlonly brings the link up — trusted devices reconnect on their own, no QR — and/remote-control pairadds another. -
On the phone: open the pivot app, scan. The device is trusted from then on:
/remote-control deviceslists them,/remote-control forgetpicks one to drop (off the disk, off the relay’s vouch list, off the live link). -
/remote-control stopends 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.