The worker installer no longer assumes the Orus work seat. It asks for the default working dir — the directory it is run from, confirmed with `y`, or a typed path (taken as is without a terminal) — and for the hub-side account, proposed from `claude auth status` (a gmail login → personal, else work). `--update` keeps the installed agent's cwd and account. Remote Control stays off on every worker. Pairing a personal Mac on `personal` would have taken every personal spawn while it was awake (for_account), so workers now carry an `autoRoute` flag: off at pairing (the lab stays the runner; the worker only gets what the composer's Runs-on chip pins to it), toggled from the Settings → Workers card or the pairing dialog. Records from before the flag keep auto-routing. Docs genericized (worker README, CLAUDE.md Workers, notify SKILL, GOAL). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
4.5 KiB
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 — this Mac's
Keychain Claude login and whatever its shell has (mise, gcloud, op, gh,
OrbStack…). Any Mac works: a personal laptop paired on the personal account,
or a work seat paired on work.
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).
cd to the directory new runs should start in (your projects dir, a repo…),
then:
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, asks you to
confirm the current directory as the default working dir (y, or type another
path — without a terminal it is taken as is), 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 (each skips its prompt or default): WORKER_CWD (where new runs
start), WORKER_PORT, WORKER_ACCOUNT (the hub's name for this login —
default personal; a work seat passes work), WORKER_REMOTE_CONTROL (1/0
— default on, off for a work account), WORKER_BIND. --update keeps the
cwd, account and Remote Control setting the installed agent already has.
Note that the hub sends every new run on an account to that account's
online worker unless the composer picks a runner explicitly: a personal Mac
paired on personal takes the lab's personal spawns while it is awake.
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 on for a personal account and off for
work(SIDECAR_REMOTE_CONTROL): org-disabled on a work seat, and a work run must not list itself there anyway.WORKER_REMOTE_CONTROL=0|1forces it. - Notifications, asks and forms work: the
notifyCLI (cli/notify.py, on every run's PATH;notify-done/notify-ask/ask-formare its aliases) queues them in this worker's outbox (/outbox, in~/.config/ai-agent-worker/outbox.json) and the hub collects them on its 2 s poll, records and forwards them, and pushes the tapped / submitted answer back — the worker still never calls the hub. The installer links the skill'sSKILL.mdinto~/.claude/skills/notify. Anotify send --finalmarks the session finished in the hub; the exit watcher still covers runs that end without one. - Not yet: the other lab skills that call the hub back (conv-meta, complete).