- 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>
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-tokenand addCLAUDE_CODE_OAUTH_TOKENto the plist'sEnvironmentVariables. - 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 withtailscale 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.