#!/usr/bin/env bash # deploy-r720-ws-rollout.sh — attended UPDATE of the live R720 agent-team # coordinator to the WS0–WS5 rollout (PRs #43–#46 + the activation wiring). # # This is an UPDATE, not a first-time provision: P1 is already deployed per # agent-team/DEPLOY-R720.md (repo rsynced to ~/orchestrator, venv at # agent-team/.venv, systemd unit agent-team-coordinator.service running). # Run this from the MAC, after the WS branches have merged to main. It rsyncs # the new code, installs the new deps, appends the new secrets if absent, syncs # the engineering handbook, restarts the coordinator, and smoke-tests. # # It is idempotent and FAILS LOUDLY. It changes a live box, so: # 1) SNAPSHOT FIRST (Hyper-V checkpoint of sh-secrev on the R720 host). # 2) It prompts before the restart. # # What goes LIVE after this (the safe, ungated seams): # * WS1 in-process multi-model invokers (bind_multi_invoker, already wired) # * WS5 handbook context_provider injected into the planner prompt # * WS2 Slack /new-task -> start a task (AUTHZ-01 owner allowlist gated) # The HTTP API (WS1) + the /delegate hook are OPTIONAL and started separately # (see step 6). The P3 dispatch/build-verify path stays INERT (gated). set -euo pipefail # ── Config (override via env) ──────────────────────────────────────────────── BOX="${BOX:-adam@10.10.60.120}" SSH_KEY="${SSH_KEY:-$HOME/.ssh/r720_seahaven}" REPO_LOCAL="${REPO_LOCAL:-$HOME/Documents/repositories/orchestrator}" HANDBOOK_LOCAL="${HANDBOOK_LOCAL:-$HOME/Documents/repositories/engineering-handbook}" # Where the handbook lands on the box; must match SEA_HAVEN_HANDBOOK_DIR below. HANDBOOK_REMOTE="${HANDBOOK_REMOTE:-/home/adam/.sea-haven/engineering-handbook}" SSH="ssh -i ${SSH_KEY} ${BOX}" say() { printf '\n\033[1;36m== %s\033[0m\n' "$*"; } confirm() { read -r -p "$1 [y/N] " a; [ "$a" = "y" ] || [ "$a" = "Y" ]; } say "Preflight" [ -f "${SSH_KEY}" ] || { echo "missing SSH key ${SSH_KEY}"; exit 1; } $SSH true || { echo "cannot reach ${BOX}"; exit 1; } echo "SNAPSHOT REMINDER: take a Hyper-V checkpoint of sh-secrev on the R720 host now." confirm "Snapshot taken and ready to update the LIVE coordinator?" || { echo "aborted"; exit 1; } say "1. rsync repo (Mac -> box; same excludes as the P1 runbook)" rsync -av --exclude .env --exclude .venv --exclude .git --exclude '__pycache__' \ "${REPO_LOCAL}/" "${BOX}:orchestrator/" say "2. rsync engineering handbook -> ${HANDBOOK_REMOTE} (WS5 context_provider source)" if [ -d "${HANDBOOK_LOCAL}" ]; then $SSH "mkdir -p ${HANDBOOK_REMOTE}" rsync -av --delete --exclude .git "${HANDBOOK_LOCAL}/" "${BOX}:${HANDBOOK_REMOTE}/" else echo "WARN: ${HANDBOOK_LOCAL} not found; context_provider will return '' (fail-safe). Skipping." fi say "3. Install venv deps from the pinned requirements.txt" # Install the FULL pinned set into the agent-team venv. This includes the # non-Claude model stack (langchain-anthropic/-openai/-google-genai/-community) # that the in-process invokers (WS1: GPT-4.1 review, Gemini scan, DeepSeek build) # import via models.py — WITHOUT these, models.py fails to import and the review # loop silently fail-closes to REQUEST_CHANGES (the non-Claude models never run). # Also brings fastapi/uvicorn (WS1 HTTP API). Leaves the venv-only deps that are # NOT in requirements.txt (claude-agent-sdk, slack_sdk, slack_bolt) untouched. $SSH 'cd ~/orchestrator/agent-team && . .venv/bin/activate && pip install --upgrade -r ~/orchestrator/requirements.txt' say "4. Append new secrets to ~/secrev.env if absent (mode 600, never committed)" # AGENT_TEAM_API_TOKEN: required only if you run the HTTP API / /delegate hook. # SEA_HAVEN_HANDBOOK_DIR: where load_handbook_conventions() reads from. $SSH "bash -s" <> ~/secrev.env if grep -q '^AGENT_TEAM_API_TOKEN=' ~/secrev.env; then echo 'AGENT_TEAM_API_TOKEN already set; leaving as-is.' else echo 'AGENT_TEAM_API_TOKEN NOT set. Add it now (generated on the Mac):' echo ' echo "AGENT_TEAM_API_TOKEN=" >> ~/secrev.env && chmod 600 ~/secrev.env' echo '(only needed for the HTTP API / auto-delegate hook; the coordinator runs without it.)' fi REMOTE say "5. Restart the coordinator daemon" confirm "Restart agent-team-coordinator.service now?" || { echo "skipped restart"; exit 0; } $SSH 'sudo systemctl restart agent-team-coordinator.service && sleep 2 && systemctl is-active agent-team-coordinator.service' $SSH 'journalctl -u agent-team-coordinator.service -n 30 --no-pager' say "6. (OPTIONAL) HTTP API + /delegate hook — start only if you want them" cat <<'NOTE' The coordinator now serves WS5 context + WS2 /new-task. The WS1 HTTP API is a SEPARATE process (api.serve(), 127.0.0.1:8765, bearer auth). To run it: - ensure AGENT_TEAM_API_TOKEN is set in ~/secrev.env - run: cd ~/orchestrator/agent-team && . .venv/bin/activate && \ python3 -c "from agent_team.api import serve; serve()" - (for persistence, add a second systemd unit; not auto-installed here.) Then set AGENT_TEAM_API_TOKEN + AGENT_TEAM_API_URL in the Mac Claude Code env to enable the /delegate hook (sea-haven-claude-plugin). NOTE say "7. SMOKE TESTS (manual)" cat <<'SMOKE' a) Coordinator up: systemctl is-active agent-team-coordinator.service -> active b) Handbook visible: cd ~/orchestrator/agent-team && . .venv/bin/activate && \ python3 -c "from agent_team.nodes.handbook import load_handbook_conventions as h; print(bool(h()))" -> True c) Slack /new-task: post "/new-task add a smoke-test file" in #agent-team as an allowlisted owner -> the bot replies with a clarifying question. d) (if API running) auth: curl -s -o /dev/null -w '%{http_code}' \ -H "Authorization: Bearer $AGENT_TEAM_API_TOKEN" http://127.0.0.1:8765/tasks -> 405 (GET not allowed = API up + authed) ROLLBACK: restore the pre-update Hyper-V checkpoint (one-command revert). SMOKE say "Done."