# agent-team Slack app Dedicated Slack app backing the Plane-2 clarifier human-gate (Socket Mode). Kept separate from the webhook-only **Tech Notifications** app (`A0ARYQZU3KJ`) that the nightly secrev sweep uses, to isolate the two-way bot's trust surface. | Field | Value | |---|---| | App name | Sea Haven agent-team | | App ID | `A0BCC7TTU66` | | Bot | `agent-team` (handle `@agentteam2`) · user id `U0BCFSJ7SUC` | | Scope | **Workspace-level** app, installed to **Sea Haven Industries** (`T0A46CP6QR3`) | | Manifest | [`agent-team-manifest.json`](./agent-team-manifest.json) (source of truth) | | Settings | https://api.slack.com/apps/A0BCC7TTU66 | > ## ⚠️ Must be a WORKSPACE-LEVEL app (hard lesson, 2026-06-22) > Socket Mode event delivery only works for **workspace-level** apps. An > **org-owned** app (one created via the Enterprise **org/config token** / > `apps.manifest.create`, even when workspace-granted with > `--org-workspace-grant`) **connects the socket but receives ZERO workspace > Events API events** — outbound `chat.postMessage` works, but inbound > `message`/`app_mention` are never delivered, so the clarifier never hears > answers. The first build hit exactly this with the now-deleted org app > `A0BC7AT8NUD`. **Create this app from the dashboard** (api.slack.com/apps → > Create New App → From manifest) and **pick the *workspace* "Sea Haven > Industries" as the dev workspace**, NOT the Enterprise org. Then a normal > *Install to Workspace* works (no org-grant dance). Do **not** recreate it via > the org config token. ## Why these scopes (verified against the code) `agent_team/transport/slack_listener.py` subscribes over Socket Mode to `message`, `app_mention`, and Block Kit `block_actions`; `slack_live.py` posts questions via `chat.postMessage`. Inbound message/app_mention arrive as the Events API envelope `{"type":"event_callback","event":{...}}` and a free-text thread reply is matched to its question by `thread_ts == channel_ref` (see `find_open_question_by_channel_ref`). | Capability | Scope / setting | Why | |---|---|---| | Post clarifier questions | `chat:write` | `slack_live.py` `chat.postMessage` | | Hear thread replies | `channels:history` / `groups:history` + `message.channels`/`message.groups` | `@app.event("message")` | | Hear DM replies | `im:history` + `message.im` | DM answer path | | Hear @mentions | `app_mentions:read` + `app_mention` | `@app.event("app_mention")` | | Block Kit answers | `interactivity.is_enabled` | `@app.action(...)` | | Inbound WebSocket | `socket_mode_enabled` + app-level token w/ `connections:write` | VPN-only box, no public HTTPS endpoint | Inbound auth is NOT scope-based: AUTHZ-01 (`AGENT_TEAM_SLACK_OWNER_IDS`) gates the *sender* and fails closed. Socket membership alone is never authorization. ## Setup (operator, in browser — secrets never echoed) 1. **Create the app** — api.slack.com/apps → *Create New App* → *From an app manifest* → **pick workspace "Sea Haven Industries"** → paste `agent-team-manifest.json`. 2. **Install to Workspace** → copy the **Bot User OAuth Token** (`xoxb-`) → `SLACK_BOT_TOKEN`. 3. **Basic Information → App-Level Tokens → Generate** with scope `connections:write` → `SLACK_APP_TOKEN`. 4. **Channel** — `/invite @agentteam2` into the clarifier channel; its `C0…` id → `SLACK_CHANNEL_ID`. 5. **Owner allowlist** — `AGENT_TEAM_SLACK_OWNER_IDS` = Adam's Slack user id (`U0A3SC48T47`), comma-separated if more than one. Fails closed if empty. All five land in `~/secrev.env` on the box (mode 600), never shell history. ## Updating the app from the manifest Edit `agent-team-manifest.json`, then in the dashboard (App Manifest tab) paste the updated manifest, or use `apps.manifest.update` with an app **config token scoped to the workspace app** (the org config token can read/manage it as org owner, but keep the app workspace-level — do not re-create it org-owned).