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

79 lines
3.7 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 — 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`).
```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`, 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
```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 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).