Slack
Socket mode (default)
Section titled “Socket mode (default)”Quick setup (beginner)
Section titled “Quick setup (beginner)”- Create a Slack app and enable Socket Mode.
- Create an App Token (
xapp-...) and Bot Token (xoxb-...). - Set tokens for Kurahi and start the gateway.
Minimal config:
{ channels: { slack: { enabled: true, appToken: "xapp-...", botToken: "xoxb-...", }, },}- Create a Slack app (From scratch) in https://api.slack.com/apps.
- Socket Mode → toggle on. Then go to Basic Information → App-Level Tokens → Generate Token and Scopes with scope
connections:write. Copy the App Token (xapp-...). - OAuth & Permissions → add bot token scopes (use the manifest below). Click Install to Workspace. Copy the Bot User OAuth Token (
xoxb-...). - Optional: OAuth & Permissions → add User Token Scopes (see the read-only list below). Reinstall the app and copy the User OAuth Token (
xoxp-...). - Event Subscriptions → enable events and subscribe to:
message.*(includes edits/deletes/thread broadcasts)app_mentionreaction_added,reaction_removedmember_joined_channel,member_left_channelchannel_renamepin_added,pin_removed
- Invite the bot to channels you want it to read.
- Slash Commands → create
/Kurahiif you usechannels.slack.slashCommand. If you enable native commands, add one slash command per built-in command (same names as/help). Native defaults to off for Slack unless you setchannels.slack.commands.native: true(globalcommands.nativeis"auto"which leaves Slack off). - App Home → enable the Messages Tab so users can DM the bot.
Use the manifest below so scopes and events stay in sync.
Multi-account support: use channels.slack.accounts with per-account tokens and optional name. See gateway/configuration for the shared pattern.
Kurahi config (minimal)
Section titled “Kurahi config (minimal)”Set tokens via env vars (recommended):
SLACK_APP_TOKEN=xapp-...SLACK_BOT_TOKEN=xoxb-...
Or via config:
{ channels: { slack: { enabled: true, appToken: "xapp-...", botToken: "xoxb-...", }, },}User token (optional)
Section titled “User token (optional)”Kurahi can use a Slack user token (xoxp-...) for read operations (history,
pins, reactions, emoji, member info). By default this stays read-only: reads
prefer the user token when present, and writes still use the bot token unless
you explicitly opt in. Even with userTokenReadOnly: false, the bot token stays
preferred for writes when it is available.
User tokens are configured in the config file (no env var support). For
multi-account, set channels.slack.accounts.<id>.userToken.
Example with bot + app + user tokens:
{ channels: { slack: { enabled: true, appToken: "xapp-...", botToken: "xoxb-...", userToken: "xoxp-...", }, },}Example with userTokenReadOnly explicitly set (allow user token writes):
{ channels: { slack: { enabled: true, appToken: "xapp-...", botToken: "xoxb-...", userToken: "xoxp-...", userTokenReadOnly: false, }, },}Token usage
Section titled “Token usage”- Read operations (history, reactions list, pins list, emoji list, member info, search) prefer the user token when configured, otherwise the bot token.
- Write operations (send/edit/delete messages, add/remove reactions, pin/unpin,
file uploads) use the bot token by default. If
userTokenReadOnly: falseand no bot token is available, Kurahi falls back to the user token.
History context
Section titled “History context”channels.slack.historyLimit(orchannels.slack.accounts.*.historyLimit) controls how many recent channel/group messages are wrapped into the prompt.- Falls back to
messages.groupChat.historyLimit. Set0to disable (default 50).
HTTP mode (Events API)
Section titled “HTTP mode (Events API)”Use HTTP webhook mode when your Gateway is reachable by Slack over HTTPS (typical for server deployments). HTTP mode uses the Events API + Interactivity + Slash Commands with a shared request URL.
- Create a Slack app and disable Socket Mode (optional if you only use HTTP).
- Basic Information → copy the Signing Secret.
- OAuth & Permissions → install the app and copy the Bot User OAuth Token (
xoxb-...). - Event Subscriptions → enable events and set the Request URL to your gateway webhook path (default
/slack/events). - Interactivity & Shortcuts → enable and set the same Request URL.
- Slash Commands → set the same Request URL for your command(s).
Example request URL:
https://gateway-host/slack/events
Kurahi config (minimal)
Section titled “Kurahi config (minimal)”{ channels: { slack: { enabled: true, mode: "http", botToken: "xoxb-...", signingSecret: "your-signing-secret", webhookPath: "/slack/events", }, },}Multi-account HTTP mode: set channels.slack.accounts.<id>.mode = "http" and provide a unique
webhookPath per account so each Slack app can point to its own URL.
Manifest (optional)
Section titled “Manifest (optional)”Use this Slack app manifest to create the app quickly (adjust the name/command if you want). Include the user scopes if you plan to configure a user token.
{ "display_information": { "name": "Kurahi", "description": "Slack connector for Kurahi" }, "features": { "bot_user": { "display_name": "Kurahi", "always_online": false }, "app_home": { "messages_tab_enabled": true, "messages_tab_read_only_enabled": false }, "slash_commands": [ { "command": "/Kurahi", "description": "Send a message to Kurahi", "should_escape": false } ] }, "oauth_config": { "scopes": { "bot": [ "chat:write", "channels:history", "channels:read", "groups:history", "groups:read", "groups:write", "im:history", "im:read", "im:write", "mpim:history", "mpim:read", "mpim:write", "users:read", "app_mentions:read", "reactions:read", "reactions:write", "pins:read", "pins:write", "emoji:read", "commands", "files:read", "files:write" ], "user": [ "channels:history", "channels:read", "groups:history", "groups:read", "im:history", "im:read", "mpim:history", "mpim:read", "users:read", "reactions:read", "pins:read", "emoji:read", "search:read" ] } }, "settings": { "socket_mode_enabled": true, "event_subscriptions": { "bot_events": [ "app_mention", "message.channels", "message.groups", "message.im", "message.mpim", "reaction_added", "reaction_removed", "member_joined_channel", "member_left_channel", "channel_rename", "pin_added", "pin_removed" ] } }}If you enable native commands, add one slash_commands entry per command you want to expose (matching the /help list). Override with channels.slack.commands.native.
Scopes (current vs optional)
Section titled “Scopes (current vs optional)”Slack’s Conversations API is type-scoped: you only need the scopes for the conversation types you actually touch (channels, groups, im, mpim). See https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overview.
Bot token scopes (required)
Section titled “Bot token scopes (required)”chat:write(send/update/delete messages viachat.postMessage) https://docs.slack.dev/reference/methods/chat.postMessageim:write(open DMs viaconversations.openfor user DMs) https://docs.slack.dev/reference/methods/conversations.openchannels:history,groups:history,im:history,mpim:historyhttps://docs.slack.dev/reference/methods/conversations.historychannels:read,groups:read,im:read,mpim:readhttps://docs.slack.dev/reference/methods/conversations.infousers:read(user lookup) https://docs.slack.dev/reference/methods/users.inforeactions:read,reactions:write(reactions.get/reactions.add) https://docs.slack.dev/reference/methods/reactions.get https://docs.slack.dev/reference/methods/reactions.addpins:read,pins:write(pins.list/pins.add/pins.remove) https://docs.slack.dev/reference/scopes/pins.read https://docs.slack.dev/reference/scopes/pins.writeemoji:read(emoji.list) https://docs.slack.dev/reference/scopes/emoji.readfiles:write(uploads viafiles.uploadV2) https://docs.slack.dev/messaging/working-with-files/#upload
User token scopes (optional, read-only by default)
Section titled “User token scopes (optional, read-only by default)”Add these under User Token Scopes if you configure channels.slack.userToken.
channels:history,groups:history,im:history,mpim:historychannels:read,groups:read,im:read,mpim:readusers:readreactions:readpins:reademoji:readsearch:read
Not needed today (but likely future)
Section titled “Not needed today (but likely future)”mpim:write(only if we add group-DM open/DM start viaconversations.open)groups:write(only if we add private-channel management: create/rename/invite/archive)chat:write.public(only if we want to post to channels the bot isn’t in) https://docs.slack.dev/reference/scopes/chat.write.publicusers:read.email(only if we need email fields fromusers.info) https://docs.slack.dev/changelog/2017-04-narrowing-email-accessfiles:read(only if we start listing/reading file metadata)
Config
Section titled “Config”Slack uses Socket Mode only (no HTTP webhook server). Provide both tokens:
{ "slack": { "enabled": true, "botToken": "xoxb-...", "appToken": "xapp-...", "groupPolicy": "allowlist", "dm": { "enabled": true, "policy": "pairing", "allowFrom": ["U123", "U456", "*"], "groupEnabled": false, "groupChannels": ["G123"], "replyToMode": "all" }, "channels": { "C123": { "allow": true, "requireMention": true }, "#general": { "allow": true, "requireMention": true, "users": ["U123"], "skills": ["search", "docs"], "systemPrompt": "Keep answers short." } }, "reactionNotifications": "own", "reactionAllowlist": ["U123"], "replyToMode": "off", "actions": { "reactions": true, "messages": true, "pins": true, "memberInfo": true, "emojiList": true }, "slashCommand": { "enabled": true, "name": "Kurahi", "sessionPrefix": "slack:slash", "ephemeral": true }, "textChunkLimit": 4000, "mediaMaxMb": 20 }}Tokens can also be supplied via env vars:
SLACK_BOT_TOKENSLACK_APP_TOKEN
Ack reactions are controlled globally via messages.ackReaction +
messages.ackReactionScope. Use messages.removeAckAfterReply to clear the
ack reaction after the bot replies.
Limits
Section titled “Limits”- Outbound text is chunked to
channels.slack.textChunkLimit(default 4000). - Optional newline chunking: set
channels.slack.chunkMode="newline"to split on blank lines (paragraph boundaries) before length chunking. - Media uploads are capped by
channels.slack.mediaMaxMb(default 20).
Reply threading
Section titled “Reply threading”By default, Kurahi replies in the main channel. Use channels.slack.replyToMode to control automatic threading:
| Mode | Behavior |
|---|---|
off |
Default. Reply in main channel. Only thread if the triggering message was already in a thread. |
first |
First reply goes to thread (under the triggering message), subsequent replies go to main channel. Useful for keeping context visible while avoiding thread clutter. |
all |
All replies go to thread. Keeps conversations contained but may reduce visibility. |
The mode applies to both auto-replies and agent tool calls (slack sendMessage).
Per-chat-type threading
Section titled “Per-chat-type threading”You can configure different threading behavior per chat type by setting channels.slack.replyToModeByChatType:
{ channels: { slack: { replyToMode: "off", // default for channels replyToModeByChatType: { direct: "all", // DMs always thread group: "first", // group DMs/MPIM thread first reply }, }, },}Supported chat types:
direct: 1:1 DMs (Slackim)group: group DMs / MPIMs (Slackmpim)channel: standard channels (public/private)
Precedence:
replyToModeByChatType.<chatType>replyToMode- Provider default (
off)
Legacy channels.slack.dm.replyToMode is still accepted as a fallback for direct when no chat-type override is set.
Examples:
Thread DMs only:
{ channels: { slack: { replyToMode: "off", replyToModeByChatType: { direct: "all" }, }, },}Thread group DMs but keep channels in the root:
{ channels: { slack: { replyToMode: "off", replyToModeByChatType: { group: "first" }, }, },}Make channels thread, keep DMs in the root:
{ channels: { slack: { replyToMode: "first", replyToModeByChatType: { direct: "off", group: "off" }, }, },}Manual threading tags
Section titled “Manual threading tags”For fine-grained control, use these tags in agent responses:
[[reply_to_current]]— reply to the triggering message (start/continue thread).[[reply_to:<id>]]— reply to a specific message id.
Sessions + routing
Section titled “Sessions + routing”- DMs share the
mainsession (like WhatsApp/Telegram). - Channels map to
agent:<agentId>:slack:channel:<channelId>sessions. - Slash commands use
agent:<agentId>:slack:slash:<userId>sessions (prefix configurable viachannels.slack.slashCommand.sessionPrefix). - If Slack doesn’t provide
channel_type, Kurahi infers it from the channel ID prefix (D,C,G) and defaults tochannelto keep session keys stable. - Native command registration uses
commands.native(global default"auto"→ Slack off) and can be overridden per-workspace withchannels.slack.commands.native. Text commands require standalone/...messages and can be disabled withcommands.text: false. Slack slash commands are managed in the Slack app and are not removed automatically. Usecommands.useAccessGroups: falseto bypass access-group checks for commands. - Full command list + config: Slash commands
DM security (pairing)
Section titled “DM security (pairing)”- Default:
channels.slack.dm.policy="pairing"— unknown DM senders get a pairing code (expires after 1 hour). - Approve via:
kurahi pairing approve slack <code>. - To allow anyone: set
channels.slack.dm.policy="open"andchannels.slack.dm.allowFrom=["*"]. channels.slack.dm.allowFromaccepts user IDs, @handles, or emails (resolved at startup when tokens allow). The wizard accepts usernames and resolves them to ids during setup when tokens allow.
Group policy
Section titled “Group policy”channels.slack.groupPolicycontrols channel handling (open|disabled|allowlist).allowlistrequires channels to be listed inchannels.slack.channels.- If you only set
SLACK_BOT_TOKEN/SLACK_APP_TOKENand never create achannels.slacksection, the runtime defaultsgroupPolicytoopen. Addchannels.slack.groupPolicy,channels.defaults.groupPolicy, or a channel allowlist to lock it down. - The configure wizard accepts
#channelnames and resolves them to IDs when possible (public + private); if multiple matches exist, it prefers the active channel. - On startup, Kurahi resolves channel/user names in allowlists to IDs (when tokens allow) and logs the mapping; unresolved entries are kept as typed.
- To allow no channels, set
channels.slack.groupPolicy: "disabled"(or keep an empty allowlist).
Channel options (channels.slack.channels.<id> or channels.slack.channels.<name>):
allow: allow/deny the channel whengroupPolicy="allowlist".requireMention: mention gating for the channel.tools: optional per-channel tool policy overrides (allow/deny/alsoAllow).toolsBySender: optional per-sender tool policy overrides within the channel (keys are sender ids/@handles/emails;"*"wildcard supported).allowBots: allow bot-authored messages in this channel (default: false).users: optional per-channel user allowlist.skills: skill filter (omit = all skills, empty = none).systemPrompt: extra system prompt for the channel (combined with topic/purpose).enabled: setfalseto disable the channel.
Delivery targets
Section titled “Delivery targets”Use these with cron/CLI sends:
user:<id>for DMschannel:<id>for channels
Tool actions
Section titled “Tool actions”Slack tool actions can be gated with channels.slack.actions.*:
| Action group | Default | Notes |
|---|---|---|
| reactions | enabled | React + list reactions |
| messages | enabled | Read/send/edit/delete |
| pins | enabled | Pin/unpin/list |
| memberInfo | enabled | Member info |
| emojiList | enabled | Custom emoji list |
Security notes
Section titled “Security notes”- Writes default to the bot token so state-changing actions stay scoped to the app’s bot permissions and identity.
- Setting
userTokenReadOnly: falseallows the user token to be used for write operations when a bot token is unavailable, which means actions run with the installing user’s access. Treat the user token as highly privileged and keep action gates and allowlists tight. - If you enable user-token writes, make sure the user token includes the write
scopes you expect (
chat:write,reactions:write,pins:write,files:write) or those operations will fail.
- Mention gating is controlled via
channels.slack.channels(setrequireMentiontotrue);agents.list[].groupChat.mentionPatterns(ormessages.groupChat.mentionPatterns) also count as mentions. - Multi-agent override: set per-agent patterns on
agents.list[].groupChat.mentionPatterns. - Reaction notifications follow
channels.slack.reactionNotifications(usereactionAllowlistwith modeallowlist). - Bot-authored messages are ignored by default; enable via
channels.slack.allowBotsorchannels.slack.channels.<id>.allowBots. - Warning: If you allow replies to other bots (
channels.slack.allowBots=trueorchannels.slack.channels.<id>.allowBots=true), prevent bot-to-bot reply loops withrequireMention,channels.slack.channels.<id>.usersallowlists, and/or clear guardrails inAGENTS.mdandSOUL.md. - For the Slack tool, reaction removal semantics are in /tools/reactions.
- Attachments are downloaded to the media store when permitted and under the size limit.
Resident session, commands and approvals
Section titled “Resident session, commands and approvals”Slack follows the shared model described in Channel residents.
-
Session:
channels.sessionMode(per-conversation: one session per DM or channel;shared: the terminal session). Thread sessions follow the threading rules above. -
Identities: keys are Slack member ids (
U…); use account-level tables for several workspaces.{channels: {slack: { identities: { U0AB12CD34: "owner", U0EF56GH78: "attach" } },approvals: { targets: [{ channel: "slack", to: "C0OPSROOM" }] },},} -
Commands:
/status,/jobs,/cancel,/evidence,/pause,/resume,/helpwork in DMs, channels and threads. These are message commands, not Slack slash commands; Slack-native slash commands are listed in the Tool actions section. -
Approvals: the ask is posted to the originating conversation and to
channels.approvals.targetswithapprove <id>/deny <id>keywords; Block Kit buttons are not attached by this layer yet. -
Chunking: 4000 characters per message (
textChunkLimit). -
Attachments: files shared with the app are copied to
<KOLO_HOME>/inbox/slack/within thechannels.inboxcaps. -
Delivery: replies go through the outbound queue; Slack rate limits (429) are retried with backoff;
kurahi channels statusshows the Slack queue and dead letters.