Tutorial · 4 of 4

Daemon mode

So far your agents only run while a terminal is open. Daemon mode keeps a folder running as a background agent that survives logout, restarts on crash, and answers your phone at 3am. This is the how; for the why — and how it compares to all-in-one platforms — see Why Pi.

The shape of it

One supervisor runs per machine. Under it sit N background agents — one per folder you promote. The supervisor is a normal user service: launchd on macOS, systemd --user on Linux. It starts at login, survives reboots, and respawns any agent that crashes.

Lock down tool permissions first
A daemon inherits the same Pi tool permissions your interactive session has — Bash, Edit, Write all run without a prompt, because no one is at the keyboard to approve them. Configure Pi's tool permissions to taste before you promote a folder to a 24/7 daemon. A tool-approval gate is on the roadmap.

1. Install the supervisor (once per machine)

From inside Pi:

In Pi — text
/remote-pi install

That single command does two things:

  • Installs and activates the user-level supervisor service (launchd / systemd --user), so it auto-starts at login and after reboot.
  • Symlinks the remote-pi and pi-supervisord CLIs into ~/.local/bin/so you can manage daemons from any shell. If that directory isn't on your $PATH, the command prints the line to add.

This is a separate, explicit opt-in — it is not part of the regular setup wizard. You only run it on machines where you want 24/7 agents.

2. Promote a folder to a daemon

No per-folder setup is needed first — create registers any folder and the supervisor injects the daemon's config at spawn (a fixed assistent workspace, relay on), so the folder needs no .pi/remote-pi/ of its own. To reach the daemon from your phone, just make sure this machine has been paired once — pairing is per-machine, so any earlier /remote-pi pair on it counts. Then register:

Shell — bash
remote-pi create ~/Movies --name "Video Editor"
# → Daemon registered: id=4e39152d name="Video Editor" cwd=/Users/you/Movies · started

The id is a stable hash of the folder path (sha256(realpath)[:8]), so it survives moves and is the same on every machine. With the supervisor running, create starts the daemon right away — there is no separate start step. It restarts on crash and comes back after a reboot on its own.

One daemon per folder
The by-path id rejects a second daemon in the same directory at createtime. Pairing stays interactive — a daemon reuses the keypair and paired devices already set up on this machine; it doesn't show a QR itself.

3. Manage the fleet

Every command works as a Pi slash command (/remote-pi …) and, once the CLI is linked, as a plain shell command (remote-pi …):

Fleet commands — bash
remote-pi daemons                  # list registered daemons + state
remote-pi daemon status            # pid, uptime, restart count
remote-pi daemon send 4e39152d "Cut the first 30s of the latest clip"
remote-pi daemon restart 4e39152d  # restart one daemon by id
remote-pi daemon restart           # ...or the whole fleet (no id)
remote-pi daemon stop 4e39152d     # stop one
remote-pi daemon stop              # stop all

A daemon receives a prompt as if a user typed it; its response flows back through the same mesh and relay you configured — your phone sees it live, and other agents on the machine see it over the local mesh.

Where the logs are

Logs — bash
# Linux
journalctl --user -u remote-pi-supervisord -f

# macOS
tail -f ~/.pi/remote/supervisord.log

Each daemon's output is forwarded into the supervisor log with a [<cwd>] prefix, so one stream shows the whole fleet.

4. Schedule recurring prompts (cron)

A daemon only acts when something prompts it. cronlets the supervisor be that something on a schedule — “every weekday at 9am, summarize the new PRs” — with no one at the keyboard. Jobs target a daemon by id and survive reboots along with the supervisor.

Cron needs the supervisor installed as a service
The scheduler runs inside the supervisor, so it only fires when the supervisor is installed as a user service. Run /remote-pi install (step 1) first — without it, cron add warns instead of pretending to schedule. It is the same launchd / systemd service that keeps your daemons alive; there is no second scheduler.

With a daemon registered (step 2) and the supervisor running, add a job. The first argument is the daemon id, then a standard five-field cron expression, then the prompt:

Schedule a prompt — bash
# every weekday at 9am, São Paulo time
remote-pi cron add 4e39152d "0 9 * * 1-5" "Summarize the new PRs" --tz America/Sao_Paulo
# → Cron j_ab12 added → daemon 4e39152d: "0 9 * * 1-5" (America/Sao_Paulo). Next run: …

Runs must be at least 60 seconds apart — a more frequent expression is rejected with a clear message, since every fire spends tokens. Flags on cron add:

  • --tz Area/City — run in a specific, DST-aware timezone (e.g. America/Sao_Paulo). Defaults to the machine's local time.
  • --wake — if the daemon is stopped when the job fires, start it first, then send. Default: skip (logged as skipped_down).
  • --no-skip-busy — send even if the daemon is mid-turn. By default a fire is skipped while the daemon is still working (skipped_busy) so prompts don't pile up on an unfinished turn.
  • --catchup — after the supervisor was down, fire one missed run on startup. Default off; never replays the whole backlog.

Each job gets an id like j_ab12 (printed by cron add and cron list). Inspect, test, and audit the fleet's schedule:

Inspect & audit — bash
remote-pi cron list                # schedule, enabled, last run/status, next run
remote-pi cron run j_ab12          # fire one now, ignoring its schedule
remote-pi cron disable j_ab12      # pause without deleting (enable to resume)
remote-pi cron log --tail 20       # recent fires AND skips
remote-pi cron remove j_ab12       # delete the job

The agent's reply is fire-and-forget into the mesh — your phone and other agents see it live, exactly like a manual daemon send. Cron itself only audits the trigger: every fire and every skip appends one line to ~/.pi/remote/cron.jsonl, which cron log tails. Full subcommand reference is in the docs.

Removing a daemon

Cleanup — bash
remote-pi remove <id>              # unregister one daemon (folder config kept)
remote-pi uninstall                # remove the supervisor service (registry kept)

uninstall is reversible — re-running /remote-pi install later brings every registered daemon back. Full flags and paths are in the reference docs.