Files
ai-agent/worker/README.md
Gabriel Vidal 7ac7fe6c17 feat(worker): generic Mac installer (cwd + account prompts) and opt-in autoRoute per worker
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>
2026-10-06 16:41:26 +02:00

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-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 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|1 forces it.
  • Notifications, asks and forms work: the notify CLI (cli/notify.py, on every run's PATH; notify-done / notify-ask / ask-form are 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's SKILL.md into ~/.claude/skills/notify. A notify send --final marks 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).