docs(integration): document WS0-WS5 components + WS-rollout deploy
This commit is contained in:
parent
0689d1696c
commit
2e5972c476
4 changed files with 128 additions and 1 deletions
|
|
@ -123,6 +123,10 @@ The `security-review/` subsystem is a high-recall, anti-complacency security gat
|
||||||
|
|
||||||
See `security-review/README.md` for full detail and `security-review/DEPLOY-R720.md` for the VM runbook.
|
See `security-review/README.md` for full detail and `security-review/DEPLOY-R720.md` for the VM runbook.
|
||||||
|
|
||||||
|
## agent-team (R720 durable SDLC pipeline)
|
||||||
|
|
||||||
|
The `agent-team/` subsystem is a separate, durable, human-gated SDLC pipeline (LangGraph + SQLite ledger) that runs as an always-on coordinator daemon on the same `sh-secrev` R720 VM. It is distinct from this stateless router: it persists tasks across restarts and runs INTAKE → CLARIFY → PLAN → REVIEW (build/verify is deploy-gated and inert). It reuses this orchestrator's `models.py` for its non-Claude invokers, and exposes an opt-in FastAPI HTTP API (loopback, bearer auth) plus a `/delegate` Claude Code plugin hook (`sea-haven-claude-plugin/`). See `agent-team/README.md` and `agent-team/DEPLOY-R720.md`.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1. Install dependencies: `pip install -r requirements.txt`
|
1. Install dependencies: `pip install -r requirements.txt`
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,51 @@ The unit runs `python3 run-team.py serve` from
|
||||||
secrets from `EnvironmentFile=/home/adam/secrev.env`. `Restart=on-failure` keeps
|
secrets from `EnvironmentFile=/home/adam/secrev.env`. `Restart=on-failure` keeps
|
||||||
it up across transient faults; `journalctl -u` is the live log.
|
it up across transient faults; `journalctl -u` is the live log.
|
||||||
|
|
||||||
|
## 4b. WS0–WS5 rollout — UPDATE an already-deployed box
|
||||||
|
|
||||||
|
The steps above (§1–4) are the **first-time** P1 provision. To bring an
|
||||||
|
already-deployed coordinator up to the WS0–WS5 rollout, use the attended update
|
||||||
|
script rather than re-running the manual steps:
|
||||||
|
|
||||||
|
```
|
||||||
|
# From the Mac, after the WS branches have merged to main, snapshot first:
|
||||||
|
agent-team/scripts/deploy-r720-ws-rollout.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
It is an UPDATE (not a provision): it snapshots-reminds, rsyncs the new code,
|
||||||
|
rsyncs the engineering handbook to the box, installs the new deps, appends the
|
||||||
|
new secrets if absent, restarts the coordinator, and smoke-tests. It is
|
||||||
|
idempotent and fails loudly. What goes **live** after it: WS1 in-process
|
||||||
|
multi-model invokers (`bind_multi_invoker`, already wired), the WS5 handbook
|
||||||
|
`context_provider` injected into the planner prompt, and the WS2 Slack
|
||||||
|
`/new-task` command (AUTHZ-01 owner-allowlist gated). The P3 dispatch/build-verify
|
||||||
|
path stays **inert** (gated behind the `agent-apply` GitHub Environment approval).
|
||||||
|
|
||||||
|
**New venv deps** (the coordinator does not need them; only the optional HTTP
|
||||||
|
API does) — now pinned in the root `requirements.txt`:
|
||||||
|
|
||||||
|
| pip dep | Why |
|
||||||
|
|---|---|
|
||||||
|
| `fastapi==0.136.1` | the WS1 HTTP API app (`agent_team/api.py`) |
|
||||||
|
| `uvicorn==0.46.0` | ASGI server for `api.serve()` |
|
||||||
|
|
||||||
|
**New env vars** — append to `~/secrev.env` (mode 600, never committed):
|
||||||
|
|
||||||
|
- `SEA_HAVEN_HANDBOOK_DIR` — where `load_handbook_conventions()` reads the
|
||||||
|
engineering handbook (the script syncs it to `/home/adam/.sea-haven/engineering-handbook`
|
||||||
|
by default; this var must match). Fail-safe: if the dir is missing the
|
||||||
|
`context_provider` returns `""` and the planner runs without handbook context.
|
||||||
|
- `AGENT_TEAM_API_TOKEN` — bearer token for the HTTP API / `/delegate` hook
|
||||||
|
**only**. Not needed by the coordinator daemon itself. The HTTP API refuses to
|
||||||
|
start if this is unset/empty.
|
||||||
|
|
||||||
|
**The HTTP API is a separate, opt-in process** — it is **not** started by the
|
||||||
|
coordinator daemon. Run it explicitly (`api.serve()`, binds `127.0.0.1:8765`,
|
||||||
|
bearer auth) only if you want the `/delegate` Claude Code hook or the
|
||||||
|
`POST /tasks` / `GET /tasks/{thread_id}` / `POST /orchestrator/invoke` endpoints.
|
||||||
|
The `/docs` + `/openapi` routes are disabled and it binds loopback by design (do
|
||||||
|
not change to `0.0.0.0`). See the deploy script's step 6 for how to start it.
|
||||||
|
|
||||||
## 5. P1 live exit-criteria demo (§3.3.1)
|
## 5. P1 live exit-criteria demo (§3.3.1)
|
||||||
|
|
||||||
Demonstrate all four once the service is live. Map each to the operator commands
|
Demonstrate all four once the service is live. Map each to the operator commands
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,10 @@ agent-team/
|
||||||
graph.py # LangGraph wiring: P1 (intake→clarify→plan) + opt-in P2
|
graph.py # LangGraph wiring: P1 (intake→clarify→plan) + opt-in P2
|
||||||
# review loop + opt-in P3 build/verify subgraph
|
# review loop + opt-in P3 build/verify subgraph
|
||||||
invoker.py # §3.1 real Claude path (subscription-OAuth / API / Bedrock)
|
invoker.py # §3.1 real Claude path (subscription-OAuth / API / Bedrock)
|
||||||
|
invoker_multi.py # WS1 in-process non-Claude invokers (GPT-4.1 / DeepSeek /
|
||||||
|
# Gemini via the orchestrator's models.py); bind_multi_invoker()
|
||||||
|
api.py # WS1 FastAPI HTTP API (bearer auth, 127.0.0.1:8765) — SEPARATE
|
||||||
|
# opt-in process (api.serve()), NOT started by the coordinator
|
||||||
billing.py # §3.1 claude_invoke billing-mode seam
|
billing.py # §3.1 claude_invoke billing-mode seam
|
||||||
ci_gate.py # §3.3.2 pure-code authenticated-Checks PASS/FAIL gate
|
ci_gate.py # §3.3.2 pure-code authenticated-Checks PASS/FAIL gate
|
||||||
task_model.py / state_store.py
|
task_model.py / state_store.py
|
||||||
|
|
@ -52,17 +56,26 @@ agent-team/
|
||||||
builders.py + builders_llm.py # candidate diff (DeepSeek) — INERT, proposes only
|
builders.py + builders_llm.py # candidate diff (DeepSeek) — INERT, proposes only
|
||||||
verifier.py + verifier_llm.py # ci_gate sole PASS authority; LLM = fix-proposer
|
verifier.py + verifier_llm.py # ci_gate sole PASS authority; LLM = fix-proposer
|
||||||
build_verify_subgraph.py # P3 BUILD→VERIFY topology (opt-in)
|
build_verify_subgraph.py # P3 BUILD→VERIFY topology (opt-in)
|
||||||
|
handbook.py # WS5 load_handbook_conventions (handbook seam,
|
||||||
|
# fail-safe → "" if dir missing); planner context
|
||||||
|
dispatch_invoker.py # WS3 auto-dispatch node — INERT (NOT wired live)
|
||||||
transport/ # one adapter contract + a live impl per channel
|
transport/ # one adapter contract + a live impl per channel
|
||||||
base.py # Transport ABC + QuestionSet / NormalizedAnswer
|
base.py # Transport ABC + QuestionSet / NormalizedAnswer
|
||||||
slack_adapter.py + slack_live.py + slack_listener.py # Block Kit + Socket Mode
|
slack_adapter.py + slack_live.py + slack_listener.py # Block Kit + Socket Mode + /new-task
|
||||||
github_adapter.py + github_live.py + github_intake.py # issue-comment + issue intake
|
github_adapter.py + github_live.py + github_intake.py # issue-comment + issue intake
|
||||||
claude_code_adapter.py + claude_code_live.py # file-drop responder
|
claude_code_adapter.py + claude_code_live.py # file-drop responder
|
||||||
|
scripts/ # deploy-r720-ws-rollout.sh — attended WS0–WS5 UPDATE of the box
|
||||||
ci/ # §3.3.2 split-job CI apply/verify workflow (DEPLOY-GATED)
|
ci/ # §3.3.2 split-job CI apply/verify workflow (DEPLOY-GATED)
|
||||||
systemd/ # agent-team-coordinator.service (not installed)
|
systemd/ # agent-team-coordinator.service (not installed)
|
||||||
DEPLOY-R720.md # provisioning runbook (snapshot-first, rsync, tokens, demo)
|
DEPLOY-R720.md # provisioning runbook (snapshot-first, rsync, tokens, demo)
|
||||||
tests/ # pytest, one module per source module + sim harness
|
tests/ # pytest, one module per source module + sim harness
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The Claude Code plugin lives in a sibling top-level dir, `../sea-haven-claude-plugin/`
|
||||||
|
(CLAUDE.md, settings.template.json, `hooks/user_prompt_submit.py`): a
|
||||||
|
`UserPromptSubmit` hook that forwards `/delegate <task>` prompts from Claude Code
|
||||||
|
to the HTTP API's `POST /tasks` (env `AGENT_TEAM_API_URL` / `AGENT_TEAM_API_TOKEN`).
|
||||||
|
|
||||||
The top directory is kebab-case (`agent-team/`); the importable package is
|
The top directory is kebab-case (`agent-team/`); the importable package is
|
||||||
snake_case (`agent_team/`), per the engineering handbook.
|
snake_case (`agent_team/`), per the engineering handbook.
|
||||||
|
|
||||||
|
|
@ -85,6 +98,22 @@ snake_case (`agent_team/`), per the engineering handbook.
|
||||||
GPT-4.1 (review) and DeepSeek (builders) route through the local orchestrator
|
GPT-4.1 (review) and DeepSeek (builders) route through the local orchestrator
|
||||||
`run.py`. Switching Claude billing is a config flip.
|
`run.py`. Switching Claude billing is a config flip.
|
||||||
|
|
||||||
|
## WS0–WS5 rollout glossary
|
||||||
|
|
||||||
|
The "WS-rollout" (workstreams 0–5) layered HTTP/integration surfaces onto the
|
||||||
|
P1–P4 pipeline. What is **live** vs **inert** after the rollout:
|
||||||
|
|
||||||
|
| WS | What it adds | Live? |
|
||||||
|
|---|---|---|
|
||||||
|
| WS1 | `invoker_multi.py` (in-process GPT-4.1 / DeepSeek / Gemini via the orchestrator's `models.py`) + `api.py` (FastAPI HTTP API, bearer auth via `AGENT_TEAM_API_TOKEN`, binds `127.0.0.1:8765`, `/docs`+`/openapi` disabled, concurrency-capped) | `bind_multi_invoker()` wired in `run-team.py` `_cmd_serve` (LIVE); the **HTTP API is a separate opt-in process** (`api.serve()`), NOT started by the coordinator |
|
||||||
|
| WS5 | `nodes/handbook.py` `load_handbook_conventions` (reads `SEA_HAVEN_HANDBOOK_DIR` or `~/.sea-haven/engineering-handbook`, fail-safe → `""`); `retriever.py` `save_memory` writes to a `_box-drafts/` review queue | LIVE — the planner prompt receives the handbook via the `context_provider` seam in `run-team.py` `_build_coordinator` |
|
||||||
|
| WS2/WS0/WS4 | Slack `/new-task` slash command (AUTHZ-01 owner-allowlist gated) → `Coordinator.set_new_task_callback`; the `sea-haven-claude-plugin/` (CLAUDE.md, settings, `/delegate` `UserPromptSubmit` hook) | LIVE (`/new-task` wired in `serve`); the plugin/HTTP-API path is opt-in |
|
||||||
|
| WS3 | `nodes/dispatch_invoker.py` (auto-dispatch LangGraph node) + graph/coordinator wiring | **INERT — NOT wired live.** The `agent-apply` GitHub Environment human-approval gate is KEPT; the P3 dispatch/build-verify path stays inert pending per-task `run_id` plumbing + a CI-boundary security re-review |
|
||||||
|
|
||||||
|
The HTTP API endpoints: `POST /tasks` (start a task), `GET /tasks/{thread_id}`
|
||||||
|
(status), `POST /orchestrator/invoke` (one-shot model invoke). See
|
||||||
|
`DEPLOY-R720.md` for the WS-rollout deploy (`scripts/deploy-r720-ws-rollout.sh`).
|
||||||
|
|
||||||
## Running the tests
|
## Running the tests
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,55 @@ journalctl -u agent-team-coordinator.service -e | grep -i "inbound Slack listene
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Incident 5b — WS0–WS5 surfaces (HTTP API, /new-task, handbook context)
|
||||||
|
|
||||||
|
These were added by the WS-rollout (see `agent-team/DEPLOY-R720.md` §4b). They
|
||||||
|
layer onto the coordinator; none of them should take down the maintenance loop.
|
||||||
|
|
||||||
|
- **HTTP API down / unreachable** — the WS1 FastAPI app (`agent_team/api.py`) is
|
||||||
|
a **separate, opt-in process** (`api.serve()`, `127.0.0.1:8765`, bearer auth),
|
||||||
|
**not** started by the coordinator daemon. If `/delegate` from Claude Code or
|
||||||
|
`POST /tasks` over HTTP stops working, the coordinator itself is unaffected —
|
||||||
|
check the API process separately:
|
||||||
|
```bash
|
||||||
|
curl -sS -o /dev/null -w '%{http_code}\n' \
|
||||||
|
-H "Authorization: Bearer $AGENT_TEAM_API_TOKEN" http://127.0.0.1:8765/tasks
|
||||||
|
# 405 = API up + authed (GET not allowed on /tasks); 000 = process down;
|
||||||
|
# 401 = AGENT_TEAM_API_TOKEN mismatch (client vs ~/secrev.env).
|
||||||
|
```
|
||||||
|
The API refuses to start if `AGENT_TEAM_API_TOKEN` is unset/empty (logs a
|
||||||
|
`RuntimeError`). Fix the token, restart the API process. Tasks already in the
|
||||||
|
ledger are unaffected — the API is only an *intake/invoke* front door; answer
|
||||||
|
via Slack or the CLI as usual.
|
||||||
|
|
||||||
|
- **`/new-task` Slack command not responding** — the WS2 slash command is
|
||||||
|
AUTHZ-01 owner-allowlist gated and routes through the same Socket Mode listener
|
||||||
|
as answers. If it silently does nothing, it is almost always the owner
|
||||||
|
allowlist (same failure mode as Incident 3's live-Slack path):
|
||||||
|
```bash
|
||||||
|
journalctl -u agent-team-coordinator.service -e | grep -iE "new-task|owner|unauthorized"
|
||||||
|
# unauthorized sender / unconfigured allowlist -> fix AGENT_TEAM_SLACK_OWNER_IDS
|
||||||
|
```
|
||||||
|
Fallback: start the task from the CLI (`run-team.py start --task "..."`) or the
|
||||||
|
HTTP API. If the listener itself is down, see Incident 5 (inbound listener).
|
||||||
|
|
||||||
|
- **Handbook dir missing → planner runs without handbook context** — the WS5
|
||||||
|
`context_provider` (`load_handbook_conventions`) is **fail-safe**: if
|
||||||
|
`SEA_HAVEN_HANDBOOK_DIR` (or `~/.sea-haven/engineering-handbook`) is missing or
|
||||||
|
unreadable it returns `""` and the planner runs normally, just without handbook
|
||||||
|
conventions injected. This is **degraded, not broken** — no park, no alarm.
|
||||||
|
Confirm and restore:
|
||||||
|
```bash
|
||||||
|
grep '^SEA_HAVEN_HANDBOOK_DIR=' ~/secrev.env
|
||||||
|
ls "$(grep '^SEA_HAVEN_HANDBOOK_DIR=' ~/secrev.env | cut -d= -f2)" # dir present + populated?
|
||||||
|
```
|
||||||
|
Re-sync the handbook (the deploy script does this) and restart the daemon so
|
||||||
|
the planner picks it back up. The WS3 dispatch node is **inert** (gated) and
|
||||||
|
should never appear in pipeline activity; if it does, treat as an unexpected
|
||||||
|
state and escalate.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Incident 6 — COMPLACENCY / COVERAGE alarms (Plane-1 checkers)
|
## Incident 6 — COMPLACENCY / COVERAGE alarms (Plane-1 checkers)
|
||||||
|
|
||||||
These come from the nightly checker run, not the coordinator daemon (design §6.4,
|
These come from the nightly checker run, not the coordinator daemon (design §6.4,
|
||||||
|
|
|
||||||
Reference in a new issue