# agent-team-coordinator.service - R720 Plane-2 coordinator daemon (sh-secrev VM, user adam). # # Long-running coordinator for the agent-team SDLC pipeline. Unlike the # sea-haven-secrev sweep (a oneshot driven by a timer), this is an always-on # service: it serves the LangGraph coordinator, the durable pending_questions # ledger, and the Slack Socket Mode inbound listener that answers clarifier # questions. ExecStart runs the `serve` subcommand of the operator CLI. # # Install (on the VM, as root): # sudo cp agent-team-coordinator.service /etc/systemd/system/ # sudo systemctl daemon-reload # sudo systemctl enable --now agent-team-coordinator.service # systemctl status agent-team-coordinator.service # journalctl -u agent-team-coordinator.service -e -f # # Secrets come from the EnvironmentFile(s) (leading '-' = optional, no failure if # absent). Two files are loaded, mirroring the sea-haven-secrev unit: # # ~/secrev.env (mode 600, NOT in git) - the agent-team runtime keys: # CLAUDE_CODE_OAUTH_TOKEN -> subscription OAuth (from `claude setup-token`). # A raw ANTHROPIC_API_KEY must NOT be set on this # box; it would silently win and meter to API # rates. The billing seam pops it defensively. # SLACK_BOT_TOKEN -> xoxb- bot token (chat:write) - posts questions. # SLACK_APP_TOKEN -> xapp- app-level token (connections:write) - # REQUIRED for Socket Mode; opens the inbound # WebSocket that receives answers. Without it the # coordinator can post but never hear replies. # serve() starts the inbound SlackListener only when # the transport is live Slack AND this token is set. # SLACK_CHANNEL_ID -> target channel for clarifier questions. # AGENT_TEAM_SLACK_OWNER_IDS -> comma-separated authorized answerer ids # (AUTHZ-01). The listener FAILS CLOSED if unset. # # --- P3 build->dispatch->verify wiring (read by the LIVE serve() path) --- # The bound P3 wiring is the serve() default; if any of the three below (plus a # CI-read token) is unset, serve() degrades to the INERT P3 path (one WARNING + # a #agent-team notice) instead of crash-looping (Decision 5). All are read at # graph-build / dispatch time from this file's environment: # AGENT_TEAM_REPO_OWNER -> dispatch target owner. Fixed at factory time, # never read from pipeline state, so model output # cannot redirect the dispatch/verify target. # AGENT_TEAM_REPO_NAME -> dispatch target repo (same fail-closed binding). # AGENT_TEAM_BASE_BRANCH -> PR base branch (optional; default "main"). # AGENT_TEAM_CI_READ_TOKEN -> the READ-ONLY CI-result token. The verifier's # authenticated conclusion read uses it (falls back # to GITHUB_TOKEN). This is read-only by contract: # NO pull-requests:write / contents:write token, # and NO AGENT_APPLY_APP_ID / _PRIVATE_KEY, may live # in this file. The apply path mints its write token # INSIDE the CI runner from Actions secrets; the box # holds no standing write credential. The invariant # is asserted by scripts/assert_no_write_token.py # (the A2 audit) at provisioning + in CI. # # ~/orchestrator/.env (mode 600, NOT in git) - the P2 review-loop provider key: # The production serve() wires the GPT-4.1 cross-review loop, which shells the # local orchestrator run.py -> cross_reviewer once a task reaches REVIEW. That # sub-process needs the non-Claude provider key from ~/orchestrator/.env (same # file the sea-haven-secrev unit loads). Optional ('-') so the daemon still # starts if it is absent; the review path then fails loudly only at REVIEW. [Unit] Description=Sea Haven agent-team Plane-2 coordinator daemon After=network-online.target Wants=network-online.target [Service] Type=simple User=adam WorkingDirectory=/home/adam/orchestrator/agent-team EnvironmentFile=-/home/adam/secrev.env EnvironmentFile=-/home/adam/orchestrator/.env # Use the agent-team venv interpreter (where the runtime deps are installed by # the DEPLOY-R720 / PROVISIONING-RUNBOOK step), NOT the bare system python3 that # `/usr/bin/env python3` would resolve under systemd's PATH (D-7). ExecStart=/home/adam/orchestrator/agent-team/.venv/bin/python run-team.py serve Restart=on-failure RestartSec=5 # Hardening - matches the level the sea-haven-secrev unit relies on, scoped for a # long-running daemon that must READ ~/secrev.env and WRITE the local ledger. NoNewPrivileges=true ProtectSystem=full # ProtectHome cannot be `true`: the daemon reads /home/adam/secrev.env and writes # the ledger under the working dir. read-only home + an explicit RW carve-out for # the state/ dir keeps the rest of $HOME unreadable/unwritable to the service. ProtectHome=read-only ReadWritePaths=/home/adam/orchestrator/agent-team/state Nice=10 [Install] WantedBy=multi-user.target