pi-daemon1.3

Log 07 · Operate

Run it unattended.

One per-user service, a private listener, a bearer in a file, a watchdog that judges health by answers, and updates you install now and activate when you choose.

01Install

A Home Manager service, one per user

{
  imports = [ inputs.pi-daemon.homeManagerModules.default ];
  services.pi-daemon = {
    enable = true;
    profiles.personal = {
      cwdRoots = [ "~/src" ];
      model = "anthropic/claude-sonnet-4-5";
      mcpServers.notes = { command = "/path/to/notes-mcp"; args = [ "stdio" ]; };
      envFiles = [ "~/.config/pi/daemon/personal.env" ];   # secrets stay out of the store
    };
    api.tokenFile = config.sops.secrets.pi-daemon-api-token.path;   # optional
  };
}
  • What it installspi-daemon under systemd, com.pi-daemon under launchd, or a supervisord program on Nix-on-Droid, plus pi-daemon-watchdog. It listens on 127.0.0.1:7463.
  • Where things liveState in ~/.local/state/pi-daemon; Pi’s logins, models and settings in ~/.pi/agent, shared with TUI Pi. Both are owner-only, and foreign-owned or open paths are refused.
  • From a checkoutjust install builds the locked npm package into ~/.local without touching a running service.
  • By handpi-daemon serve --config FILE, with --instance, --state-dir and --api-port for a separate trust domain.

02Configuration

A strict file, flags on top

# ~/.config/pi/daemon/INSTANCE/config.yaml, or --config, or PI_DAEMON_CONFIG
instance: work
stateDir: ./state
agentDir: ~/.pi/agent
api:
  bind: 127.0.0.1
  port: 7463
  tokenFile: /run/secrets/pi-daemon-work
profiles:
  personal: { cwdRoots: [~/src] }

The file must belong to you or root and not be group- or world-writable. Unknown fields, aliases and duplicate keys are refused; relative paths resolve from the file’s directory. Command-line flags override it. Secrets are never values in it: they are files, descriptors or environment references. In Nix the profiles are rendered into an immutable file for you.

Every field in the operations reference

03Bearer

One token in one owner-only file

  • Generated or yoursWithout a configured source, the first start atomically creates a random STATE_DIR/api-token, owner-only, stable across restarts. Or point api.tokenFile at a secret.
  • Managed secretsA sops-nix symlink chain is resolved, and its final file opened without following another link; it must be a regular, owner-only file you own.
  • Checked firstEvery path but /healthz and /readyz needs Authorization: Bearer. The token is reduced to a digest once loaded, and never logged or returned.
  • Shared by clientsEvery client of an instance sees every session. Point clients at the file; never paste the token into chat, a command line or a URL.
Plain HTTP by default

The listener speaks plain HTTP on loopback. Binding another address is an explicit choice: use a trusted network such as a tailnet, or put a TLS reverse proxy in front.

04Watchdog

Health is an answer, not a PID

How the watchdog decides Every 30 seconds the watchdog probes the API root with a 5-second deadline and expects the exact unauthenticated 401. An answer that takes more than 2 seconds is degraded latency: reported, never a restart. One hard failure is suspect; a second in a row triggers one recovery of only the pi-daemon service, recorded in STATE_DIR/watchdog-v1.json before the supervisor is asked. While the daemon stays unhealthy it does not try again; the next good answer clears the latch and starts a new failure epoch. healthy401 within 2 s degraded401, but over 2 s suspectone hard failure recover oncerestart only pi-daemon latchedno second try a probe fails next one fine fails again recorded a good answer clears the latch slow reported only
Fig. 7.1A stuck event loop still has a PID and a listening socket; only an HTTP answer proves it is alive. Slowness is reported, not punished.

pi-daemon-watchdog is its own small Node process, so it keeps working when the daemon does not. Under launchd it first sends TERM to the exact job and watches the PID; only the same stuck PID gets one recorded kickstart -k. Its records hold the phase, latency, status and supervisor, never URLs, tokens or response bodies. Tune it with services.pi-daemon.watchdog, or disable it only if another semantic supervisor does the same job.

05Inspect

Ask the daemon

pi-daemon status               # ready · not_ready · unreachable; exits 0 only when ready
pi-daemon sessions list        # the catalog as JSON, most recently active first
systemctl --user status pi-daemon pi-daemon-watchdog
launchctl print "gui/$UID/com.pi-daemon"

The daemon writes one JSON object per line to stderr — pi_daemon_startup_stage, pi_daemon_worker_ready, pi_daemon_worker_exited, pi_daemon_ready and so on — which the service modules send to pi-daemon.err.log in the state directory. Logs never contain prompts, model output, credentials, environment values or tokens.

06Update

Install now, activate when you choose

Where updates go pi-daemon update downloads the latest GitHub release's npm artifact and checksum, verifies both and the artifact's exact shrinkwrap integrity, installs it with lifecycle scripts disabled into an owner-private versions directory, then atomically switches the current link and ~/.local/bin/pi-daemon. One previous version is kept for rollback. With services.pi-daemon.mutableRuntime enabled, the Home Manager service starts through a Nix-store launcher that prefers ~/.local/bin/pi-daemon and falls back to the Nix package. Nothing restarts until you restart the service. GitHub releasenpm artifact + SHA-256 verifychecksum · shrinkwrap versions/<previous>kept for rollback versions/<new>newly installed currentswitched atomically ~/.local/bin/pi-daemonthe managed CLI service launchermutableRuntime Nix packagethe fallback npm, no scripts prefers else
Fig. 7.2A verified release becomes the managed CLI; the service picks it up on its next restart, which is yours to make.
pi-daemon self-update status     # offline
pi-daemon update                 # check, verify, install, switch
systemctl --user restart pi-daemon            # Linux, when you are ready
launchctl kickstart -k "gui/$UID/com.pi-daemon"   # macOS
pi-daemon self-update rollback   # back to the one kept version

A restart is a resume, so activating an update loses no work. For a private repository, pass a token only through the environment, as GH_TOKEN; it is sent as a GitHub bearer header and never stored. Releases older than the running CLI or the active version are refused.

07Reference

The detail behind these pages

  • QuickstartInstall the service, find the bearer, connect a client.
  • OperationsEvery protocol message, profile field, service option and watchdog setting.
  • Culture setupThe host entry, what Culture shows, reaching it from elsewhere.
  • ApplicationsA profile plus an AHP or Chord client, step by step.
  • SecurityThe enforced defaults and the trust model.
  • ReleaseHow releases are cut and verified.