Files
Gabriel Vidal 2b2ca23ad3 feat(workers): Settings → Workers, composer Runs-on chip, badges, takeover confirm
- lib/workers.tsx: useWorkers (workers SSE), WorkerSelect chip (auto from
  the account, amber when it fell back to the lab), WorkerBadge, StatusDot
- Settings → Workers page + pairing form (one string; account picker only
  when the login matches no account), install instructions
- ResumeBox: static worker badge; terminal_live 409 → confirm → takeover
- home banner for an offline/refused worker; typed worker run errors
- mock: workers seed (online/offline/unauthorized) + /api/workers handlers
- docs: CLAUDE.md Workers section, GOAL item, README box, worker/README.md
- standalone smoke asserts the runner's /feed + /pair routes

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 14:06:39 +02:00
..

ai-agent worker (macOS)

Run a Mac as a worker for the ai-agent hub: the hub spawns, resumes, forks and interrupts claude -p sessions on this machine, and shows its terminal sessions live. A session here is exactly a terminal session — your Keychain Claude login, mise, gcloud, op, gh, OrbStack.

The worker is the same sidecar/sidecar.py the homelab runs on its own host, as a launchd user agent. The hub always calls in; the worker never contacts the hub — it binds the Mac's Tailscale address only and holds one bearer token minted at pairing.

Install

Prerequisites: Claude Code installed and logged in (claude auth status), Tailscale up, git, and uv (or python3).

curl -fsSL https://git.gabvdl.xyz/gabrielvidal/ai-agent/raw/branch/main/worker/install-macos.sh | bash

It clones this repo into ~/.local/share/ai-agent-worker/src, builds a venv, writes ~/Library/LaunchAgents/xyz.gabvdl.ai-agent-worker.plist bound to tailscale ip -4 on port 8790, waits for /health, and prints the pairing string:

    100.80.162.92:8790/K7QMX4PJ2R/you@company.com

Paste it in the hub → Settings → Workers → Add worker. The login at the end picks the hub account automatically; the code works once.

Overrides: WORKER_CWD (where new runs start — default ~/projects/orus-monorepo), WORKER_PORT, WORKER_ACCOUNT (the hub's name for this login, default work), WORKER_BIND.

Day to day

S=~/.local/share/ai-agent-worker/src/worker/install-macos.sh
$S --status      # launchd state + /health
$S --pair        # a fresh pairing string (re-pair, or a new hub) — rotates the token on use
$S --update      # git pull + deps + restart (keeps the pairing)
$S --uninstall   # stop + remove the agent (state in ~/.config/ai-agent-worker kept)

Logs: ~/Library/Logs/ai-agent-worker/worker.log (the worker) and runs/<session>.log (each claude -p). State: ~/.config/ai-agent-worker/ (token, worker.json, a pending pairing-code) — delete it to forget the hub.

Notes

  • Keychain. A launchd agent runs in your GUI session and can read the login keychain; the first run may pop a Keychain access prompt for claude — pick Always Allow. If it can't, claude setup-token and add CLAUDE_CODE_OAUTH_TOKEN to the plist's EnvironmentVariables.
  • Asleep = offline. A closed lid drops the Mac off the tailnet; the hub marks the worker offline and new runs on its account go to the lab (the composer chip turns amber and says so). In-flight runs survive a worker restart (they're detached; launchd's AbandonProcessGroup), not a sleep.
  • Binding. The listener is on the Tailscale IP only — nothing on the office LAN. If the App-Store Tailscale client ever refuses the bind, set WORKER_BIND=127.0.0.1, re-run the installer, and expose it with tailscale serve --bg --tcp 8790 tcp://127.0.0.1:8790.
  • Remote Control is off (SIDECAR_REMOTE_CONTROL=0): org-disabled on the work seat, and a work run must not list itself there anyway.
  • Not yet: the lab skills that call the hub back (notify-done, ask-form, conv-meta, complete) — a worker never contacts the hub, so a worker session's "finished" comes from the hub's exit watcher.