The phone control plane for ruche, the disposable agent Host: tmux sessions that know whether an agent is idle, working, blocked or done, plus a web dashboard that installs as a PWA and pushes a notification when a session blocks or finishes (ADR-0054). Upstream: agent-of-empires/agent-of-empires
URL: https://essaim.{agents_domain} — tailnet only, on the agent tier’s own DNS zone (ADR-0068)
Data: nothing backed up. Sessions, worktrees and the push keypair are all rebuildable or disposable, which is the whole point of the Host
[!IMPORTANT]
The role is guarded on when: "'agent' in group_names", so the Host must carry tags = ["agent"] in hosts.toml or the deploy is a green no-op that installs nothing. auberge host list shows the TAGS column. Not to be confused with tailnet_tag = "agent", an unrelated field naming the ACL tier.
Set up the agent tier’s DNS zone first — the vhost needs a certificate for essaim.{agents_domain}, and DNS-01 is the only challenge a Host with no public ingress can answer.
the agent tier’s own domain; essaim.{agents_domain} is the dashboard
agents_cloudflare_dns_api_token
answers DNS-01 for that zone, and for no other
aoe_subdomain
the label, and the agent tier’s serving gate — host-scope it
aoe_tailscale_ip
the Host’s tailnet IPv4: what Caddy binds and what Blocky publishes
aoe_passphrase
the login passphrase, minimum 8 characters
aoe_tailscale_ip is fleet-wide, never host-scoped: the Blocky that publishes the name runs on another Host and cannot read a value scoped to this one (ADR-0059). aoe_subdomain is the opposite — host-scope it, because it also decides which Host’s Caddy holds the agent tier’s ACME token (ADR-0072).
generated by aoe serve, in the URL, rotates every four hours
the passphrase
aoe_passphrase, typed on the login page
The token is not settable and not stored in config.toml — there is no flag or environment variable for it. Read the current pairing URL on the box:
Terminal window
sshruche'aoe url'# the full URL, token included
sshruche'aoe url --token-only'# just the token, to paste into the PWA
Pairing binds the device, and a bound device stays signed in across token rotations. Changing aoe_passphrase signs every device out.
[!TIP]
Wrap the two commands above into one-command pairing — open the dashboard directly, or print a QR code (--qr) for a phone, which beats typing a 64-character token on a keyboard that isn’t there. -h/--help prints usage without touching the network:
HTTPS is functional here, not cosmetic: PWA install and web push both require a secure context, which is why the tailnet-only dashboard still carries a real certificate.
Install from Chrome on Android: open the dashboard, menu → Add to Home screen.
Push is offered after install. The VAPID keypair is server-generated and held on the box; nothing provisions it and nothing backs it up. Losing it means every device re-subscribes — acceptable under the Host’s disposability rule.
An installed PWA binds to its exact origin. Treat essaim.{agents_domain} as frozen once anyone installs it; changing the domain forces a delete-and-reinstall on every device.
Push subscriptions are bound to the bearer token and dropped when it rotates past a grace period. Login sessions are independent (30-day sliding window, persisted across restarts), so rotating the token kills push, not sign-in.
Sessions survive both a client detach and an SSH disconnect: the dashboard runs in the unprivileged agent user’s own systemd manager with lingering enabled, so nothing is torn down at logout.
Terminal window
sshruche'systemctl --user status aoe'
sshruche'journalctl --user -u aoe -f'
sshruche'aoe serve --status'# the daemon's own view: PID, mode, URLs, log path
MemoryHigh is declared at 4G and MemoryMax deliberately is not. MemoryHigh throttles and reclaims; MemoryMax is where the kernel kills. aoe supervises tmux sessions running agents it did not fork, and until it is observed which cgroup those land in, a kill line risks OOM-killing an agent mid-run. Set one once that is known.