Skip to content

Slack channel adapter

How to wire your Trimus instance to a Slack workspace so you can chat with your agents via Slack DMs the same way you can via the web UI or Telegram. Connection is Socket Mode, so no public URL is required.

Scope

Supported
Connection Socket Mode (no public URL needed)
Triggers — DMs direct messages (channel_type=="im")
Triggers — channel @-mentions app_mention in channels the bot is invited to
Identity one bot per Trimus instance → one company → one default agent (named agents can be addressed per-turn via @-mention)
Reply synchronous, plain text
UX placeholder‑then‑edit ("Thinking…" → real reply)
Access — DMs required allowlist — explicit Slack user IDs, or * for the whole workspace
Access — channels channel membership is the access policy; no separate allowlist

Out of scope (clean follow-ups):

  • Slash commands (e.g. /trimus)
  • Block Kit (interactive buttons / forms)
  • Streaming replies
  • File uploads / attachments
  • Multi-workspace from one Trimus instance

One-time Slack app setup

Five operator steps, ~10 minutes. The paste-able app manifest below pre-configures every scope so you don't have to click each one individually.

Step 1 — Create the app from manifest

Go to https://api.slack.com/appsCreate New AppFrom a manifest → pick your workspace → paste:

display_information:
  name: Trimus
  description: Chat with your Trimus agents from Slack.
  background_color: "#1f2937"
features:
  bot_user:
    display_name: Trimus
    always_online: true
  app_home:
    home_tab_enabled: false
    messages_tab_enabled: true
    messages_tab_read_only_enabled: false
oauth_config:
  scopes:
    bot:
      - chat:write
      - im:history
      - im:read
      - im:write
      - users:read
      - app_mentions:read
settings:
  event_subscriptions:
    bot_events:
      - message.im
      - app_mention
  interactivity:
    is_enabled: false
  org_deploy_enabled: false
  socket_mode_enabled: true
  token_rotation_enabled: false

The app_home block is important — without messages_tab_enabled: true Slack shows the DM input as "Sending messages to this app has been turned off" and operators can't talk to the bot at all. The manifest above sets it correctly; the same toggle lives under App Home → Show Tabs → Messages Tab if you ever need to flip it manually.

Optional — Slack knowledge ingestion (#266)

The base scopes above cover the DM/mention chat integration. If you want Trimus to ingest selected channels into the company Knowledge Base (admin-selected channels only — see the Slack Knowledge Ingestion → Channels page under a company's Integrations), add the read scopes it needs and re-install the app:

      # add under oauth_config.scopes.bot:
      - channels:history   # read public-channel messages
      - channels:read      # resolve public-channel metadata
      - groups:history     # (only if ingesting private channels)
      - groups:read        # (only if ingesting private channels)

The bot must also be a member of each channel you allowlist (invite it with /invite @Trimus). Ingestion is gated on TRIMUS_SLACK_INGEST_ENABLED=true on the worker tier and runs a background reconcile roughly every 6 hours, backfilling about the last 90 days and then keeping current. Messages become one Knowledge Base document per thread; the bot never posts to an ingested channel.

Click NextCreate.

Step 2 — Generate the app-level token

In the new app's settings → Basic Information → scroll to App-Level Tokens → click Generate Token and Scopes.

  • Token Name: socket-mode (any label works)
  • Scope: connections:write

Click Generate. Copy the xapp-… value — this is the TRIMUS_SLACK_APP_TOKEN. Save it now — Slack only shows this value once.

Step 3 — Install to the workspace

Left sidebar → Install AppInstall to → click Allow on the permission screen.

Slack will display the Bot User OAuth Token (xoxb-…). Copy it — this is the TRIMUS_SLACK_BOT_TOKEN.

Step 4 — Configure Trimus

Set five environment variables on your Trimus server:

# REQUIRED:
TRIMUS_SLACK_BOT_TOKEN=xoxb-...
TRIMUS_SLACK_APP_TOKEN=xapp-...
TRIMUS_SLACK_COMPANY_ID=<uuid-of-the-trimus-company>
TRIMUS_SLACK_USER_ALLOWLIST=U12345,U67890   # or "*" for everyone in the workspace

# OPTIONAL:
TRIMUS_SLACK_DEFAULT_AGENT_ID=<uuid-of-the-default-agent>

To find your Slack user id: in Slack, click your profile photo → ProfileCopy member ID. It starts with U.

Restart the Trimus server. The startup log will show:

INFO slack dispatcher started team_name=Acme bot_name=Trimus
     self_user_id=U0BOTID company_id=... default_agent_id=...

If the log instead shows slack dispatcher NOT started, check which env var is missing/wrong (the WARN line names each one with a boolean).

Step 5 — Test

In Slack, find the Trimus app in your sidebar (under "Apps") and DM it. The bot replies with a placeholder, then edits the placeholder with the real LLM response when it's ready.

That's it.

Allowlist policy

The TRIMUS_SLACK_USER_ALLOWLIST env var is required — the adapter refuses to start without it. This is deliberate: a Slack workspace can have contractors, customers, or other untrusted members; defaulting to "anyone can use the bot" is the wrong default.

Two forms:

  • Explicit list: U12345,U67890,UABCDE (comma-separated Slack user_ids). Whitespace around entries is tolerated. Only listed users can DM the bot; everyone else gets a polite rejection reply pointing them at this env var.

  • Wildcard: *. Everyone in the workspace can DM the bot. Use this only when every workspace member is trusted.

To add a user later, update the env var and restart the server.

Long messages

Slack rejects single messages over 40,000 characters. The adapter truncates verbose LLM replies at ~39,900 chars and appends a marker:

[…response truncated; check the Trimus UI for the full message]

The full message is always saved to the chats table and viewable in the Trimus web UI.

How a Slack DM maps to a Trimus chat

One Slack DM channel (D… id) ↔ one Trimus chats row, identified by:

{
  "slack": {
    "channel_id": "D012ABC",
    "user_id":    "U345DEF",
    "user_name":  "alice",
    "team_id":    "T678GHI"
  },
  "strict_actions": false
}

Every DM message becomes a chat turn in the same Trimus chat, forever. To start a fresh chat, archive the existing one from the Trimus UI; the next DM creates a new chat row.

strict_actions: false mirrors the Telegram default — DMs are operator-initiated and you usually want the agent to be able to take real actions, not just suggest. Toggle per-chat from the UI if you want a brainstorm-only DM.

DM pairing — linking a Slack user to a Trimus account

By default a Slack DM is anchored only to the Slack user_id. To link that Slack identity to a real Trimus user account (so the agent treats the conversation as that user — their private notes, their user model), use DM pairing:

  1. The Trimus user mints a single‑use code:
  2. UI: user menu (top‑right) → Settings → create a pairing code (POST /me/pairing-codes/create), or
  3. CLI: trimus-cli pair-code <email> slack — prints an 8‑char, 5‑minute, single‑use code.
  4. The user DMs the bot: /pair <code>. Trimus links the (user, channel) identity.

Revoke a pairing from the UI (/me/pairing-codes/delete) or with trimus-cli unpair-user <email> slack (idempotent, audited). A cleanup worker expires unused codes and auto‑revokes identities idle for more than 90 days. The per‑company pairing audit is at /companies/{id}/pairings/audit. The same pairing flow works for Telegram (trimus-cli pair-code <email> telegram).

Channel mentions

The Concierge agent can participate in Slack channel threads when @-mentioned. This requires the app_mention manifest items (already in the manifest above) and a per-channel /invite.

Enable channel mentions

  1. Ensure the app carries app_mentions:read (scope) and app_mention (event subscription) — both are in the manifest above. If you created the app from an older manifest, add them and reinstall the app (Slack may re‑issue the bot token on reinstall; check it before updating TRIMUS_SLACK_BOT_TOKEN).
  2. Invite the bot into channels where you want it reachable: run /invite @trimus in each channel. Slack only delivers app_mention events from channels the bot is a member of — your existing channel-invite decisions ARE the access policy. No changes to TRIMUS_SLACK_USER_ALLOWLIST are needed.

Behavior

  • Per-thread scope. Each Slack thread maps to one Trimus chat. A top-level @-mention starts a thread; the bot's reply roots it. Subsequent @-mentions inside that thread continue the same Trimus chat. A new top-level @-mention in the same channel starts a new thread and a new Trimus chat.

  • Address named agents by @-mention. Beyond the default responder, you can @-mention named agents — e.g. @Legal, @lead-researcher — in a DM or a channel thread. Each mentioned chat-capable agent replies sequentially, in mention order, and each later agent sees the earlier ones' fresh replies. This is one-off routing: the mention answers that one turn and does not switch the DM's or thread's default responder — unmentioned messages still go to it. Unknown @-words are ignored (a bare @-word in prose won't trigger anyone). A mentioned agent with no synchronous chat provider stays silent, but a brief _can't reply in chat_ marker is posted to the thread so members see why.

  • Private data is filtered on reads. In channel context the bot only surfaces company-wide notes. /list-notes and /get-note filter out user-private rows. This is intentional: a Slack thread is visible to every channel member, and surfacing one user's private data in a public thread is a leak. If you need access to private notes, DM the bot instead.

  • Writes still attribute to you. /create-note in a channel creates a note authored by you — private to you by default. You can read it later via DM. The bot's confirmation in the thread says _/create-note success: n_xxxxxxxx_; it does not echo the note body into the channel.

  • No backfill of prior thread history. The bot's memory of a thread starts at its first invitation into it. If you want the bot to consider earlier context from the thread, paste it into your @-mention.

Allowlist in channel context

TRIMUS_SLACK_USER_ALLOWLIST gates DMs only. It does not apply to channel @-mentions — Slack channel membership is the channel‑mode access policy, so anyone who can see the channel can @-mention the bot.

Identity linkage in channels

The identity‑linkage marker that appears in DM chats for unlinked Slack users does not fire in channel threads (emitting it in a public thread would expose one user's linkage state to the channel). To link your Slack identity to your Trimus account, open the user menu (top‑right) in Trimus → Settings. See DM pairing below.

Common gotchas

  • Slack shows "Sending messages to this app has been turned off" in the DM input field: the Messages Tab isn't enabled. App dashboard → App HomeShow Tabs, flip Messages Tab on, then tick "Allow users to send Slash commands and messages from the messages tab". No reinstall needed; the change takes effect immediately. (The manifest above sets this for new apps — this gotcha only hits apps created with older manifests or via the click-through flow.)

  • You changed OAuth scopes or Event Subscriptions, and the bot stopped receiving events: Slack requires you to reinstall the app to the workspace after these kinds of changes. App dashboard → Install AppReinstall to Allow. The existing xoxb-… token usually stays the same; double-check before updating TRIMUS_SLACK_BOT_TOKEN in your env file.

  • Bot doesn't reply, no error in logs: the dispatcher logged slack dispatcher started at boot but you see no further slack: lines no matter how many DMs you send. Use the slack-probe diagnostic tool (below) to bisect "Slack‑side delivery problem" vs "Trimus‑side dispatch problem".

  • token must start with xoxb- at startup: you pasted the app token (xapp-…) into the TRIMUS_SLACK_BOT_TOKEN slot (or vice versa). Swap them.

  • Bot replies with "This Trimus bot is restricted…": your Slack user_id isn't in TRIMUS_SLACK_USER_ALLOWLIST. Add it and restart.

  • Bot replies with "no agent is attached…": you haven't set TRIMUS_SLACK_DEFAULT_AGENT_ID, AND this is a fresh DM (no chat row yet). Either set the default agent in the env, or open the new chat in the Trimus UI and pick an agent from the sidebar.

  • Adding the bot to a channel does nothing: the app is missing the app_mention event subscription, so channel events never arrive. Confirm app_mention is present in Event Subscriptions → Bot Events (reinstall if you just added it) before /invite-ing the bot into channels.

  • The bot stops responding after a while: Socket Mode connections drop occasionally; slack-go/slack auto- reconnects with backoff. If it's not coming back, check the server logs for slack: Socket Mode connection ended and restart the server.

Debugging silent failures — the slack-probe tool

When DMs aren't reaching Trimus and the logs don't make the cause obvious, slack-probe is the right next step. It's a small standalone CLI that uses the same env-var token pair the production dispatcher uses, opens a Socket Mode WebSocket independently of trimus-server, and prints every event Slack delivers — with a 🎯 banner specifically on message.im DM events. By design it has no DB, no engine, no agent: if the probe sees a DM but Trimus doesn't, the bug is in Trimus's dispatch; if neither sees the DM, the bug is on Slack's side (or the network).

Build and run

# On any dev machine — Mac, the trimus host, or another box
# with the same Slack tokens in scope.
make build-slack-probe

# Load the same env vars trimus-server uses, then run.
# On a server with /etc/trimus/trimus.env (root-owned 600):
sudo bash -c 'set -a; source /etc/trimus/trimus.env; set +a; ./bin/slack-probe'

# On a dev machine where the tokens are in a different file:
set -a; source /path/to/slack-tokens.env; set +a
./bin/slack-probe

Reading the output

At startup the probe prints the auth.test identity (team, bot user, bot id) so you can confirm the tokens point at the expected Slack app + workspace. Then it logs every Socket Mode event. Things to watch for:

  • ✓ Socket Mode handshake complete — bot is subscribed and listening → Slack accepts the tokens and the WebSocket is open. Required before any event delivery can happen.
  • Reconnecting due to websocket: close 1006 (abnormal closure): unexpected EOF repeating every ~10s → a protocol‑level issue (e.g. mis‑Acking lifecycle frames). If the probe stays connected cleanly while trimus‑server does not, the network path is fine and the problem is in Trimus's dispatch.
  • 🎯 GOT A DM → Slack is delivering events end-to-end; if Trimus doesn't see this on the same DM, the bug is in Trimus's dispatcher (filter? early-return? Ack?).
  • Nothing at all after hello → Slack isn't pushing events. Most common cause: the app needs to be reinstalled to the workspace because scopes or event subscriptions changed.

Don't run alongside trimus-server on the same tokens

Each Slack app supports multiple concurrent Socket Mode connections (HA), so the probe and trimus-server can both connect at once. But Slack load-balances events between connections, so the DM you send might land at either side. For a clean "is Slack delivering?" reading, stop trimus (sudo systemctl stop trimus) for the duration of the probe test, then start it again afterward.

Stopping

Ctrl-C. The probe closes the WebSocket cleanly.

What to do next

If this doesn't cover what you need:

  • Slash commands: requires a small re-shape of the request/response cycle (Slack expects an immediate ack within 3s; the dispatcher would post the answer as a follow-up).
  • Multi-workspace: requires a slack_workspaces table mapping team_idcompany_id, and a per-event lookup. Doable but ~half a day of work.

See also

  • environment.md — the Slack/Telegram env vars in the full reference.
  • security.md — the channel:<name> readiness signal and what to alert on when the bridge is down.