Tradecraft // NO. 010

One assistant, two channels: Telegram and Slack together

28 July 2026· 6 min read· Case № 010

Wire one Hermes profile to Telegram and Slack: the opt-in rule that makes Slack different, the two tokens it needs, and the one-connection trap that took down every profile at once.

The question that comes next

Article 4 showed how to wire a Telegram bot. The natural next step, if you run one assistant for yourself, is: can I reach the same agent from Slack without standing up a second profile?

Yes. One Hermes profile can listen on Telegram and Slack at the same time. The gateway manages both connections. One profile, one SOUL, two platforms.

Telegram is already working from the previous article. This one is about adding Slack, and the one rule that makes Slack behave completely differently: Slack is opt-in, Telegram is opt-out. Slack only sees the events you subscribe to, and those events only flow while the socket stays alive. Understand that rule and most Slack-on-Hermes failures stop being mysterious.

The architectural difference

Telegram and Slack solve the same problem – getting a message from a user to your bot – in completely different ways underneath.

Telegram is simple. The bot holds a long-polling HTTP connection back to Telegram’s servers, and Telegram pushes messages down it when they arrive. One credential, one connection, no ceremony.

Slack uses Socket Mode. Instead of your bot dialling out, Slack’s servers push events down a WebSocket connection that your bot maintains. A WebSocket is a live two-way connection that stays open. That means two tokens instead of one, and a connection that has to stay alive for events to move at all.

Telegram is a bot you can talk to anywhere. Slack is a bot that lives inside one workspace. For a personal assistant that is fine; for reachability across organisations, Slack is the wrong tool and Telegram wins by design.

The token setup: two tokens instead of one

Telegram needs one credential from BotFather. Slack needs two, and both land in the profile’s .env.

App-Level Token (xapp-...). This is the socket credential. It authorises the WebSocket connection to Slack’s events system, and it needs the connections:write scope. You generate it once from api.slack.com; it does not rotate when you reinstall the app.

Bot Token (xoxb-...). This is the identity credential. It authorises API calls – posting messages, reading channels, sending DMs – as the bot user inside your workspace. You get it after installing the app. It is workspace-specific, and it rotates if you reinstall the app.

You also need your own Slack Member ID (a U01ABC2DEF3-style string, not your username) for the allowed-users list. Copy it from your profile: click your name, View full profile, the “more” button, Copy member ID.

SLACK_APP_TOKEN=xapp-your-app-level-token
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_ALLOWED_USERS=U01ABC2DEF3

Optional: SLACK_HOME_CHANNEL=C01234567890 sets the default channel for cron and scheduled messages.

The named rule: Slack is opt-in, Telegram is opt-out

This is the rule the whole setup hangs on.

Telegram delivers every direct message to your bot with no configuration. The loop is simple: message arrives, you read it, you reply. There is nothing to subscribe to.

Slack delivers nothing until you declare an interest. In the app dashboard, Event Subscriptions must be enabled, and the bot events you want must be listed. The minimum set for a personal bot:

  • message.im – someone sends the bot a direct message
  • message.channels – someone writes in a public channel the bot is in
  • message.groups – private channels the bot is invited to
  • app_mention – someone @mentions the bot

Without these, the socket opens, the logs look healthy, and nothing ever arrives. Telegram has no equivalent step because it needs none.

The second half of the rule is the socket. Because events only arrive over a live WebSocket, the connection is the delivery mechanism. If the gateway restarts or the transport drops, events stop until the socket reconnects. Hermes reconnects automatically, but there is a window, and on a flaky network that window can stretch.

What broke: one app token is one live connection

The token setup and the opt-in rule are the theory. Here is what actually broke when I wired Slack across profiles.

I ran several Hermes profiles on the same box, and more than one of them pointed at the same Slack app. Slack’s Socket Mode allows exactly one live connection per app-level token. When the gateway multiplexed those profiles, every profile after the first was refused at connect:

ERROR gateway.platforms.base: [Slack] Slack app token already in use (PID 581832). Stop the other gateway first.
WARNING gateway.run: ✗ slack failed to connect (profile: accountant)

Five profiles failed the same way in the same second, one per PID. One held the socket; the rest bounced. The error message tells you the fix – stop the other gateway first – but the general lesson is the load-bearing one: an app-level token is a single-connection credential, not a shareable one. If you run multiple profiles on one machine, each profile needs its own Slack app and its own token pair. Reusing one app across profiles does not fail loudly with a useful error once; it fails silently for everyone but the first.

The wiring pattern

The whole setup is a fixed sequence, and every step is load-bearing.

  1. Generate the manifest. Run hermes slack manifest --agent-view. It prints a JSON app manifest that declares every Hermes slash command, every required OAuth scope, every event subscription, and Socket Mode, all at once.
  2. Create the app from it. Go to api.slack.com, Create New App, From an app manifest. Paste the JSON, pick your workspace, create.
  3. Install the app to your workspace. On the Install App page, click Install to Workspace and copy the Bot User OAuth Token (the xoxb- one).
  4. Create the app-level token. Under Settings, Socket Mode (or Basic Information, App-Level Tokens), generate one with the connections:write scope and copy the xapp- token.
  5. Add the keys to the profile’s .env – the three lines above, plus your Member ID.
  6. Enable Slack in config.yaml. Telegram auto-enables when its token is present in .env. Slack does not. It needs the explicit flag:
platforms:
  slack:
    enabled: true
  1. Start the gateway, then bind the home channel.
hermes -p personal gateway install
hermes -p personal gateway start

(Use your own profile name in place of personal.) After startup, message the bot /hermes sethome from the channel you want as the home channel. This is where cron results and scheduled messages land.

The manifest, since it decides everything downstream

The manifest is the part that actually declares the app’s permissions, so it is worth seeing rather than trusting. The pieces that matter:

{
  "features": {
    "app_home": { "messages_tab_enabled": true },
    "slash_commands": [ { "command": "/hermes" }, { "command": "/btw" } ]
  },
  "oauth_config": {
    "scopes": {
      "bot": [
        "app_mentions:read", "chat:write", "channels:history",
        "channels:read", "groups:history", "im:history", "im:write",
        "mpim:history", "mpim:read", "users:read", "files:read", "files:write"
      ]
    }
  },
  "settings": {
    "event_subscriptions": {
      "bot_events": [
        "app_mention", "message.im", "message.channels",
        "message.groups", "message.mpim"
      ]
    },
    "socket_mode_enabled": true
  }
}

That is the opt-in rule written down. The scopes decide what the bot may do; the bot events decide what it can see; socket_mode_enabled: true is what lets it connect from a private box with no public URL. When you change scopes or events later, reinstall the app or the changes do not take effect.

Cost and honest limits

Cost is zero on both sides. Telegram is free, bots are free, and the BotFather flow costs nothing. Slack Free is $0 and is genuinely free forever for a personal workspace. Nothing in this build needs API credits or a paid Slack tier.

The limits are worth naming before you build on it:

  • Workspace-bound. The bot token is scoped to one workspace. People outside it cannot reach the bot. If that is a requirement, Slack is the wrong channel.
  • 90-day history on Slack Free. Older messages are searchable only if you upgrade. Your agent’s session state is separate; it does not depend on Slack’s history retention.
  • No shared context across platforms by default. A conversation started on Telegram and continued on Slack does not share context. Same profile, separate sessions, deliberately. Cross-platform continuity is a separate design decision.

The takeaway

The useful lesson is not that you can reach one assistant from two chat apps. It is the pattern: when a platform only delivers what you subscribe to, the subscription list is the product, and the connection that carries the events is the infrastructure – neither can be assumed. Slack’s opt-in model rewards declaring exactly what you need; its single-connection tokens punish sharing them.

The agent does the tedious work. I do the one thing that matters: I read the event list before I trust the socket.

More soon – Michael.


Filed under: Hermes, Tradecraft. Tested on a multiplexed gateway, Slack Socket Mode.

← All transmissions