188 lines
9.5 KiB
Markdown
188 lines
9.5 KiB
Markdown
|
|
# DEPLOY-AUDIT — `agent-team/DEPLOY-R720.md` + systemd unit vs. actual code
|
||
|
|
|
||
|
|
Cross-check of the drafted deploy doc (`agent-team/DEPLOY-R720.md`) and the
|
||
|
|
systemd unit (`agent-team/systemd/agent-team-coordinator.service`) against the
|
||
|
|
**actual current code** in `agent-team/agent_team/**` and `agent-team/run-team.py`.
|
||
|
|
|
||
|
|
Each finding: **location → claimed → actual → fix**. Severity: 🔴 blocker /
|
||
|
|
🟠 should-fix / 🟡 nit. Items confirmed clean are stated explicitly.
|
||
|
|
|
||
|
|
> **Status (deploy-readiness PR):** the three deploy-correctness bugs that
|
||
|
|
> blocked the live provisioning session — **D-1, D-2, D-7** — are **RESOLVED** in
|
||
|
|
> this PR (`feature/agent-team-deploy-readiness`). The remaining items
|
||
|
|
> (**D-4 / D-5** dependency pinning, the **operator-CLI divergence**) are kept
|
||
|
|
> below as **provisioning notes** — they do not block the coordinator deploy.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ D-1 — `serve` now starts the Slack inbound listener — RESOLVED (this PR)
|
||
|
|
|
||
|
|
- **Location:** `agent_team/coordinator.py` (`Coordinator.serve` +
|
||
|
|
`_maybe_start_slack_listener` / `_slack_listener_enabled` / `_stop_slack_listener`
|
||
|
|
/ `default_slack_listener_factory`); `agent_team/transport/slack_listener.py`
|
||
|
|
(`SlackListener.serve` + new `close`).
|
||
|
|
- **Was:** `Coordinator.serve()` did only `bind_subscription_invoker()`,
|
||
|
|
`setup()`, `recover()`, then an infinite tick/sleep loop — it never constructed
|
||
|
|
or started `SlackListener`, so a deployed daemon posted clarifier questions and
|
||
|
|
expired them on deadline but could **not hear Slack answers**.
|
||
|
|
- **Now:** `serve()` starts the `SlackListener` on a background **daemon thread**,
|
||
|
|
concurrently with the tick/drain loop, **when** the live transport is a
|
||
|
|
`SlackTransport` AND `SLACK_APP_TOKEN` is set. It shares the coordinator's own
|
||
|
|
transport, ledger `db_path`, and `resume_queue` put; on shutdown it calls the
|
||
|
|
listener's new `close()` and joins the thread in a `finally`. When Slack is not
|
||
|
|
the transport or the app token is absent, no listener starts and `serve` behaves
|
||
|
|
exactly as before — **Slack is never made mandatory**. The AUTHZ-01 owner
|
||
|
|
allowlist + the open-status compare-and-set are untouched (still fail closed on
|
||
|
|
an empty `AGENT_TEAM_SLACK_OWNER_IDS`).
|
||
|
|
- **Tests:** `tests/test_coordinator.py` — start-when-Slack+app-token, no-start
|
||
|
|
without the token, no-start when the transport is not Slack, idempotent start,
|
||
|
|
clean shutdown, serve start/stop around the loop; `tests/test_slack_listener.py`
|
||
|
|
— `close()` no-op + handler teardown.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ D-2 — coordinator unit loads `~/orchestrator/.env` — RESOLVED (this PR)
|
||
|
|
|
||
|
|
- **Location:** `agent-team/systemd/agent-team-coordinator.service`;
|
||
|
|
`run-team.py:_build_coordinator` (always wires `default_review_wiring`);
|
||
|
|
`coordinator.py:default_review_wiring` → `review_loop_llm.default_plan_reviewer`
|
||
|
|
(shells the local orchestrator `run.py` → `cross_reviewer` GPT-4.1).
|
||
|
|
- **Was:** the unit loaded only `~/secrev.env`. `run-team.py serve` builds the
|
||
|
|
coordinator with the P2 review loop wired, and once a task reaches REVIEW the
|
||
|
|
reviewer shells the orchestrator `run.py`, whose GPT-4.1 call reads the
|
||
|
|
non-Claude provider key from `~/orchestrator/.env`. The review path would fail
|
||
|
|
to authenticate.
|
||
|
|
- **Now:** the unit adds `EnvironmentFile=-/home/adam/orchestrator/.env`
|
||
|
|
(optional `-`, mirroring the `sea-haven-secrev` unit). Does not affect the P1
|
||
|
|
demo (P1 stops at PLAN before REVIEW); closes the latent P2 break.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🟠 D-4 — pip install list omits `requests` (provisioning note)
|
||
|
|
|
||
|
|
- **Location:** `agent_team/transport/github_live.py` / `github_intake.py`
|
||
|
|
(`import requests`).
|
||
|
|
- **Actual:** the GitHub transport + intake require `requests`; the Slack-first
|
||
|
|
path does not hit it, but any `--transport github` / `intake-github` use fails
|
||
|
|
with a clear RuntimeError without it.
|
||
|
|
- **Fix:** PROVISIONING-RUNBOOK Step 4 installs `requests` into the venv.
|
||
|
|
`requests` is also absent from `requirements.txt` (see D-5).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🟠 D-5 — agent-team runtime deps are not pinned in `requirements.txt` (provisioning note)
|
||
|
|
|
||
|
|
- **Location:** `requirements.txt` (repo root).
|
||
|
|
- **Actual:** `requirements.txt` pins `langgraph==1.1.10` and
|
||
|
|
`langgraph-checkpoint-sqlite==3.1.0`, but the agent-team runtime deps
|
||
|
|
`claude-agent-sdk`, `slack_sdk`, `slack_bolt`, `requests` (and `anthropic` for
|
||
|
|
api mode) are **not in `requirements.txt` at all** — they are installed ad-hoc
|
||
|
|
into the agent-team venv by the runbook. There is no pinned, reproducible source
|
||
|
|
of truth for the box's runtime set.
|
||
|
|
- **Fix (deferred):** add an `agent-team/requirements.txt` (or extras group)
|
||
|
|
pinning these, version-matched to the root `requirements.txt` langgraph pin.
|
||
|
|
Until then, PROVISIONING-RUNBOOK Step 4 pins `langgraph==1.1.10` /
|
||
|
|
`langgraph-checkpoint-sqlite==3.1.0` explicitly so the unpinned `pip install`
|
||
|
|
cannot pull a newer, untested major. **Do NOT modify `requirements.txt` or the
|
||
|
|
checkers in this PR** (out of scope).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ D-6 — `slack_bolt` is now exercised by the daemon — RESOLVED (consequence of D-1)
|
||
|
|
|
||
|
|
- **Location:** `slack_listener.py:serve` (the only `slack_bolt` import).
|
||
|
|
- **Now:** with D-1 fixed, `Coordinator.serve()` starts `SlackListener.serve()`,
|
||
|
|
which imports + uses `slack_bolt` for the Socket Mode handler. The dep is right
|
||
|
|
and now actually exercised on the live Slack path.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ D-SLACKVAR (clean) — `SLACK_CHANNEL_ID` matches
|
||
|
|
|
||
|
|
- `run-team.py:_build_transport` reads exactly `os.environ.get("SLACK_CHANNEL_ID")`.
|
||
|
|
The runbook, the unit comment, and the code all use `SLACK_CHANNEL_ID` (not
|
||
|
|
`SLACK_CHANNEL`). **CLEAN.**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ D-ENV-SLACKBOT / OAUTH / OWNERS / APPTOKEN (clean) — names match
|
||
|
|
|
||
|
|
- **`SLACK_BOT_TOKEN`** ↔ `slack_live.py` + `slack_listener` env read. **CLEAN.**
|
||
|
|
- **`CLAUDE_CODE_OAUTH_TOKEN`** ↔ `invoker.py`. **CLEAN.**
|
||
|
|
- **`AGENT_TEAM_SLACK_OWNER_IDS`** ↔ `slack_listener.py` (name + fail-closed
|
||
|
|
semantics). **CLEAN** — now read by the running daemon (D-1 fixed).
|
||
|
|
- **`SLACK_APP_TOKEN`** — now read in two places: `coordinator._slack_listener_enabled`
|
||
|
|
gates the listener on its presence, and `default_slack_listener_factory` /
|
||
|
|
`SlackListener.serve` source it to open the socket. **CLEAN** (read site exists
|
||
|
|
now that D-1 is fixed).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ D-7 — `ExecStart` uses the venv interpreter — RESOLVED (this PR)
|
||
|
|
|
||
|
|
- **Location:** `agent-team/systemd/agent-team-coordinator.service` ExecStart.
|
||
|
|
- **Was:** `ExecStart=/usr/bin/env python3 run-team.py serve` resolved the
|
||
|
|
**system** interpreter under systemd's PATH — not the venv where the deps were
|
||
|
|
installed, so the daemon would fail at import.
|
||
|
|
- **Now:** `ExecStart=/home/adam/orchestrator/agent-team/.venv/bin/python run-team.py serve`
|
||
|
|
(matches the runbook venv path + `WorkingDirectory`).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ D-SUBCMD (mostly clean) — run-team.py subcommands referenced exist
|
||
|
|
|
||
|
|
Cross-checked every `run-team.py <sub>` the deploy doc + demo name against
|
||
|
|
`run-team.py:build_parser`: `init-db`, `serve`, `list` (+ `--all` / `--parked`),
|
||
|
|
`show`, `expire`, `answer`, `redeliver`, `supersede`, `force-resume`, `start`,
|
||
|
|
`intake-github`, `intake-checker`, `fix` — all exist. No invented verbs.
|
||
|
|
|
||
|
|
> ### Operator-CLI divergence (provisioning note)
|
||
|
|
>
|
||
|
|
> Two operator CLIs exist with **different verb names**:
|
||
|
|
>
|
||
|
|
> - `run-team.py` (the entry CLI): `init-db, list, show, redeliver, expire,
|
||
|
|
> answer, supersede, force-resume, start, serve, intake-github, intake-checker,
|
||
|
|
> fix`. Has `show` and `--parked`; `--db` / `--audit-log` default sensibly.
|
||
|
|
> - `agent_team/operator_cli.py`: `list, redeliver, force-expire,
|
||
|
|
> answer-on-behalf, force-resume` — **no `show`**, `--db` / `--audit-log` are
|
||
|
|
> **required**, and its `force-resume` **supersedes** (unlike `run-team.py`'s,
|
||
|
|
> which reopens an expired row and never supersedes).
|
||
|
|
>
|
||
|
|
> **Use `run-team.py` for provisioning + the demo + incident recovery.** The
|
||
|
|
> docs reference only `run-team.py`. Reconciling the two CLIs is a follow-up.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ D-PYTHONPKG (clean) — package import bootstrap is correct
|
||
|
|
|
||
|
|
`run-team.py` inserts its own dir into `sys.path` so the hyphenated script
|
||
|
|
imports the `agent_team` package without an editable install. **CLEAN.**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ D-HARDENING (clean, and matches the locked decision)
|
||
|
|
|
||
|
|
- Unit: `NoNewPrivileges=true`, `ProtectSystem=full`, `ProtectHome=read-only`,
|
||
|
|
`ReadWritePaths=/home/adam/orchestrator/agent-team/state`. **Retained unchanged**
|
||
|
|
in this PR (locked decision — do not revert to secrev parity).
|
||
|
|
- The `ReadWritePaths` carve-out matches the ledger + audit-log location
|
||
|
|
(`state/agent_team.sqlite`, `state/audit.log.jsonl`). **CLEAN.**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Summary table
|
||
|
|
|
||
|
|
| ID | Sev | Status | One-line |
|
||
|
|
|---|---|---|---|
|
||
|
|
| D-1 | 🔴 | ✅ RESOLVED (PR) | `serve` starts `SlackListener` (Slack + app-token gated; Slack stays optional) |
|
||
|
|
| D-2 | 🔴 | ✅ RESOLVED (PR) | unit loads `~/orchestrator/.env` for the P2 GPT-4.1 review provider key |
|
||
|
|
| D-7 | 🟡 | ✅ RESOLVED (PR) | `ExecStart` points at the agent-team venv interpreter |
|
||
|
|
| D-6 | 🟡 | ✅ RESOLVED | `slack_bolt` now exercised by the daemon (consequence of D-1) |
|
||
|
|
| D-4 | 🟠 | NOTE | pip list omits `requests` — runbook Step 4 installs it |
|
||
|
|
| D-5 | 🟠 | NOTE | agent-team runtime deps not pinned in `requirements.txt` — runbook pins langgraph |
|
||
|
|
| operator-CLI | — | NOTE | `run-team.py` vs `operator_cli.py` divergent verbs — use `run-team.py` |
|
||
|
|
| D-SLACKVAR | ✅ | CLEAN | `SLACK_CHANNEL_ID` matches everywhere |
|
||
|
|
| D-ENV-* | ✅ | CLEAN | bot/oauth/owner/app-token env names match; all read sites now exist |
|
||
|
|
| D-SUBCMD | ✅ | CLEAN | every `run-team.py` verb/flag the docs cite exists |
|
||
|
|
| D-HARDENING | ✅ | CLEAN | unit hardening retained unchanged (locked decision) |
|