Files
ai-agent/worker/make_plist.py
Gabriel Vidal 3a0ef08dcf feat(worker): sidecar runs as a paired macOS worker — feed, pairing, launchd installer
- liveness falls back to psutil where there is no /proc (macOS)
- GET /feed + /feed/file: the hub pulls transcripts it has no mount for
- POST /pair trades a one-time code for the worker's bearer; /unpair drops it
- /health reports worker identity + permission mode
- worker/install-macos.sh: venv, launchd agent bound to the Tailscale IP,
  prints the pairing string (addr/code/claude login)
- Dockerfile copies every sidecar module (claude_cli was missing, so the
  in-container runner could not import)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 13:49:07 +02:00

46 lines
1.7 KiB
Python

"""Write the worker's launchd agent plist (called by install-macos.sh, which
passes everything through the environment).
KeepAlive restarts the worker if it dies — including at login, when Tailscale
may not be up yet and binding its address fails for the first few seconds.
AbandonProcessGroup keeps launchd from killing in-flight `claude -p` runs when
the worker restarts: they're daemons (their own session), and a restarted
worker re-discovers them from its pidfiles, exactly like the host sidecar
under systemd's KillMode=process."""
import os
import plistlib
e = os.environ
logs = e["LOGS"]
plist = {
"Label": e["LABEL"],
"ProgramArguments": [e["PY"], os.path.join(e["SIDECAR"], "sidecar.py")],
"WorkingDirectory": e["SIDECAR"],
"RunAtLoad": True,
"KeepAlive": True,
"ThrottleInterval": 10,
"AbandonProcessGroup": True,
"ProcessType": "Interactive",
"StandardOutPath": os.path.join(logs, "worker.log"),
"StandardErrorPath": os.path.join(logs, "worker.log"),
"EnvironmentVariables": {
"HOME": os.path.expanduser("~"),
"PATH": e["RUN_PATH"],
"SIDECAR_BIND": e["BIND"],
"SIDECAR_PORT": e["PORT"],
"SIDECAR_CWD": e["CWD"],
# This Mac's own `claude` login (Keychain) is the only account here;
# the id is what the hub calls it. Remote Control is org-disabled on
# the work seat, and a work run must never list itself there anyway.
"SIDECAR_DEFAULT_ACCOUNT": e["ACCOUNT"],
"SIDECAR_REMOTE_CONTROL": "0",
"SIDECAR_LOG_DIR": os.path.join(logs, "runs"),
"CLAUDE_BIN": e["CLAUDE_BIN"],
"WORKER_STATE_DIR": e["STATE"],
"PYTHONUNBUFFERED": "1",
},
}
with open(e["PLIST"], "wb") as f:
plistlib.dump(plist, f)