Files
ai-agent/worker/README.md
Gabriel Vidal 2fc19c2470 feat(notify): one notify CLI (send/ask/form/wait) that also runs on workers via a sidecar outbox
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>
2026-10-06 15:06:27 +02:00

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-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 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 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).