cli/notify.py merges the lab's notify-done, notify-ask and ask-form scripts
into one stdlib-only CLI shipped with this repo (the aliases stay as shims),
so a session on a remote worker has it too. Two transports: the hub directly
on the lab, or — on a worker — the sidecar's new /outbox, which the hub's
OutboxMirror polls every 2 s, creating the record through the same functions
/api/notify, /api/ask and /api/forms run and pushing the answer back. The
worker still never calls the hub.
The hub now owns what the CLI used to compute: the default conversation URL
(worker transcripts included), the `notified` meta stamp, and a `--final`
push marks the session finished. A push's --action-cmd button answers on
/api/notify/{id}/action like an ask (HA integration updated). Ids are minted
by the CLI (ntf_/ask_/frm_) so viewer, phone and worker agree. Dropped: the
direct-HA fallback, the DONE button, the Forge cost line, the dashboard
mirror. The viewer's widgets also parse the `notify send|ask|form|wait`
spelling; the worker installer links the skill into ~/.claude/skills.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3.7 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 — 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. - 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).