Skip to main content

Roles are reached from Slack through an app per role, over Socket Mode

Conversations (ADR 0011) were meant from the start to be reachable from elsewhere: a person talking to the Head of Staff from Slack, or someone who never opens the web app talking to a supporter role. Each role worth reaching from Slack gets a Slack app of its own, so the role appears in Slack as a person to message, and a person's DM with that app is their conversation with the role. The server reaches Slack over Socket Mode, so it needs no public address. A Slack module listens on conversation events and calls the same ConversationService.send the web app does; conversations never learn that Slack exists.

Considered Optionsโ€‹

  • One shared app for every role: rejected. One DM would carry several conversations, told apart by a slash command that switches roles (hidden state: replies go to whichever role was picked last) or by a thread per role (people must answer in the right thread). Its variant of one fixed role per person, set when linking, is the fallback if per-role apps hurt: simple, but every other role is out of reach from Slack.
  • The Events API (Slack posting to a public URL): rejected. It needs the server reachable from the internet, while MC_PUBLIC_URL (ADR 0004) is where sandboxed agents reach it and need not be public; Socket Mode only rules out Slack Marketplace apps, which these are not.
  • People linking their Slack account themselves (a one-time code, or Sign in with Slack): deferred. It only works for people who use the web app, and Sign in with Slack needs a public redirect URL. Owners link people instead, which also covers people who only use Slack.
  • A general channel seam for Slack and any later channel: not built. With one channel it would be a guess; Slack's tables and code stay Slack's, and a second channel shows what they share.
  • Slack's AI app threads (a real "is thinkingโ€ฆ" status): deferred. Every message a person sends would open a thread with the answer inside it, while messages the agent sends on its own land at the top level: another way of reading the conversation than the plain DM.

Consequencesโ€‹

  • Setting up a role's app is a person's job, a few minutes per role: Mission Control generates the app's manifest (name, description, permissions, Socket Mode on), an owner creates and installs the app in Slack, generates its Socket Mode token there (Slack has no API for that), and gives Mission Control the bot token and the Socket Mode token, kept as write-only secrets (ADR 0002). This happens on the role's page, by owners only, as with forges and secrets; agents have no tool for it. Slack's free plan allows 10 apps in a workspace, every integration counted.
  • An organization uses one Slack workspace: every role app in it must belong to the same one.
  • Owners link Slack people to members in the members settings, picking them from the workspace's people. Linking someone with no Mission Control user creates one without a password: a member who only uses Slack, who can be given a password later. A linked person acts in Slack exactly as their member does in the web app; removing the membership ends the link. An unlinked person is answered once that an owner must link them, with their Slack ID, and nothing they say is kept: only who they are, so owners see who is waiting and can link them from there.
  • A DM message from a linked person opens their conversation with the app's role, as the web app does, and is said in it by them; from there everything works as it does from the web app.
  • Everything the agent says goes to the DM, replies and what it says on its own when a followed task changes, and counts as read once delivered: Slack has already notified the person. What a person writes in the web app is copied into the DM by the app, marked as written in Mission Control, so the DM reads as the whole conversation.
  • What people said is never lost to an outage: on every connect the module reads each linked DM's history since the last message it took, and stores each message with its Slack timestamp, so one arriving both live and from history is kept once.
  • Agent messages are posted as a Markdown block, with a plain-text copy for notifications, and split at paragraph breaks past Slack's 12,000 characters. While the agent works, the app puts ๐Ÿ‘€ on the person's message, and takes it off when the agent answers or goes quiet. When its run fails, the app says so in the DM, once, and the details stay in Mission Control.
  • Starting afresh and deleting stay in the web app: Slack slash commands are shared by every app in a workspace, so each role's app would claim the same one.
  • The first version is DMs and text only: no channels, group DMs or mentions, and files people send are left out, which the app says.
  • It is built on Slack's lower-level clients (@slack/socket-mode, @slack/web-api), not Bolt, a framework that expects to own the app while the module routes everything into conversations.
  • The role's app is named its "Slack app", not a bot: the glossary keeps "bot" away from roles and agents, and the app is how a role appears in Slack, not a thing of its own. It joins the glossary when it is built.

Later: mentions in channelsโ€‹

Decided, as sketched here, in ADR 0014.

Not a goal for the first version, but the intended direction: mentioning @role in a channel reaches the role through the mentioning person's conversation with it, as a message carrying the mention, its channel and its thread. The role gets Slack tools, to read that thread and to post in it, so it can fetch what was said, create tasks and answer where it was asked. One conversation per person and role holds: the channel is something the conversation's agent works in, not a conversation of its own.

When to revisitโ€‹

  • Setting up apps per role hurts, or the 10-app limit is reached: one shared app, with one fixed role per person.
  • Copies of web messages, and their notifications, are noise for people using both: a per-person setting for them.
  • The ๐Ÿ‘€ reaction is not enough to tell the agent is at work: Slack's AI app threads, with all of a person's threads one conversation.
  • A second channel, such as Telegram: extract what it shares with Slack then, not before.