feat(agent-queue): macOS LaunchAgent boot-persistence (auto-start + KeepAlive)
Adds agent-queue-boot.sh (PATH repair + ~/.agent-queue.env overrides + caffeinate wrap) and launchd/ (install.sh + README) so the run loop auto-starts on login and survives reboot/crash — the persistence layer tmux+caffeinate alone cannot give. No secrets tracked (host config lives in untracked ~/.agent-queue.env). Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
f7999fb11b
commit
d574f5dda3
48
agent-queue/agent-queue-boot.sh
Executable file
48
agent-queue/agent-queue-boot.sh
Executable file
@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# agent-queue-boot.sh — boot/login entrypoint for the agent-queue run loop.
|
||||
#
|
||||
# Launched by the macOS LaunchAgent (see launchd/) so the folder-kanban worker
|
||||
# auto-starts on login AND survives reboot/crash (LaunchAgent KeepAlive). This is
|
||||
# the reboot-persistence layer that tmux + caffeinate alone cannot provide.
|
||||
#
|
||||
# It does three things launchd's minimal environment needs:
|
||||
# 1. Repairs PATH so the agent CLIs (codex/devin/claude) + caffeinate are found.
|
||||
# 2. Loads optional overrides from ~/.agent-queue.env.
|
||||
# 3. Wraps `agent-queue run` in caffeinate (macOS) so the Mac won't sleep while
|
||||
# a job is running. NOTE: because the run loop is long-lived, this keeps the
|
||||
# machine awake for as long as the LaunchAgent runs — intended for a dedicated
|
||||
# overnight runner. Set AGENT_QUEUE_NO_CAFFEINATE=1 to allow idle sleep.
|
||||
#
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd -P)"
|
||||
|
||||
# launchd hands processes a bare PATH — prepend the usual CLI install locations
|
||||
# (Homebrew arm64/intel, ~/.local/bin for devin, system dirs) ahead of it.
|
||||
export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${PATH:-}"
|
||||
|
||||
# Optional per-machine overrides (engine, concurrency, tokens, NETWORK, etc.).
|
||||
# This file is NOT tracked — keep secrets/host-specific config here.
|
||||
if [ -f "$HOME/.agent-queue.env" ]; then
|
||||
# shellcheck disable=SC1091
|
||||
. "$HOME/.agent-queue.env"
|
||||
fi
|
||||
|
||||
# Recommended default for a local monorepo overnight runner (see long-running-jobs
|
||||
# cheat sheet): codex runs in-repo so @bytelyst/* workspace links resolve locally.
|
||||
: "${AGENT_QUEUE_ENGINE:=codex}"
|
||||
export AGENT_QUEUE_ENGINE
|
||||
|
||||
echo "[agent-queue-boot] $(date '+%Y-%m-%d %H:%M:%S') starting run loop" \
|
||||
"(engine=$AGENT_QUEUE_ENGINE, max=${AGENT_QUEUE_MAX:-3})"
|
||||
|
||||
# Keep the Mac awake for the lifetime of the loop unless explicitly opted out.
|
||||
keep=""
|
||||
if [ "${AGENT_QUEUE_NO_CAFFEINATE:-0}" != "1" ] && command -v caffeinate >/dev/null 2>&1; then
|
||||
keep="caffeinate -dimsu"
|
||||
fi
|
||||
|
||||
# exec so the LaunchAgent tracks the real worker PID (clean KeepAlive restarts).
|
||||
# shellcheck disable=SC2086
|
||||
exec $keep "$SCRIPT_DIR/agent-queue.sh" run
|
||||
70
agent-queue/launchd/README.md
Normal file
70
agent-queue/launchd/README.md
Normal file
@ -0,0 +1,70 @@
|
||||
# Boot-persistence: agent-queue as a macOS LaunchAgent
|
||||
|
||||
Auto-start the `agent-queue` run loop on login and keep it alive across
|
||||
**reboot / crash / logout** — the one failure mode that `tmux` + `caffeinate`
|
||||
alone can't cover.
|
||||
|
||||
| Layer | Survives terminal close | Survives sleep | Survives reboot |
|
||||
| ----- | :---------------------: | :------------: | :-------------: |
|
||||
| plain shell | no | no | no |
|
||||
| `tmux` | yes | no | no |
|
||||
| `caffeinate` | n/a | yes | no |
|
||||
| **LaunchAgent (this)** | yes | yes (via caffeinate) | **yes** |
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bash launchd/install.sh # render plist, load, start now (RunAtLoad + KeepAlive)
|
||||
tail -f ~/Library/Logs/agent-queue/agent-queue.out.log
|
||||
```
|
||||
|
||||
It renders `~/Library/LaunchAgents/com.bytelyst.agent-queue.plist` from the
|
||||
resolved repo path (works on any clone) and bootstraps it into your GUI session.
|
||||
|
||||
## Use
|
||||
|
||||
The LaunchAgent runs `agent-queue-boot.sh`, which wraps `agent-queue run` in
|
||||
`caffeinate`. Just drop prompt `.md` files into `queue/inbox/` — they get picked
|
||||
up automatically, now or after the next reboot.
|
||||
|
||||
```bash
|
||||
aq add ~/jobs/phase3-overnight.md --engine codex # or drop the file in queue/inbox/
|
||||
aqs # status
|
||||
```
|
||||
|
||||
## Configure (no need to edit the plist)
|
||||
|
||||
Put overrides in `~/.agent-queue.env` (untracked — also the place for tokens):
|
||||
|
||||
```bash
|
||||
AGENT_QUEUE_ENGINE=codex # codex (recommended: local repo) | devin | claude
|
||||
AGENT_QUEUE_MAX=1 # concurrent jobs on this host (default 3)
|
||||
# AGENT_QUEUE_NO_CAFFEINATE=1 # allow the Mac to idle-sleep (NOT for overnight runs)
|
||||
# DEVIN_BIN=/custom/path/devin # if a CLI isn't on the default PATH
|
||||
```
|
||||
|
||||
## Stop / uninstall
|
||||
|
||||
```bash
|
||||
bash launchd/install.sh --uninstall # bootout + remove plist (queued jobs stay put)
|
||||
```
|
||||
|
||||
## Notes & gotchas
|
||||
|
||||
- **codex vs devin:** for a local monorepo overnight runner, **codex** is the
|
||||
default — it runs in-repo so `@bytelyst/*` workspace links resolve locally and
|
||||
logs/token-usage parsing already work. Use **devin** when you want a cloud
|
||||
sandbox doing the heavy lifting (and ACUs/network aren't a concern).
|
||||
- **Power:** caffeinate wraps the long-lived loop, so the Mac stays awake the
|
||||
whole time the LaunchAgent runs. That's intended for a dedicated runner. Set
|
||||
`AGENT_QUEUE_NO_CAFFEINATE=1` if you'd rather let it idle-sleep when no job is
|
||||
active. Keep it plugged in with the lid open for true overnight runs.
|
||||
- **PATH:** launchd starts processes with a minimal `PATH`. Both the plist
|
||||
(`EnvironmentVariables`) and the wrapper repair it, but if a CLI lives
|
||||
somewhere unusual, point at it explicitly via `~/.agent-queue.env`.
|
||||
- **Dangerous mode:** jobs run `--yolo` (auto-approve) by default. The safety net
|
||||
is the agent-queue lifecycle itself — jobs land in `review/` → `testing/` and
|
||||
**shipping is always a manual human gate**. Never let an unattended run touch
|
||||
`main`; push to a branch and open one PR.
|
||||
- **Auth:** cache `gh auth login` / git credentials and the agent CLI's auth
|
||||
before relying on it overnight, or the first `push` will block forever.
|
||||
105
agent-queue/launchd/install.sh
Executable file
105
agent-queue/launchd/install.sh
Executable file
@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# install.sh — install (or remove) the macOS LaunchAgent that auto-starts the
|
||||
# agent-queue run loop on login and keeps it alive across reboot/crash.
|
||||
#
|
||||
# bash launchd/install.sh # render plist, load, and start now
|
||||
# bash launchd/install.sh --uninstall # stop, unload, and remove the plist
|
||||
#
|
||||
# The plist is generated from the resolved repo path so it works on any clone.
|
||||
# Logs land in ~/Library/Logs/agent-queue/.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd -P)"
|
||||
AQ_DIR="$(cd -- "$SCRIPT_DIR/.." >/dev/null 2>&1 && pwd -P)"
|
||||
WRAPPER="$AQ_DIR/agent-queue-boot.sh"
|
||||
|
||||
LABEL="com.bytelyst.agent-queue"
|
||||
PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
|
||||
LOG_DIR="$HOME/Library/Logs/agent-queue"
|
||||
UID_NUM="$(id -u)"
|
||||
DOMAIN="gui/$UID_NUM"
|
||||
|
||||
if [ "$(uname -s)" != "Darwin" ]; then
|
||||
echo "install.sh: macOS only (LaunchAgents). On Linux use a systemd --user unit." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
uninstall() {
|
||||
echo "[launchd] booting out $LABEL ..."
|
||||
launchctl bootout "$DOMAIN/$LABEL" 2>/dev/null || true
|
||||
rm -f "$PLIST"
|
||||
echo "[launchd] removed $PLIST"
|
||||
echo "[launchd] (the run loop is stopped; queued jobs stay in queue/inbox/)"
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "--uninstall" ] || [ "${1:-}" = "-u" ]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[ -f "$WRAPPER" ] || { echo "install.sh: missing $WRAPPER" >&2; exit 1; }
|
||||
chmod +x "$WRAPPER" "$AQ_DIR/agent-queue.sh" 2>/dev/null || true
|
||||
mkdir -p "$HOME/Library/LaunchAgents" "$LOG_DIR"
|
||||
|
||||
echo "[launchd] writing $PLIST"
|
||||
cat > "$PLIST" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>$LABEL</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/bin/bash</string>
|
||||
<string>$WRAPPER</string>
|
||||
</array>
|
||||
|
||||
<!-- Start on login and restart if it ever exits non-zero (crash/reboot). -->
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<dict>
|
||||
<key>SuccessfulExit</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<!-- Guard against tight crash loops. -->
|
||||
<key>ThrottleInterval</key>
|
||||
<integer>30</integer>
|
||||
|
||||
<key>WorkingDirectory</key>
|
||||
<string>$AQ_DIR</string>
|
||||
|
||||
<key>StandardOutPath</key>
|
||||
<string>$LOG_DIR/agent-queue.out.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>$LOG_DIR/agent-queue.err.log</string>
|
||||
|
||||
<!-- launchd's PATH is minimal; the wrapper also repairs PATH defensively. -->
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||
<key>AGENT_QUEUE_ENGINE</key>
|
||||
<string>codex</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
# Reload cleanly (bootout first so a re-run picks up plist changes).
|
||||
launchctl bootout "$DOMAIN/$LABEL" 2>/dev/null || true
|
||||
launchctl bootstrap "$DOMAIN" "$PLIST"
|
||||
launchctl enable "$DOMAIN/$LABEL"
|
||||
launchctl kickstart -k "$DOMAIN/$LABEL"
|
||||
|
||||
echo "[launchd] installed + started: $LABEL"
|
||||
echo "[launchd] status : launchctl print $DOMAIN/$LABEL | sed -n '1,20p'"
|
||||
echo "[launchd] logs : tail -f $LOG_DIR/agent-queue.out.log"
|
||||
echo "[launchd] stop : bash $SCRIPT_DIR/install.sh --uninstall"
|
||||
echo
|
||||
echo "Drop prompt .md files into: $AQ_DIR/queue/inbox/"
|
||||
echo "Override engine/concurrency/secrets in ~/.agent-queue.env (e.g. AGENT_QUEUE_MAX=1)."
|
||||
Loading…
Reference in New Issue
Block a user