Wire the rounds timer to a pure-Python skip gate so claire-serve only wakes the orchestrator model when worker fleet state changed (not every tick): - web/rounds.py: fleet_fingerprint() over worker sessions (minus the orchestrator's own) + open tasks; should_skip_round() with heartbeat floor. - web/app.py: _rounds_loop tracks last fingerprint + consecutive skips. - excludes the orchestrator's own session/chat so a round's self-side-effects can't defeat the gate. Add scripts/release-fleet.sh (test -> deploy apricot+black -> restart plum services) and harden deploy-agent.sh's cosmetic status check against a SIGPIPE false-abort. 3 new discriminating tests; 349 pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
91 lines
4.1 KiB
Bash
Executable file
91 lines
4.1 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# release-fleet.sh — one command to release the current claire working tree to
|
|
# the WHOLE fleet and restart every service. Runs FROM plum (the source of
|
|
# truth + the only host with the launchd services).
|
|
#
|
|
# scripts/release-fleet.sh # test → deploy apricot+black → restart plum
|
|
# scripts/release-fleet.sh --no-test # skip the pre-deploy pytest gate
|
|
# scripts/release-fleet.sh --no-plum # leave plum's services running (only push workers)
|
|
# scripts/release-fleet.sh --hosts apricot # restrict the worker host list
|
|
# scripts/release-fleet.sh --dry-run # print the plan, change nothing
|
|
#
|
|
# What it does, in order:
|
|
# 1. (gate) run the test suite — abort the whole release if it fails.
|
|
# 2. workers (apricot, black): scripts/deploy-agent.sh <host>
|
|
# → rsync working tree + `uv pip install -e .` + restart claire-agent.service.
|
|
# 3. plum: `launchctl kickstart -k` claire-serve + claire-tray
|
|
# → editable install, so a restart is all that's needed to load new code.
|
|
#
|
|
# ⚠ Restarting plum's `com.lilith.claire-serve` briefly drops the web / API /
|
|
# MCP endpoint (~a few seconds). Anything mid-call against claire's MCP tools
|
|
# — INCLUDING the orchestrator itself — will blip until it comes back. Run it
|
|
# when you can tolerate that, or pass --no-plum and restart plum by hand.
|
|
#
|
|
# Requires (same as deploy-agent.sh): `remote-run` on PATH, ssh to the worker
|
|
# hosts, uv/python on the remotes, NTP-synced clocks.
|
|
set -euo pipefail
|
|
|
|
SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
HOSTS=(apricot black)
|
|
RUN_TESTS=1
|
|
RESTART_PLUM=1
|
|
DRY_RUN=0
|
|
PLUM_SERVICES=(com.lilith.claire-serve com.lilith.claire-tray)
|
|
|
|
say() { printf '\033[1;35m▸\033[0m %s\n' "$*"; }
|
|
warn() { printf '\033[1;33m⚠\033[0m %s\n' "$*" >&2; }
|
|
die() { printf '\033[1;31m✗\033[0m %s\n' "$*" >&2; exit 1; }
|
|
run() { if [ "$DRY_RUN" = 1 ]; then printf ' [dry-run] %s\n' "$*"; else eval "$@"; fi; }
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--no-test) RUN_TESTS=0; shift ;;
|
|
--no-plum) RESTART_PLUM=0; shift ;;
|
|
--dry-run) DRY_RUN=1; shift ;;
|
|
--hosts) shift; IFS=' ' read -r -a HOSTS <<< "${1:?--hosts needs a value}"; shift ;;
|
|
-h|--help) sed -n '2,30p' "$0"; exit 0 ;;
|
|
*) die "unknown arg: $1 (try --help)" ;;
|
|
esac
|
|
done
|
|
|
|
# --- 1. test gate ----------------------------------------------------------
|
|
if [ "$RUN_TESTS" = 1 ]; then
|
|
say "test gate: pytest (use --no-test to skip)"
|
|
# Run via the project venv (where pytest + dev deps live); fall back to uv's
|
|
# managed env. `uv run pytest` is deliberately NOT used — pytest is a dev-extra
|
|
# in .venv, not a uv tool, so that spawn fails on this repo.
|
|
if [ -x "$SRC/.venv/bin/python" ]; then
|
|
run "(cd '$SRC' && .venv/bin/python -m pytest -q)" || die "tests failed — release aborted"
|
|
elif command -v uv >/dev/null 2>&1; then
|
|
run "(cd '$SRC' && uv run python -m pytest -q)" || die "tests failed — release aborted"
|
|
else
|
|
die "no .venv/bin/python and no uv — cannot run the test gate (use --no-test to override)"
|
|
fi
|
|
else
|
|
warn "skipping test gate (--no-test)"
|
|
fi
|
|
|
|
# --- 2. worker hosts -------------------------------------------------------
|
|
for h in "${HOSTS[@]}"; do
|
|
say "deploy worker → $h"
|
|
run "'$SRC/scripts/deploy-agent.sh' '$h'" || die "deploy-agent.sh $h failed"
|
|
done
|
|
|
|
# --- 3. plum services ------------------------------------------------------
|
|
if [ "$RESTART_PLUM" = 1 ]; then
|
|
warn "restarting plum services ${PLUM_SERVICES[*]} — claire-serve restart blips the MCP/web endpoint"
|
|
uid="$(id -u)"
|
|
for svc in "${PLUM_SERVICES[@]}"; do
|
|
if launchctl print "gui/$uid/$svc" >/dev/null 2>&1; then
|
|
say "kickstart $svc"
|
|
run "launchctl kickstart -k 'gui/$uid/$svc'" || warn "kickstart $svc returned non-zero"
|
|
else
|
|
warn "$svc not loaded in gui/$uid — skipping (load its LaunchAgent first)"
|
|
fi
|
|
done
|
|
else
|
|
warn "leaving plum services running (--no-plum) — restart them by hand to load new code"
|
|
fi
|
|
|
|
if [ "$DRY_RUN" = 1 ]; then say "release plan printed (dry-run — nothing changed)."; else say "release complete."; fi
|