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>
93 lines
4.5 KiB
Markdown
93 lines
4.5 KiB
Markdown
# 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:
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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).
|