Telegram, Discord & Slack
The three primary messaging platforms with streaming modes, reactions, and history logging.
Telegram, Discord, and Slack are the three messaging platforms most Revka operators reach for. All three are real-time, need no public inbound port (each pulls messages over an outbound connection, so they work behind NAT and on a Raspberry Pi), and ship in the default build with no extra Cargo features. This page is the per-platform reference: configuration keys, streaming modes, acknowledgement reactions, the Discord history-logging variant, and Slack permalink expansion.
Every channel lives under [channels_config] in ~/.revka/config.toml and runs inside revka daemon. If you have not connected a channel before, read Connect a messaging channel for the allowlist model and the polling-vs-webhook distinction, and see the Channels overview for the shared trait model. For a guided, step-by-step Telegram walkthrough (including the /bind pairing flow), see Set up Telegram & Matrix.
Streaming modes
Section titled “Streaming modes”Telegram and Discord share the stream_mode key (a StreamMode enum). Slack uses a boolean stream_drafts instead, because it implements draft updates but not multi-message splitting.
| Mode | Value | Behavior | Platforms |
|---|---|---|---|
| Off | "off" | Send the complete response as a single message once generation finishes. Default. | Telegram, Discord |
| Partial | "partial" | Post an editable draft and live-edit it as tokens arrive, then finalize with the full text. Throttled by draft_update_interval_ms. | Telegram, Discord, Slack (stream_drafts = true) |
| Multi-message | "multi_message" | Deliver the response as separate messages split on paragraph boundaries (\n\n), spaced by multi_message_delay_ms. Never splits a code fence. | Discord |
Acknowledgement reactions
Section titled “Acknowledgement reactions”Acknowledgement (ack) reactions tell a sender their message was received and is being worked on, before any reply text exists. They are controlled by a global switch under [channels_config]:
[channels_config]ack_reactions = true # default: add 👀 on receipt, ✅/⚠️ on completionshow_tool_calls = false # default: don't forward per-tool 🔧 notices to chat| Key | Type | Default | Meaning |
|---|---|---|---|
ack_reactions | bool | true | Add a 👀 reaction on receipt and ✅ / ⚠️ on completion to incoming channel messages. |
show_tool_calls | bool | false | Forward per-tool notices (e.g. 🔧 web_search_tool: …) to channel users. When false, tool calls are still logged server-side. |
Telegram layers its own behavior on top: when it starts working on a message it adds a randomized emoji reaction drawn from ⚡️, 👌, 👀, 🔥, 👍 via setMessageReaction. Telegram also accepts a per-channel ack_reactions override (see below) that takes precedence over the global value.
Telegram
Section titled “Telegram”Telegram is the fastest channel to stand up: create a bot with @BotFather, paste the token, and you are talking to your agent in minutes. It receives messages via getUpdates long-poll and replies via sendMessage.
[channels_config.telegram]bot_token = "123456:TELEGRAM-TOKEN" # required, from @BotFatherallowed_users = ["*"] # tighten after testingstream_mode = "off" # off | partialdraft_update_interval_ms = 1000 # edit throttle for partial modemention_only = false # only respond to @mentions in groupsinterrupt_on_new_message = false # cancel in-flight reply on a new message# ack_reactions = true # optional per-channel override of the global setting# proxy_url = "socks5://127.0.0.1:9050"# notification_chat_id = "123456789" # target for workflow notify/approval prompts| Field | Type / values | Default | Meaning |
|---|---|---|---|
bot_token | string (required) | — | Bot API token from @BotFather. |
allowed_users | list | [] (deny all) | Telegram usernames (without @) or numeric user IDs, or "*". |
stream_mode | "off" | "partial" | "off" | Reply delivery mode. |
draft_update_interval_ms | integer | 1000 | Edit throttle while streaming a draft in partial mode. |
mention_only | bool | false | Require an @mention before responding in group chats. DMs are always processed. |
interrupt_on_new_message | bool | false | Cancel the in-flight generation when the same sender messages again in the same chat. |
ack_reactions | bool (optional) | inherits global | Per-channel override of [channels_config].ack_reactions. |
proxy_url | string | — | Per-channel HTTP/HTTPS/SOCKS5 proxy, overriding the global [proxy]. |
notification_chat_id | string | — | Chat ID for workflow notify steps and approval prompts targeting "telegram". |
Add the channel and start the daemon:
revka channel add telegram '{"bot_token":"123456:TELEGRAM-TOKEN","name":"my-bot"}'revka daemonBecause the allowlist denies everyone by default, a new sender is refused and Revka replies with the exact revka channel bind-telegram <id> command to allowlist them. See Set up Telegram & Matrix for the full /bind pairing flow.
In-chat runtime commands
Section titled “In-chat runtime commands”These commands are available to allowed Telegram (and Discord) senders. They are sender-scoped and need no daemon restart:
| Command | Effect |
|---|---|
/models | Show available providers and current selection. |
/models <provider> | Switch provider for this sender’s session. |
/model | Show the current model. |
/model <model-id> | Switch model for this sender’s session. |
/new | Clear this sender’s conversation history and start fresh. |
Discord
Section titled “Discord”Discord connects over the Gateway WebSocket (op IDENTIFY), receives guild and DM messages in real time, and sends replies via REST. It is the richest of the three for streaming, supporting all three stream_mode values, typing indicators, emoji reactions, and approval-keyword intercept.
[channels_config.discord]bot_token = "DISCORD-BOT-TOKEN" # requiredguild_id = "123456789012345678" # optional: restrict to one guildallowed_users = ["*"] # tighten after testinglisten_to_bots = false # process messages from other botsmention_only = false # require an @mentioninterrupt_on_new_message = false # cancel in-flight reply on a new messagestream_mode = "multi_message" # off | partial | multi_messagedraft_update_interval_ms = 1000 # edit throttle (partial mode)multi_message_delay_ms = 800 # delay between chunks (multi_message mode)# proxy_url = "socks5://127.0.0.1:9050"# notification_channel_id = "123456789012345678"| Field | Type / values | Default | Meaning |
|---|---|---|---|
bot_token | string (required) | — | Bot token from the Discord Developer Portal. |
guild_id | string | — | Restrict the bot to a single guild (server). |
allowed_users | list | [] (deny all) | Discord user IDs, or "*". |
listen_to_bots | bool | false | Process messages from other bots. The bot always ignores its own messages. |
mention_only | bool | false | Require an @mention; other guild messages are ignored. |
interrupt_on_new_message | bool | false | Cancel the in-flight generation when the same sender messages again in the same channel. |
stream_mode | "off" | "partial" | "multi_message" | "off" | Reply delivery mode. |
draft_update_interval_ms | integer | 1000 | Edit throttle in partial mode (PATCH /channels/{id}/messages/{id}). |
multi_message_delay_ms | integer | 800 | Delay between paragraph chunks in multi_message mode. |
proxy_url | string | — | Per-channel HTTP/HTTPS/SOCKS5 proxy. |
notification_channel_id | string | — | Channel ID for workflow notify steps targeting "discord". |
Add and start:
revka channel add discord '{"bot_token":"DISCORD-BOT-TOKEN","name":"my-discord"}'revka daemonDiscord History Channel
Section titled “Discord History Channel”The Discord History channel is a distinct variant of Discord, configured under its own [channels_config.discord_history] key. It logs all non-bot messages to a dedicated discord.db memory backend while only forwarding @mention messages to the agent — turning Revka into a persistent guild archive that still answers when called. Use it when a team wants a searchable message history alongside the bot.
[channels_config.discord_history]bot_token = "DISCORD-BOT-TOKEN" # requiredguild_id = "123456789012345678" # optional: restrict logging to one guildallowed_users = ["*"] # empty = allow all (open logging)channel_ids = [] # channels to watch; empty = allstore_dms = true # persist DM messages to discord.dbrespond_to_dms = true # forward DM @mentions to the agent# proxy_url = "socks5://127.0.0.1:9050"| Field | Type / values | Default | Meaning |
|---|---|---|---|
bot_token | string (required) | — | Bot token from the Discord Developer Portal. |
guild_id | string | — | Restrict logging to a single guild. |
allowed_users | list | [] → allow all | Discord user IDs, or "*". Note: unlike standard Discord, an empty list here means open logging, not deny-all. |
channel_ids | list | [] (watch all) | Channel IDs to watch. |
store_dms | bool | true | Persist DM messages to discord.db. |
respond_to_dms | bool | true | Forward @mentions received in DMs to the agent. |
proxy_url | string | — | Per-channel proxy. |
discord_search tool currently unavailable
Section titled “discord_search tool ”A discord_search agent tool was designed to query the discord.db history backend — returning messages matching a keyword query, optionally filtered by channel_id or a since/until RFC 3339 time range. The tool code remains in the tree, but it is currently unwired: the SQLite memory backend it depended on was removed, and the tool is registered only when a discord-backed Memory handle is supplied. Until it is reintroduced on top of Kumiho graph memory, the tool will not appear in the agent’s tool list. For persistent, cross-session message recall today, use the Kumiho memory tools instead.
Slack connects through Socket Mode using an app-level token (xapp-...) for real-time events, falling back to conversations.history polling when no app token is present. It supports draft streaming via chat.update, cancel-by-reaction, typing indicators, file attachments, and permalink expansion.
[channels_config.slack]bot_token = "xoxb-..." # required, bot OAuth tokenapp_token = "xapp-..." # app-level token for Socket Modechannel_id = "C1234567890" # single channel; "*" or omit = all accessiblechannel_ids = ["C123...", "D456..."] # explicit list; takes precedence over channel_idallowed_users = ["*"] # tighten after testingthread_replies = true # reply in the originating threadmention_only = false # require an @mention in groups; DMs always alloweduse_markdown_blocks = true # use the markdown block type (12k char limit)stream_drafts = false # progressive draft updates via chat.updatedraft_update_interval_ms = 1200 # edit throttle for draft streamingcancel_reaction = "x" # emoji name (no colons) that cancels an in-flight requestinterrupt_on_new_message = false # newer message from same sender cancels and restarts# proxy_url = "socks5://127.0.0.1:9050"# notification_channel_id = "C1234567890"| Field | Type / values | Default | Meaning |
|---|---|---|---|
bot_token | string (required) | — | Bot OAuth token (xoxb-...). |
app_token | string | — | App-level token (xapp-...) enabling Socket Mode. Without it, Slack falls back to polling. |
channel_id | string | — | Single channel ID. Omit or use "*" to auto-discover all accessible channels. |
channel_ids | list | [] | Explicit channel/DM IDs to watch. Takes precedence over channel_id. |
allowed_users | list | [] (deny all) | Slack user IDs, or "*". |
thread_replies | bool | true | Reply in the originating thread. When false, replies post to the channel root. |
mention_only | bool | false | Require an @mention in groups. DMs remain allowed. |
use_markdown_blocks | bool | false | Use the newer markdown block type (12,000-char limit) instead of section blocks (3,000). Enable only if your workspace supports it. |
stream_drafts | bool | false | Enable progressive draft streaming via chat.update. |
draft_update_interval_ms | integer | 1200 | Edit throttle while streaming a draft. |
cancel_reaction | string | — | Emoji name (no colons), e.g. "x", that cancels an in-flight request when a user reacts with it. Leave unset to disable. |
interrupt_on_new_message | bool | false | A newer message from the same sender in the same channel cancels the in-flight request and restarts with preserved history. |
proxy_url | string | — | Per-channel proxy. |
notification_channel_id | string | — | Channel ID for workflow notify steps and approval prompts targeting "slack". |
Add and start:
revka channel add slack '{"bot_token":"xoxb-...","app_token":"xapp-...","name":"my-slack"}'revka daemonPermalink expansion
Section titled “Permalink expansion”When an inbound Slack message contains links to other Slack messages, Revka resolves them so the agent sees the referenced content without a separate tool call. It parses https://<workspace>.slack.com/archives/<channel>/p<timestamp> permalinks and fetches their context:
- Up to 3 permalinks per message are expanded (deduplicated by channel + timestamp).
- A thread permalink pulls in surrounding replies, up to the most recent 20; earlier replies are summarized as
… N earlier thread messages omitted …. - Combined expanded content is capped at 8,000 characters per message.
Expansion is transparent: the resolved Message: or Thread: text is prepended to what the agent reads. This pairs well with the cross-channel Link Enricher for non-Slack URLs.