Skip to main content

PRD: Slack channel — per-Specialist Slack Apps (1:1) with a client-facing "Chat on Slack" entry

中文: slack-per-specialist-app-prd.zh.md

StatusDraft — for review (rev 2: gap-analysis pass applied)
OwnerFranky (Product)
Date2026-07-03 · rev 2 2026-07-07
Architecture basisADR-030 + Slack subdocument (Draft) — this PRD productizes that design
Related#1471 (ADR-030), #1158 (Slack inbound refactor), #1159 (Marketplace submission), #3161/#3188 (per-thread conversations), #3630 (workspace-conflict UX), #3482 (channels settings cleanup), #1232 (PostHog)

1. Problem statement

Today the platform ships one shared "H-work" Slack app for all Specialists and all client orgs. Three product problems follow:

  1. Multi-Specialist orgs collapse to one bot. Slack routing resolves team_id → org, then falls back to the primary OSA unless a channel is explicitly bound (slack_channel_bindings). Non-primary Specialists are effectively unreachable on Slack — an org that hired Kai (KYC) and Mei (Finance) gets one generic bot.
  2. The bot is not the persona. Clients hire a named Specialist ("Kai"), but on Slack they talk to a generic "H-work" app with a generic icon. Persona identity — the core of the product — is lost on the channel where daily work happens.
  3. No discoverable entry. Nothing on the Specialist Profile tells a client member that Slack is available or how to start; setup state (installed vs not) is invisible outside the admin settings page.

This PRD introduces: one Slack App per Specialist (1:1) whose profile mirrors the Specialist (name, avatar, description); a self-serve admin flow (toggle → auto-generate app → install); and a "Chat on Slack" entry on the Specialist Profile that is truthfully gated on install state.

2. Goals

  • G1 — Multi-Specialist Slack. A client org can converse with each assigned Specialist as a distinct bot identity in the same workspace (N Specialists = N bots). Measured: Slack conversations attributed to non-primary OSAs > 0; primary-OSA fallback hits on migrated orgs → 0.
  • G2 — Self-serve setup ≤ 5 minutes. A client admin activates Slack for a Specialist end-to-end (toggle → install) without AM involvement. Measured: ≥ 80% of installs complete without a support ticket.
  • G3 — 2-click entry. A client member opens a Slack conversation with a Specialist from the Specialist Profile in ≤ 2 clicks; the entry state never lies (disabled ⇔ not installed). Measured: entry click-through → Slack DM open rate ≥ 70%.
  • G4 — Persona fidelity. Specialist name/avatar/description on Slack matches the platform profile; changes propagate within 24h (best-effort sync success ≥ 95%).
  • G5 — Deterministic isolation. Inbound resolves (api_app_id, team_id) → OSA in one lookup (ADR-030 invariant I-3); per-thread conversation semantics (#3161 Option B) preserved.

3. Non-goals (v1)

  • Org front-door bot (one bot per org + topic routing to Specialists) — deferred to ADR-028; conflicts with specialist_id NOT NULL and needs a classifier.
  • Slack Marketplace listing — default distribution is unlisted_distributed (installable via OAuth URL, not listed). Marketplace is per-Specialist opt-in later (#1159).
  • Enterprise Grid org-wide installs — schema is forward-compatible (enterprise_id), UX is not built.
  • Per-org bot display-name overrides — the endpoint column supports it; not surfaced in v1 UI (default bot name = Specialist first name).
  • Forced migration of existing shared-app installs — v1 dual-runs with the legacy shared app; migration + slack_channel_bindings degradation is its own later phase (ADR-030 Phase 6).
  • DM/channel threading changes — #3161 Option B semantics stay exactly as shipped.

4. User stories

Client admin (org_admin)

  1. As a client admin, I want to turn on Slack for a specific Specialist so that my team can reach that Specialist where we already work.
  2. As a client admin, I want the Slack app to be generated automatically with the Specialist's name/avatar/bio so that I don't do any Slack developer work.
  3. As a client admin, I want a clear install step (one OAuth click) and a visible status (Ready to install / Installed / Uninstalled) so that I always know what remains.
  4. As a client admin, I want to be warned before install if my Slack plan's app limit could block it so that I don't hit a dead end mid-flow.
  5. As a client admin, I want to uninstall/reinstall from the same screen so that lifecycle management doesn't require support.

Client member 6. As a client member, I want a "Chat on Slack" button on the Specialist's profile so that I can start a Slack conversation without asking anyone how. 7. As a client member, when Slack isn't set up yet, I want the button disabled with the reason ("Ask your admin to finish Slack setup") so that I'm not led into a dead end.

Ops (SuperAdmin / AM) 8. As a SuperAdmin, I want a fleet view of Specialist apps (persona state) and per-org installs (endpoint state) so that I can troubleshoot generation/install failures. 9. As an AM, I want to see which of my clients' Specialists have Slack active so that I can nudge adoption during check-ins.

Expert — no workflow change: inbound Slack messages arrive in the queue already attributed to the correct Specialist; replies post back into the source thread.

5. UX flows

5.1 Admin configuration flow (client portal → Settings → Channels → Slack)

The single Slack card becomes a per-Specialist list. Each row: Specialist avatar/name + state + action.

Toggle ON
→ [Generating Slack app…] persona provisioning (auto; minutes)
└─ failure → [Generation failed] (Retry / Contact support)
→ [Ready to install] (Install to Slack →) OAuth to client workspace
└─ OAuth denied / scope refused → stays Ready, error toast, retry
→ [Installed ✓] workspace name · installed by · date
actions: Reinstall · Uninstall
→ (Slack-side uninstall detected) → [Uninstalled from Slack] (Reinstall →)

State model (maps to ADR-030): persona pending/provisioning/provisioned/failed × endpoint pending/oauth_*/provisioned/suspended/revoked. The UI collapses these to: Off / Generating / Ready to install / Installed / Uninstalled / Failed.

Key rules:

  • The app (persona) is per Specialist, platform-wide — generated once, on the first org's toggle; later orgs skip straight to "Ready to install".
  • The install (endpoint) is per OSA — each org installs the Specialist's app into its own workspace.
  • Toggle OFF with an active install = confirm dialog → uninstall (auth.revoke) + entry hidden.

5.2 End-user flow (Specialist Profile)

  1. Client member opens the Specialist Profile (client portal).
  2. Profile shows a "Chat on Slack" entry:
    • Endpoint provisioned → button active. Click → deep link slack://app?team={team_id}&id={api_app_id}&tab=messages, web fallback https://slack.com/app_redirect?app={api_app_id}&team={team_id} (reuse the existing /client/slack-connect interstitial pattern).
    • No endpoint / not provisioned → button disabled + hint: "Slack isn't set up for this Specialist yet — ask your workspace admin to install the Slack app." If the viewer is an admin, show a "Set up →" shortcut to Settings → Channels instead.
    • Endpoint suspended (uninstalled on Slack side) → same disabled state as above (admins see "Reinstall →").
  3. Clicking the active entry opens the Specialist bot's DM in Slack — the conversation continues through the normal inbound → agent → Expert-review pipeline.

Design note (cross-channel consistency): build the entry as a channel-generic profile affordance ("Contact via …") rather than a Slack one-off, so the WhatsApp/Email per-OSA entries (ADR-030 siblings) can slot in later without a redesign.

5.3 Copy (v1, EN)

SurfaceText
Entry (active)Chat on Slack
Entry (disabled, member)tooltip: Ask your workspace admin to finish Slack setup for {Specialist}.
Entry (disabled, admin)Set up Slack → (links to Settings → Channels)
Install buttonInstall to Slack
GeneratingGenerating {Specialist}'s Slack app…
Uninstalled banner{Specialist}'s Slack app was uninstalled from {workspace}. Reinstall to resume.

6. Requirements

Must-have (P0)

R1 — Per-Specialist app auto-generation. First toggle-on for a Specialist creates its Slack App via the App Manifest API: name "{firstName} by h.work", bot display name {firstName}, description from the Specialist bio; scopes = the current slack/manifest.ts set; per-app webhook URL (…/slack/webhook/{api_app_id}); distribution auto-activated (unlisted_distributed); signing_secret/client_secret stored in Secrets Manager; persona row provisioned.

  • Given a Specialist with no Slack persona, when an admin toggles Slack on, then within 5 minutes the row shows "Ready to install" — or "Generation failed" with retry; never silently stuck.
  • If any step is not API-automatable (see O1 app icon), the persona enters pending_ops and an ops task is surfaced; the admin row shows "Generating" with a note, not a failure.
  • Generation is idempotent and concurrency-safe: two orgs toggling the same Specialist simultaneously yield exactly one persona (distributed lock / unique constraint per active-active rules); repeated toggles never create duplicate apps.

R2 — Per-OSA install (OAuth). "Install to Slack" produces the OAuth URL with a signed single-use state (osa_id, 5-min TTL). Callback validates: api_app_id matches persona; granted scopes ⊇ manifest scopes; (persona_id, team_id) not already bound. On success: token → Secrets Manager, endpoint provisioned, audit-logged.

  • Given the app is Ready, when the admin completes OAuth, then status flips to Installed and the profile entry activates without page reload assumptions (poll or socket).
  • Given OAuth is denied, then status remains Ready with a retriable error — never a dead end.

R3 — Gated profile entry (the core UX ask).

  • Given the OSA has a provisioned Slack endpoint, when a member views the Specialist Profile, then "Chat on Slack" is enabled and opens the bot DM (deep link + web fallback).
  • Given no provisioned endpoint, then the entry is disabled with the admin hint (§5.3); admins see the setup shortcut.
  • Given the app is uninstalled from Slack, then the entry reverts to disabled within 1 minute of the app_uninstalled event.

R4 — Deterministic inbound routing. Events arrive on the per-app webhook path; signature verified with that app's signing_secret; (api_app_id, team_id) resolves exactly one OSA (single-row lookup). No primary-OSA fallback for per-app events. Per-thread conversation semantics (#3188) and the HP-staff DM block (#2164) unchanged.

  • Invariant tests: endpoint.specialist_id === conversation.specialist_id; endpoint.org_id === conversation.org_id.

R5 — Outbound identity. Expert-released replies dispatch via the endpoint's bot token (3-layer pattern: circuit → retry → DLQ), land in the source thread, and render as the Specialist's bot (native bot user, not per-message impersonation).

R6 — Lifecycle handling. app_uninstalled → endpoint suspended (UI: Uninstalled; entry disabled). tokens_revokedrevoked. Reinstall restores the same conversation history (keyed by (persona_id, team_id)).

  • OSA termination / Specialist unassignment → explicit revocation (ADR-030 §9.3 Path B: auth.revoke, endpoint revoked, entry removed).
  • Org deactivation (deactivateOrg) → all of the org's Slack endpoints move to suspended, alongside the org's other integration pauses; restore re-activates.
  • Specialist archived/retired platform-wide → persona deprecated (no new installs); existing installs wound down per the OSA-termination path.

R7 — Identity sync (best-effort). Specialist name/avatar/description changes propagate to bot profiles per install (users.profile.set / users.setPhoto), rate-limit-aware, results in profile_sync_state; failures never block messaging.

R8 — Legacy coexistence. Orgs on the legacy shared app keep working untouched. An org can adopt per-Specialist apps while the legacy install still exists (dual-run); no regression to existing conversations.

R9 — Funnel instrumentation (server-side). The setup funnel and entry usage are measurable at launch without new client-side dependencies, consistent with the existing channel-metrics family (#3541/#3542):

  • Counters: slack_persona_generation_total{outcome}, slack_install_total{outcome} (started/completed/failed/denied), slack_entry_redirect_total (hit on the /client/slack-connect interstitial, per endpoint).
  • Audit log rows for toggle-on/off, generation, install, uninstall, revocation (actor + org + specialist).
  • Given the feature is live, then every §11 leading metric is computable from these counters + existing tables — no manual data pulls.

Nice-to-have (P1)

  • Ops fleet dashboard: persona states, endpoint states per org, scopes_drifted flags, profile-sync failures.
  • Admin notifications (in-app + email) on install completed / failed / app uninstalled.
  • Pre-install warning when the target workspace may be at its app limit (Slack free-plan cap; see O2).
  • Localized (zh) copy for §5.3.
  • Bot welcome message / App Home tab on first DM open (an empty DM after clicking the entry is confusing; greet with who this Specialist is + what to ask).
  • Client-side analytics (PostHog, #1232) for entry views → true CTR (R9 covers clicks only).

Future (P2)

  • Marketplace listing for flagship Specialists (#1159).
  • Per-org bot display-name override (endpoint column exists).
  • Enterprise Grid org-wide install.
  • Migration tool: legacy shared-app orgs → per-Specialist apps; degrade slack_channel_bindings to scope-hint (ADR-030 §10.3, Phase 6).
  • Org front-door mode (ADR-028).

7. Current implementation baseline (what changes)

AspectTodayAfter v1
Slack app1 platform-wide "H-work" app1 app per Specialist (persona), platform-wide
Credentialintegration_credentials UNIQUE(org, 'slack'); workspace ↔ org 1:1 (uq_integration_slack_team, #3630 conflicts)osa_channel_endpoints per OSA; uniqueness (persona_id, team_id) — one workspace hosts many Specialist apps
Specialist routingteam_id → org, then channel binding else primary OSA(api_app_id, team_id) → OSA, single lookup, no fallback
Bot identitygeneric H-workSpecialist name/avatar/bio
Client entrynone (admin settings only)gated "Chat on Slack" on Specialist Profile
Threadingper-thread conversations (#3161 Option B)unchanged
Mention gatemention/engagement gating in channelsunchanged for channels; DM with the Specialist bot is full-engage as today

8. Platform constraints & risks

  1. App icon automation (open — O1). The Manifest API sets name/description but historically not the app icon; the bot avatar may require a manual upload at app creation or users.setPhoto per install (itself restricted by token type/workspace policy — ADR-030 §15.7). v1 must tolerate a pending_ops manual step without blocking the admin flow.
  2. Slack free-plan app cap (O2). Free workspaces limit installed apps (~10). A client with many Specialists on free Slack can hit it mid-rollout; surface a pre-install warning and a graceful error.
  3. Fleet of unlisted commercial apps (risk). Slack's 2025+ posture hints at stricter rate limits/policy for non-Marketplace commercial apps (ADR-030 §13). Mitigation: per-method throttles, Marketplace opt-in path, monitor.
  4. App-config token rotation. Manifest API automation requires app-configuration tokens (12h rotation) — needs a refresh worker + runbook.
  5. Scopes are additive. Manifest scope changes → scopes_drifted → coordinated reinstall consent per org (ADR-030 §14.4).
  6. 3-second webhook ACK. Per-app webhook URLs registered at creation; ACK-then-queue as today.

9. Security & privacy

  1. Installer permission. Only client admins (org_admin/owner) can toggle, install, uninstall, or reinstall; members see the page read-only (existing #2242 pattern). Backend OrgRolesGuard is authoritative — the UI gating is not the control.
  2. Bot data access scope. The Specialist bot reads only its own DMs and channels it is explicitly invited to. This expectation is stated in the install consent copy and customer onboarding docs (ADR-030 §14).
  3. Workspace membership = access. Anyone in the client's Slack workspace can DM the Specialist bot and open a conversation — unchanged from today's model; there is no per-user allowlist in v1. (If a client needs tighter control, that is a future requirement, not silently assumed.)
  4. Blast radius & rotation. Per-install token leak → one OSA (rotate by reinstall). Per-app signing_secret leak → forged webhooks for all installs of that Specialist; a signing-secret rotation runbook is a GA gate (ops deliverable). Secrets live in Secrets Manager (per-app and per-install refs), never in the DB.
  5. Data retention on uninstall. Uninstall suspends messaging but retains conversation history under the platform's normal retention policy; reinstall resumes the same history ((persona_id, team_id) keying). Hard deletion follows the platform data-deletion process, not channel uninstall.
  6. Tenant isolation. Unchanged guarantees per ADR-030 §14: same-Specialist different-org isolated by install (token/team_id/RLS); same-org different-Specialist isolated by app (api_app_id/signing_secret/bot user).

10. Rollout & rollback

  • Feature flag, org-allowlisted. The per-Specialist Slack UI ships behind a per-org flag. Pilot: 1 internal test org + 1–2 friendly client orgs. Expand after 2 weeks of clean funnel metrics (R9: completion ≥ 80%, generation failures < 5%).
  • Rollback = flag off. UI reverts to the legacy shared-app card; per-app endpoints move to suspended (not deleted) so re-enabling restores them; per-app webhooks keep ACKing 200 with a structured drop log (never 4xx storms toward Slack).
  • Legacy is the safety net. Because v1 never touches legacy shared-app installs (R8), rollback cannot strand an org's Slack channel — worst case they return to today's behavior.
  • No forced migration during rollout (Non-goals): migration of legacy orgs is a separate later phase with its own plan.

11. Success metrics

Leading (evaluate at 2 weeks): toggle→installed completion rate ≥ 80%; median setup time ≤ 5 min; profile-entry CTR among Slack-enabled orgs; % Slack conversations attributed to non-primary OSAs > 0; generation failure rate < 5%.

Lagging (evaluate at 1 quarter): Slack-originated conversation volume per org (vs pre-launch); multi-Specialist orgs actively using ≥ 2 Specialist bots; support tickets tagged slack-setup (target: ↓); primary-OSA fallback hits on per-app traffic = 0.

Measurement method: funnel rates from the R9 counters (Prometheus/Grafana); entry clicks from slack_entry_redirect_total; conversation attribution by joining conversations.specialist_id against OSA primary flags; setup time from audit-log timestamps (toggle-on → install-completed). Entry view-based CTR needs client analytics (#1232, P1) — until then CTR is click-side only.

12. Open questions

#QuestionOwnerBlocking?
O1Can the app icon/avatar be set via API end-to-end (Manifest API / other)? If not, what is the exact manual step and who performs it?EngineeringYes — shapes R1's pending_ops design
O2Current Slack free-plan installed-app cap and detection method (can we know the workspace plan pre-install?)EngineeringNo — warning copy can ship later
O3Does client-admin toggle trigger platform-level app creation directly, or does first-time generation require SuperAdmin/ops approval?Product (Franky)Yes — defines R1 trigger
O4Exact placement/design of the profile entry (which profile surfaces: chat rail card, profile modal, mobile)DesignYes for UI build
O5For orgs already on the legacy shared app: does enabling a per-Specialist app hide/replace the legacy path in that workspace, or run alongside until Phase-6 migration?Product + EngYes — coexistence rules in R8
O6App naming collisions (two Specialists named "Kai") — Slack app names need not be unique, but listing/consent screens may confuse; suffix rule?ProductNo
O7Accept the unlisted-fleet policy risk (§8.3) for launch, or gate launch count?ProductNo
O8Shared workspace across orgs. (persona_id, team_id) uniqueness newly permits org A installing Kai's app and org B installing Mei's app into the same Slack workspace (today blocked by uq_integration_slack_team, #3630). Allow (per-app isolation suffices) or keep an org↔workspace exclusivity rule?Product + EngYes — isolation rule affects R2 validation
O9Should ops (AM Setup Wizard) also be able to enable Slack / generate the install link on the client's behalf (e.g., email the OAuth URL), or is v1 strictly client-admin self-serve?ProductNo

13. Phasing & dependencies

Maps to ADR-030 Slack §16 (Phases 1–7). Suggested v1 cut:

  • v1 (this PRD): Phases 1–3 (schema; backfill shadow; SlackPersonaService app generation + per-OSA OAuth install) + Phase 4 inbound refactor (coordinate with #1158 — the per-app webhook is the natural landing) + the client UI (admin per-Specialist list §5.1 + profile entry §5.2) + R9 instrumentation + §10 flag/pilot. New installs only; legacy untouched.
  • v1.1: Phase 5 identity sync + P1 items (fleet dashboard, notifications, welcome message, PostHog views).
  • v2: Phase 6 migration + bindings degrade; Phase 7 Marketplace (#1159); P2 items.

Dependencies: ADR-030 slack subdoc acceptance (currently Draft); #1158 refactor sequencing; Secrets Manager availability for per-app secrets; app-config token provisioning (ops); signing-secret rotation runbook (GA gate, §9.4).

14. References