Hermes
Hermes Agent is an open-source AI agent from Nous Research that runs on your own machine and connects to messaging platforms like Telegram, Discord, Slack, LINE, and now Roam. With Roam, you can run your agent directly inside Roam chat — users DM it, mention it in groups, and pull it into threads, the same way they would any teammate.
This guide is about exactly that: running a Hermes bot in Roam chat. There are two flavors of bot (Personal and Org); pick whichever fits your use case and follow the matching section. You can run both side by side from the same machine.
Prerequisites
- A working Hermes Agent install. Hermes is a CLI that runs on macOS or Linux (Windows via WSL); confirm it works with
hermes --version. - A Roam workspace where you can create either a Personal Access Token (for a Personal Bot) or an API Key (for an Org Bot).
- A way to expose a local HTTPS endpoint so Roam can deliver webhooks. ngrok is easiest for dev; Cloudflare Tunnel is the recommended free option for long-running setups.
Pick your bot
| Personal Bot | Org Bot | |
|---|---|---|
| Acts as | You (the user who created the token). | Its own persona (e.g. "OnCallBot"), owned by the workspace. |
| Who can talk to it | Only people you allowlist. Other users are dropped. | Anyone in the workspace can DM it, @-mention it, or pull it into a thread, subject to the allowlists you configure. |
| In groups | Cannot be added as a member (Roam restricts PAT bots to user-typed accounts). Pulled into chats by @-mentions in your own conversations. | Can be added as a member of any group (required for private groups). In public groups, @-mentions work without membership. |
| Token type | Personal Access Token (rmp-…). | API Key (rmk-…). |
| Best for | Personal workflows — research, summarizing your own meetings, drafting messages on your own data. | Team workflows — on-call assistant, FAQ bot, status reports, anything everyone needs to talk to. |
Run both at once if you need both — see Run additional bots.
How access control works (read this first)
Hermes' Roam adapter has three layers of access control, in order:
- Token type filtering. A Personal Access Token discovers its owner from
/v1/token.infoat startup. The owner's name appears in the agent's context (so the agent addresses you by name), but the plugin does not restrict messages to that owner automatically — you control who can talk to a PAT bot via the allowlist below. - Sender allowlists. Set
ROAM_ALLOWED_USERS(DM senders) andROAM_ALLOWED_GROUPS(group/chat IDs) to comma-separated UUID lists, or setROAM_ALLOW_ALL_USERS=trueto skip allowlisting entirely. For development,ROAM_ALLOW_ALL_USERS=trueis the easiest start; for production, an explicit allowlist is the safer default. - Mention gate (groups only).
ROAM_REQUIRE_MENTION=truemakes the bot silent in groups unless someone @-mentions it. Defaults tofalse(responds to every message).
The boundary you actually rely on for safety is the allowlist. For a Personal Bot in dev, put just your own user ID in ROAM_ALLOWED_USERS; for an Org Bot, set ROAM_ALLOWED_GROUPS to the chat IDs you want it to live in. Workspace membership in Roam is also a coarse boundary — an Org Bot can only see messages from groups it's been invited to.
Set up a Personal Bot
A Personal Bot acts as you. DM it for one-on-one help, or @-mention it from groups you're in to pull it into a conversation on your own behalf. It can't be added to groups as a member.
1. Create a Personal Access Token in Roam
Prerequisite: Your workspace admin must enable Personal Access Tokens (see Admin Policy for Personal Access).
- In Roam, open User Settings > Developer.
- Click Create Personal Access Token.
- Name it (e.g. "Hermes").
- Select the permissions to grant — at minimum Chat Send and Chat Read so the agent can send and receive messages. Add User Read Email if you want the agent to know your email, plus Meetings Read and other scopes for the features you need.
- Upload an avatar image — this is the picture shown next to the agent's messages in Roam chat.
- Click Create Token. Copy both the token (starts with
rmp-…) and the webhook signing secret (whsec_…) shown on the confirmation screen — you'll need both in step 3.
2. Approve the token
If your workspace requires admin approval for personal access (the default), an admin must approve the new token before it can be used:
- In Roam, open Roam Administration > Developer > Personal Access Tokens.
- Find the new token and click Approve.
If your workspace is set to Auto-approve, skip this step. See Admin Policy for Personal Access.
3. Install the Hermes Roam plugin
Install it straight from the plugin's Git repo:
hermes plugins install WonderInventions/hermes-roam
This clones the plugin into ~/.hermes/plugins/roam/ and prompts you for the two required credentials — paste the token (rmp-…) and webhook signing secret (whsec_…) from step 1; they're saved to ~/.hermes/.env. When it asks Enable 'roam' now?, answer yes.
The Hermes web dashboard does the same thing: open the Plugins page, paste WonderInventions/hermes-roam into Install from GitHub / Git URL, then enable it. Other install methods (release tarball, manual git clone) are in the plugin's INSTALL.md.
Confirm it loaded:
hermes plugins list | grep roam
You should see roam | enabled | <version> | Roam ... | git.
4. Configure access
Step 3 already saved ROAM_API_KEY and ROAM_WEBHOOK_SECRET to your default profile's environment file at ~/.hermes/.env. Add the access setting:
cat >> ~/.hermes/.env <<EOF
# For dev: allow any sender. For prod, switch to ROAM_ALLOWED_USERS=<your UUID>.
ROAM_ALLOW_ALL_USERS=true
EOF
If you skipped the credential prompts in step 3, add them here too:
ROAM_API_KEY=<your rmp-… token> and ROAM_WEBHOOK_SECRET=<your whsec_… secret>.
5. Expose a public webhook URL
Roam delivers webhooks to a public HTTPS URL. The plugin's webhook server listens on localhost:8647/roam/webhook by default. Start a tunnel pointing at port 8647:
ngrok http 8647
ngrok prints something like https://<random>.ngrok.app. Add it to ~/.hermes/.env:
echo "ROAM_WEBHOOK_PUBLIC_URL=https://<random>.ngrok.app" >> ~/.hermes/.env
With ROAM_WEBHOOK_PUBLIC_URL set, the plugin auto-calls webhook.subscribe on startup so you don't need to register the URL manually in Roam admin.
6. Start the gateway
hermes gateway restart
tail -f ~/.hermes/logs/gateway.log | grep -i roam
You should see:
roam: PAT owner=<Your Name> (id=…); bot persona=<Bot Name> (id=…)
roam: subscribed to chat.message webhooks at https://….ngrok.app/roam/webhook
roam: webhook listening on 0.0.0.0:8647/roam/webhook (public: https://….ngrok.app)
✓ roam connected
The PAT owner line confirms the plugin read your identity from /v1/token.info — the agent will now address you by name in conversation.
7. Verify
In Roam, DM your bot. It should reply. The first message in any new chat triggers Hermes' onboarding flow ("Hi, I'm Hermes…"); send a second message to see a real LLM reply. To pull the bot into a group you're already in, @-mention it — its webhook fires for that single message, and it replies in-thread under your mention.
Set up an Org Bot
An Org Bot is its own persona — anyone in the workspace can DM it for help, @-mention it from any chat to pull it into a thread, or add it as a member of a channel where it responds to every message (your choice).
Hermes' multi-bot pattern is profiles: one Hermes profile per bot, each with its own gateway, state, and credentials. We'll create a new profile called org for the Org Bot.
1. Create an API Key in Roam
You'll need workspace-admin access for this step.
- As a Roam admin, open Roam Administration > Developer.
- Create a new API Client and select API Key as the authorization type.
- Give it a name (e.g. "OnCallBot") and select the scopes for what the bot will do — at minimum:
chat:send_message— Send messageschat:read— List groups it's added tochat:history— Read recent messages in those groups (used for context)
- Add additional scopes as needed:
meetings:read,recordings:read,transcript:read,groups:read, etc. - Upload an avatar image and set a display name — this is what users see in Roam chat.
- Click Create. Copy both the token (
rmk-…) and the webhook signing secret (whsec_…).
You'll add the bot to specific groups in step 7, once the Hermes side is wired up.
2. Create a Hermes profile
Each profile is an isolated Hermes instance — separate gateway, separate state, separate plugin install. See Hermes' Profiles guide for the full model.
hermes profile create org --description "Org bot for #engineering, runs OnCallBot persona"
This creates ~/.hermes/profiles/org/ and an org shim in ~/.local/bin/ so you can run org <subcommand> instead of hermes -p org <subcommand>.
3. Install the plugin in the org profile
Plugins are per-profile. The org shim runs every command under the org profile (hermes -p org), so installing through it lands the plugin in the org profile's own plugin directory and enables it there:
org plugins install WonderInventions/hermes-roam
When prompted, paste the org bot's token (rmk-…) and webhook signing secret (whsec_…) from step 1 — they're saved to ~/.hermes/profiles/org/.env. Answer yes at Enable 'roam' now? (this writes plugins.enabled: [roam] to the org profile's config.yaml).
If you want the org profile to share the default profile's LLM model setup (recommended for parity), append the model block to that config too:
grep -A 4 "^model:" ~/.hermes/config.yaml >> ~/.hermes/profiles/org/config.yaml
You may also need to copy auth tokens for OAuth-based providers (e.g. xAI Grok via OAuth):
cp ~/.hermes/auth.json ~/.hermes/profiles/org/auth.json
4. Configure behavior
Step 3 saved ROAM_API_KEY and ROAM_WEBHOOK_SECRET to the org profile's environment file at ~/.hermes/profiles/org/.env. Append the behavior settings:
cat >> ~/.hermes/profiles/org/.env <<'EOF'
# Each profile binds its own webhook port. The default profile uses 8647;
# use 8648 (or any free port) for the org profile.
ROAM_WEBHOOK_PORT=8648
# Org bots that idle in busy channels usually want every reply in a thread
# off the post being answered, so the main channel stays clean.
ROAM_REPLY_IN_THREAD=true
# Mention gate. false = respond to every message in groups. true = silent
# unless @-mentioned. Choose based on whether this bot lives in dedicated
# channels (false) or general ones (true).
ROAM_REQUIRE_MENTION=false
# For dev: allow any sender. For prod, switch to:
# ROAM_ALLOWED_GROUPS=<comma-separated chat UUIDs>
ROAM_ALLOW_ALL_USERS=true
EOF
If you skipped the credential prompts in step 3, add them to that file too: ROAM_API_KEY=<your rmk-… token> and ROAM_WEBHOOK_SECRET=<your whsec_… secret>.
If you set up the LLM model in step 3 but want any non-OAuth API keys too (OpenAI, Anthropic, OpenRouter, etc.), copy them from the default profile:
grep -E "^(OPENROUTER|XAI|ANTHROPIC|OPENAI)_" ~/.hermes/.env >> ~/.hermes/profiles/org/.env
5. Expose a public webhook URL for the org bot
The default profile's tunnel forwards to port 8647; the org profile needs its own forwarding to port 8648. Start a second tunnel:
ngrok http 8648
Take note of the new https://<random>.ngrok.app URL and add it to the org profile's .env:
echo "ROAM_WEBHOOK_PUBLIC_URL=https://<random>.ngrok.app" >> ~/.hermes/profiles/org/.env
For a long-running setup, Cloudflare Tunnel is the recommended alternative to ngrok.
6. Give the bot a persona
A new bot has no behavior of its own — it'll respond like a generic assistant. Edit ~/.hermes/profiles/org/SOUL.md to set the bot's personality, role, and prompt:
You are OnCallBot, an SRE assistant living in #alerts in Roam. Every
message in this channel is typically a Datadog alert. Your job is to
investigate and report.
When a new alert arrives:
1. Extract the monitor name, the failing condition, the affected service /
host / tag(s), and the alert time window.
2. Investigate using the `datadog` tool: monitor details, metrics for the
affected service around the alert time, related logs, recent deploys.
3. Reply with a concise, well-structured investigation:
- **Summary** — one sentence: what's wrong and the likely cause.
- **Evidence** — specific metric values, log excerpts, exact times.
- **Suggested next steps** — what a human on-call would do next.
4. Keep replies tight. No filler.
5. If you can't form a hypothesis, say so plainly — list what you looked at.
6. Never claim a fix is applied. You investigate and report; humans act.
When asked who you are, say you are OnCallBot.
See Hermes' Configuring the agent guide for the full personality model.
7. Invite the bot to your channels
In Roam, open Group Settings for the channel you want the bot to live in and add it as a member. This is required for private groups (the bot only sees messages in groups it's been added to). In public groups, @-mentions reach the bot without explicit membership, but adding it as a member is still the cleanest way to scope its presence.
Repeat for each channel the bot should hang out in.
8. Start the gateway and verify
org gateway start
tail -f ~/.hermes/profiles/org/logs/gateway.log | grep -i roam
You should see:
roam: org token; bot=OnCallBot (id=…)
roam: subscribed to chat.message webhooks at https://….ngrok.app/roam/webhook
roam: webhook listening on 0.0.0.0:8648/roam/webhook (public: https://….ngrok.app)
✓ roam connected
The org token line (rather than PAT owner=…) confirms the plugin recognized this as an API-key bot rather than a personal-access token.
Then in Roam:
- DM the bot. Open a direct message with the bot in Roam and send it a hello. It should reply with the persona you set.
- Post a message in a channel you added it to. If
ROAM_REQUIRE_MENTION=false, post any message — the bot replies in a new thread (withROAM_REPLY_IN_THREAD=true). IfROAM_REQUIRE_MENTION=true, @-mention the bot to trigger it. - Pull it into a thread. From another chat the bot isn't a member of, @-mention it inside an existing thread. The bot replies in that same thread.
Caveats for proactive (every-message) bots
With ROAM_REQUIRE_MENTION=false, the bot answers every message in the channel. Two things to watch for:
- Self-message loop protection comes from the bot's identity. When the bot posts a reply, Roam delivers that outbound back via the webhook (the bot is technically a participant in its own chat). The plugin filters these out by comparing
userIdagainst the bot's address ID, fetched at startup via/v1/token.info. If that call fails, the log showsroam: token.info failed: …— in that state the bot can loop on its own replies once you fliprequireMentionoff. Fix thetoken.infoissue (check token scopes, approval status, and network reachability) before enabling proactive mode. - Every message in the channel triggers an agent run. If the channel is busy, runs queue up and tokens burn fast. Either narrow which groups the bot listens in (
ROAM_ALLOWED_GROUPS) or add aSOUL.mdrule telling the agent when not to reply.
Restricting which users and groups can reach the bot
By default, with ROAM_ALLOW_ALL_USERS=true, the bot answers anyone who can reach it. To restrict:
# DM senders allowlist — Roam user UUIDs, comma-separated.
echo "ROAM_ALLOWED_USERS=<uuid1>,<uuid2>" >> ~/.hermes/profiles/org/.env
# Groups allowlist — Roam chat/group UUIDs, comma-separated.
echo "ROAM_ALLOWED_GROUPS=<chatId1>,<chatId2>" >> ~/.hermes/profiles/org/.env
# Then remove the catch-all:
sed -i '' '/^ROAM_ALLOW_ALL_USERS/d' ~/.hermes/profiles/org/.env
org gateway restart
ROAM_ALLOWED_GROUPS IDs are the group's chat UUID, found at the bottom of the group's Group Settings page in Roam.
Run additional bots
To add another bot, create another Hermes profile and repeat the Org Bot setup with a new name and a unique webhook port:
hermes profile create another
another plugins install WonderInventions/hermes-roam
# Then set ~/.hermes/profiles/another/.env (ROAM_WEBHOOK_PORT=8649 or any free
# port, plus the allowlist / behavior vars) as in the Org Bot steps above.
another gateway start
Troubleshooting
"I sent a message and got nothing back"
Tail the gateway log and re-send:
tail -f ~/.hermes/logs/gateway.log | grep -i roam
# or for a non-default profile:
tail -f ~/.hermes/profiles/<name>/logs/gateway.log | grep -i roam
The most common drop reasons are logged explicitly:
roam: dropping group message (require_mention=true, no @bot mention)— flipROAM_REQUIRE_MENTION=false, or @-mention the bot using Roam's autocomplete (so it inserts a rich mention, not a plain string).roam: rejecting unauthorized message chat_type=… chat_id=… user_id=…— the sender or group isn't on the allowlist. Add them toROAM_ALLOWED_USERS/ROAM_ALLOWED_GROUPS, or enableROAM_ALLOW_ALL_USERS=truefor dev.- No log entry at all means the webhook never reached the plugin. Check the tunnel's request inspector (ngrok exposes one at
http://127.0.0.1:4040) to see whether Roam delivered to the URL.
webhook.subscribe failed at startup
The plugin's auto-subscription call returned a non-2xx. Common causes:
- The URL isn't reachable from Roam's servers (tunnel not running, wrong port).
- The token is missing the scopes required to manage webhook subscriptions.
- The webhook secret is malformed (must be the
whsec_…value Roam printed when you created the token).
You can subscribe manually if you'd rather skip auto-subscription:
curl -X POST https://api.ro.am/v1/webhook.subscribe \
-H "Authorization: Bearer $ROAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"<your tunnel>/roam/webhook","event":"chat.message"}'
Markdown rendering looks wrong in Roam
Hermes' content generator emits Markdown using the chat-platform convention where a single newline is a hard line break (the same convention used by GFM, Slack, Discord, Telegram). Roam's renderer follows CommonMark's other allowed softbreak rendering — a single newline collapses to a space — so unmodified Hermes output would smush lines together. The plugin reconciles this by inserting blank lines between non-empty lines outside fenced code blocks, which renders correctly under either softbreak choice.
Two bots with the same display name
If you create both a Personal Bot and an Org Bot and give them the same name in Roam (e.g. both "Hermes"), users can't tell which one they're @-mentioning. Rename one in Roam admin — the display name on the token (Personal) or API client (Org) is what shows in chat.
Where to file issues
The plugin source lives at WonderInventions/hermes-roam. File issues there for bugs in the Roam integration; for Hermes core issues, use the Hermes Agent repo.