Last updated: 2026-04-29

Hermes Discord Gateway — The Definitive Setup

The Discord gateway is the single highest-friction part of installing Hermes on a VPS. Out of the box it can fail silently in five distinct ways, four of which look identical to "the bot is just slow." This guide walks every step from "I have Hermes installed" to "I can chat with my agent in dedicated channels and it stays online forever."

Prerequisites

This guide assumes you've completed Phases 1–4 of the VPS Install Guide: hardened server, isolated hermes user, Hermes Agent installed, hermes --version works.

What you'll end up with
  • A Discord application + bot that only you can talk to (or whoever you list)
  • A persistent systemd user service that runs the gateway in the background, restarts on crashes, survives SSH disconnects and server reboots
  • A multi-channel layout for project work — #general, #planning, #code, #files, #research, #review, #home — with appropriate per-channel behavior
  • Logs you can tail in real time, and the knowledge to interpret them

Step 1 — Create the Discord application and bot

Open discord.com/developers/applications.

  1. Click New Application (top right). Name it. Accept the Terms. Click Create.
  2. Left sidebar → Bot.
  3. Under "Build-A-Bot," click Reset Token, confirm, and copy the token immediately. It's shown once. Format: MTAxMzQy...long-string. Treat it like a password.
  4. Scroll to Privileged Gateway Intents. Toggle ON:
    • Message Content Intent — required, otherwise the bot can see only that a message exists, not its text.
    • Server Members Intent — recommended.
  5. Save Changes at the bottom.

Generate the invite URL

  1. Left sidebar → OAuth2URL Generator.
  2. Under Scopes, check bot and applications.commands.
  3. Under Bot Permissions (which appears after bot is checked), enable:
    • View Channels
    • Send Messages
    • Send Messages in Threads
    • Read Message History
    • Embed Links
    • Attach Files
    • Use Slash Commands
    • Manage Messages (optional, allows the bot to delete/edit its own posts)
  4. Copy the generated URL at the bottom and open it. Pick a server you own and click Authorize.

The bot now appears in your server's member list, offline (grey dot). It will go online once Hermes' gateway connects to Discord.

Get your Discord user ID

This is the second critical credential — without it, anyone who finds your bot can talk to it and burn through your OpenRouter credits.

  1. Discord client → Settings (gear icon, bottom-left) → Advanced → toggle Developer Mode on.
  2. Right-click your own username anywhere in Discord → Copy User ID. The number looks like 123456789012345678.

Step 2 — Run the Hermes gateway setup

In your hermes session (sudo -iu hermes from root):

hermes setup gateway

When the platform list appears:

  • Arrow keys to Discord.
  • Press Spacebar to toggle it (you should see a [x] or filled circle indicator).
  • Press Enter to confirm.
Don't skip the spacebar

If you press Enter without first pressing Space, the wizard saves "no platforms selected" and exits silently. Re-running the wizard re-enters the same flow.

Paste prompts as they appear:

  • Discord bot token: the long string from the Developer Portal (Step 1, item 4).
  • Allowed user IDs: your numeric ID. Comma-separate if you want multiple users. Do not leave this empty.
  • Home channel ID: leave empty for now. We'll set it via env later.

Skip the systemd-service "install now" prompt for the moment — there's a bus-socket dance to do first. Or accept it and we'll fix the result.

Step 3 — Fix the systemd / bus-socket issue

If you accepted the install-as-systemd-service prompt, you probably saw:

Failed to connect to bus: No medium found
✗   Install failed: ... systemctl ... daemon-reload returned non-zero exit status 1.

This happens because sudo -iu hermes does not create a real PAM login session, so the user-level systemd manager (the thing systemctl --user talks to) is not running for the hermes user. Fix it in two steps.

3a. Enable lingering (run as root)

In your root window:

loginctl enable-linger hermes
sleep 2
loginctl show-user hermes | grep -E 'Linger|State'

Expected: Linger=yes and State=lingering or State=active. Lingering tells systemd: "start this user's systemd manager unconditionally and keep it running, regardless of whether the user is logged in." Without lingering, your services die every time you log out.

3b. Set XDG_RUNTIME_DIR (run as the hermes user)

Back in your hermes window:

export XDG_RUNTIME_DIR=/run/user/$UID
echo 'export XDG_RUNTIME_DIR=/run/user/$UID' >> ~/.bashrc
systemctl --user daemon-reload
hermes gateway install

XDG_RUNTIME_DIR is the directory where the user systemd's bus socket lives. sudo -iu does not set it; we fix that and persist it in .bashrc so future sessions inherit it. After this, systemctl --user works as expected.

hermes gateway install should now write the unit file to ~/.config/systemd/user/hermes-gateway.service cleanly.

3c. Start and enable the service

systemctl --user start hermes-gateway
systemctl --user enable hermes-gateway
systemctl --user status hermes-gateway

Expected:

● hermes-gateway.service - Hermes Agent Gateway - Messaging Platform Integration
     Loaded: loaded (.../hermes-gateway.service; enabled; preset: enabled)
     Active: active (running) since ...
   Main PID: 8761 (python)

Press q to exit the pager. Check Discord — your bot's icon should now have a green status dot.

A WARNING gateway.platforms.discord: [Discord] Slash command sync timed out after 30s line in the log is benign on the first start. The bot is online and will respond to @mentions and DMs even if slash commands take a few minutes to register.

Step 4 — Confirm the bot is alive

Tail logs in your hermes window:

journalctl --user -u hermes-gateway -f

In Discord, in #general, send:

@your-bot-name reply with the single word pong

Within 30–90 seconds the bot should reply. (Free OpenRouter models are slow on first call.) Ctrl+C to stop tailing logs.

If the bot reacts with a checkmark but never sends words, the most common cause is the next section.

Step 5 — The auto_thread trap (silent failure mode)

The default Hermes config sets:

discord:
  auto_thread: true

This makes the bot try to create a Discord thread under your message and post its reply inside the thread. That requires Create Public Threads and Send Messages in Threads permissions for the bot in that specific channel. If those are missing or denied at the channel level, the bot fails silently — it acknowledges with a checkmark, fails to create the thread, and never replies.

Fix: open ~/.hermes/config.yaml and change:

discord:
  require_mention: true
  free_response_channels: ''
  allowed_channels: ''
  auto_thread: false        # ← was true
  reactions: true

Save (Ctrl+O, Enter, Ctrl+X in nano), then restart the gateway:

systemctl --user restart hermes-gateway

The bot will now reply inline in the channel rather than fighting with thread permissions.

If you specifically want threads (e.g., long-running tasks where each conversation gets its own thread), leave auto_thread: true and ensure the bot has thread permissions in every channel it operates in.

Step 6 — Channel architecture for project work

Multi-channel layouts are the closest you can get to an OpenClaw-style dashboard without standing up a web service. Hermes natively supports per-channel routing and per-user sessions.

Recommended channels for a single project

ChannelPurposeFree response?
#generalDefault chat, low-stakes pingsOptional
#planningHigh-level decisions, scope, milestonesNo (require @mention)
#researchSource gathering, fact-checkingNo
#codeTelling the agent what to implement; viewing diffsYes
#filesDrag-drop files for the agent to consumeYes
#reviewReviewing what the agent built; requesting changesNo
#homeCron output, daily digests, proactive messagesN/A (one-way)

Names with a project prefix (charity-code, charity-files, etc.) keep multiple projects organized in the sidebar.

Get channel IDs

For each channel: right-click → Copy Channel ID. Keep them in a notepad as you go.

Set the home channel

Either inside Discord:

/set-home

(typed in the channel you want as home — but only works if Hermes' slash commands have registered)

Or via env, which is more reliable:

nano /home/hermes/.hermes/.env

Add:

DISCORD_HOME_CHANNEL=<paste-home-channel-id>

Save, then systemctl --user restart hermes-gateway.

Set free-response channels

Edit the same .env file. Add:

DISCORD_FREE_RESPONSE_CHANNELS=<code-id>,<files-id>

Save, restart. The bot will now reply to every message in those channels without needing an @mention. Other channels still require @mention.

Verify what is configured

Do not ask the bot in natural language — it will hallucinate based on Discord-bot stereotypes. Inspect the actual config:

grep -E '^DISCORD' /home/hermes/.hermes/.env
cat /home/hermes/.hermes/config.yaml | grep -A 20 discord

That output is ground truth.

Step 7 — Channel-permission overrides (the other silent failure)

Symptom: bot replies in #general but not #news-home (or vice versa) even though they're both standard text channels and the bot's role looks fine.

Discord has three permission layers, applied in this order: server-default → role → channel-specific override. A channel-specific override beats a role permission. So even if the bot's role globally has View Channel, a per-channel override can deny it.

Diagnosis: in your hermes window, tail logs while sending a message in the offending channel:

journalctl --user -u hermes-gateway -f

At default log level Hermes does not log incoming messages, only warnings/errors. To get visibility, raise the log level temporarily in ~/.hermes/config.yaml and restart, or skip directly to the fix.

Fix: in Discord, click the gear icon next to the channel → Permissions. Click your bot's role in the left list. Set these to explicit green checkmark (not red X, not grey neutral):

  • View Channel
  • Send Messages
  • Read Message History
  • Embed Links
  • Attach Files
  • Send Messages in Threads (if you ever set auto_thread: true)
  • Create Public Threads (likewise)

Also click @everyone in the same list and confirm View Channel is not red — neutral or green is fine, red breaks everything beneath it.

Nuclear option that always works: delete the channel, recreate it with the same name. The new channel inherits current default permissions cleanly.

Step 8 — Day-to-day operation

Status and logs

# from the hermes user
systemctl --user status hermes-gateway
systemctl --user restart hermes-gateway      # after config edits
journalctl --user -u hermes-gateway -f       # tail live logs
journalctl --user -u hermes-gateway -n 100   # last 100 lines

Editing config

Two files matter:

  • ~/.hermes/.env — secrets and per-platform IDs (token, allowed users, home channel, free-response channels).
  • ~/.hermes/config.yaml — agent behavior (auto_thread, require_mention, channel prompts, model fallbacks, log level).

After any edit, restart with systemctl --user restart hermes-gateway.

Per-channel system prompts (advanced)

In config.yaml:

discord:
  channel_prompts:
    "123456789012345678":      # planning channel ID
      prompt: "You are a terse strategic planner. Output bullet decisions only."
    "234567890123456789":      # code channel ID
      prompt: "You are a senior full-stack engineer. Make changes step by step. Show diffs."

Restart after editing. Different channels now produce different agent personalities — useful when one channel is for design decisions and another is for implementation.

Disabling the bot temporarily

systemctl --user stop hermes-gateway

The bot drops offline. The Hermes Python process exits. Bring it back with start. Add disable to remove auto-start on boot.

Rotating the bot token

Discord Developer Portal → Bot → Reset Token. Update DISCORD_BOT_TOKEN= in ~/.hermes/.env. Restart the gateway. Old sessions are invalidated immediately.

Quick troubleshooting matrix

SymptomMost likely causeFix
Bot is grey/offline in member listGateway service not running, or token invalidsystemctl --user status hermes-gateway; check journal for 401 Unauthorized
Bot reacts with checkmark but never repliesauto_thread: true + missing thread permissionsSet auto_thread: false, restart gateway
Bot replies in some channels, not othersChannel-specific permission overrideEdit channel permissions; or delete + recreate channel
Failed to connect to bus: No medium foundXDG_RUNTIME_DIR not set or linger not enabledloginctl enable-linger hermes (root) + export XDG_RUNTIME_DIR=/run/user/$UID (hermes)
Service starts then immediately exitsBad token, intent disabled in Developer Portal, or duplicate processCheck journal; verify Message Content Intent is on; ps aux | grep hermes
Slash commands don't appear after /Slash command sync timed out (transient)Restart gateway; wait 5 minutes for Discord propagation
Bot replies in DMs but not channelsBot not in any server, or require_mention: true and you didn't @mentionRe-run invite URL; or use @mention; or add channel to free_response_channels
Anyone in any server can talk to my botDISCORD_ALLOWED_USERS is emptyEdit .env; add your numeric Discord user ID; restart

What this gets you

A Discord-driven coding agent that:

  • Stays online 24/7 without an SSH session
  • Auto-restarts if it crashes
  • Comes back automatically after server reboots
  • Is reachable from your phone, your desktop, or any device with Discord
  • Cannot be talked to by random Discord users
  • Logs everything centrally via systemd journal
  • Works inside a multi-channel layout that mirrors the structure of your project

For everything else — install errors, OpenRouter quirks, model selection, Kilo-specific issues — see Hermes + Kilo Code Troubleshooting & FAQ.

← Back to Hermes hub · Previous: VPS Install · Next: Troubleshooting & FAQ →

📬 Weekly Digest — In Your Inbox

One email a week: top news, releases, and our deepest new guide. No spam. Same content via RSS if you prefer.