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/apps → Create New App → From 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 Next → Create.
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 App → Install to
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
→ Profile → … → Copy 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:
- The Trimus user mints a single‑use code:
- UI: user menu (top‑right) → Settings →
create a pairing code (
POST /me/pairing-codes/create), or - CLI:
trimus-cli pair-code <email> slack— prints an 8‑char, 5‑minute, single‑use code. - 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¶
- Ensure the app carries
app_mentions:read(scope) andapp_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 updatingTRIMUS_SLACK_BOT_TOKEN). - Invite the bot into channels where you want it reachable:
run
/invite @trimusin each channel. Slack only deliversapp_mentionevents from channels the bot is a member of — your existing channel-invite decisions ARE the access policy. No changes toTRIMUS_SLACK_USER_ALLOWLISTare 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-notesand/get-notefilter 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-notein 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 Home → Show 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 App → Reinstall to
→ Allow. The existing xoxb-…token usually stays the same; double-check before updatingTRIMUS_SLACK_BOT_TOKENin your env file. -
Bot doesn't reply, no error in logs: the dispatcher logged
slack dispatcher startedat boot but you see no furtherslack:lines no matter how many DMs you send. Use theslack-probediagnostic 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 theTRIMUS_SLACK_BOT_TOKENslot (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_mentionevent subscription, so channel events never arrive. Confirmapp_mentionis 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/slackauto- reconnects with backoff. If it's not coming back, check the server logs forslack: Socket Mode connection endedand 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 EOFrepeating 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_workspacestable mappingteam_id→company_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.