# OpenClaw

[OpenClaw](https://openclaw.ai/) is an open-source AI agent that connects to messaging platforms like WhatsApp, Telegram, Slack, and Discord. 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 mostly about that — running an OpenClaw 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 on the same OpenClaw install.

If you'd rather drive OpenClaw from another surface (Telegram, Slack, terminal) and only need it to read or write Roam data, jump to [Use Roam as a data source from another surface](#use-roam-as-a-data-source-from-another-surface) at the bottom.

## Prerequisites

- A running [OpenClaw instance](https://docs.openclaw.ai/gateway/setup)
- A Roam workspace where you can create either a Personal Access Token (for a Personal Bot) or an API Key (for an Org Bot)

## 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 you. Other users can't DM it or mention it.                                                                | Anyone in the workspace can DM it, @-mention it, or pull it into a thread.                                             |
| **In groups**         | Cannot be added as a member. Only sees messages where you explicitly @-mention it in your own chats.            | 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 multiple bots from one OpenClaw install](#run-multiple-bots-from-one-openclaw-install).

## How access control works (read this first)

OpenClaw used to require a per-sender **pairing** challenge before the bot would respond to a DM. That's no longer the default. The current model is intentionally simple:

- **A Personal Bot responds only to its owner.** Automatic — the plugin discovers the owner from `/v1/token.info` at startup and drops every inbound from anyone else, on every surface. No setup needed; no allowlist to maintain. An adversary who creates a private group and adds the bot still gets dropped.
- **An Org Bot responds to anyone who can reach it in the workspace.** No per-sender approval — the security boundary is workspace membership (and, for private groups, who's been added to that group). This matches how bots in Slack, Discord, and Telegram work at the protocol level.

If you need a hard sender allowlist on an Org Bot, set `allowFrom` (for DMs) or `groupAllowFrom` (for groups) on the account — see [Restricting which users can talk to the bot](#restricting-which-users-can-talk-to-the-bot). Pairing is still available as an explicit opt-in (`dmPolicy: "pairing"`) for operators who want a UX-driven approval flow, but it's no longer recommended.

## Set up a Personal Bot

A Personal Bot acts as you. You can 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, and other users can't talk to it directly.

### 1. Create a Personal Access Token in Roam

**Prerequisite:** Your workspace admin must enable Personal Access Tokens (see [Admin Policy for Personal Access](/docs/guides/access-models#admin-policy-for-personal-access)).

1. In Roam, open **User Settings > Developer**.
2. Click **Create Personal Access Token**.
3. Name it (e.g. "OpenClaw").
4. Select the permissions to grant — at minimum **Chat Send** and **Chat Read** so the agent can send and receive messages. Add **Meetings Read** if you want it to access your meetings.
5. Upload an avatar image — this is the picture shown next to the agent's messages in Roam chat.
6. Click **Create Token**. Copy both the **token** (starts with `rmp-…`) and the **webhook signing secret** shown on the confirmation screen — you'll need both in the next step.

### 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:

1. In Roam, open **Roam Administration > Developer > Personal Access Tokens**.
2. Find the new token and click **Approve**.

If your workspace is set to **Auto-approve**, skip this step. See [Admin Policy for Personal Access](/docs/guides/access-models#admin-policy-for-personal-access).

### 3. Install the channel plugin

In your OpenClaw instance:

```bash
openclaw plugins install @roamhq/openclaw-roam
```

### 4. Configure the plugin

Set the token, webhook signing secret, and webhook URL under `channels.roam.accounts.default`:

```bash
openclaw config set channels.roam.accounts.default.apiKey         "$TOKEN"
openclaw config set channels.roam.accounts.default.webhookSecret  "$WHSEC"
openclaw config set channels.roam.accounts.default.webhookUrl     https://YOUR_HOST/roam-webhook
```

Replace `https://YOUR_HOST/roam-webhook` with the public URL of your OpenClaw instance — the plugin registers a local listener at that URL's path and automatically subscribes Roam to deliver messages there on startup. If your OpenClaw instance is only accessible on a private network during development, tools like [ngrok](https://ngrok.com/) or [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) can expose it temporarily.

The `accounts.default` prefix keeps room for [running multiple bots](#run-multiple-bots-from-one-openclaw-install) later without restructuring config.

Apply the changes:

```bash
openclaw daemon restart
```

Every config change in this guide takes effect after a gateway restart.

### 5. Verify

Send a direct message to your bot in Roam. It should reply.

### 6. Using the bot from groups you're in

To pull the bot into a conversation in a group you're already part of, just @-mention it. The bot's webhook fires for that single message; it replies in-thread under your mention so the conversation stays scoped.

The bot ignores everyone except you, automatically:

- It **cannot** be added as a member of any group (Roam restricts PAT bots to user-typed accounts).
- Even when other users in a group try to @-mention or DM it, the plugin drops their messages — it discovers your user ID from `/v1/token.info` at startup and responds only to that ID. Same rule on every surface; no allowlist to maintain.

If you want a bot that anyone can talk to, you want an Org Bot — see the next section.

## 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).

### 1. Create an API Key in Roam

You'll need workspace-admin access for this step.

1. As a Roam admin, open **Roam Administration > Developer**.
2. Create a new API Client and select **API Key** as the authorization type.
3. Give it a name (e.g. "OnCallBot") and select the scopes for what the bot will do — at minimum:
   - `chat:send_message` — Send messages
   - `chat:read` — List groups it's added to
   - `chat:history` — Read recent messages in those groups (used for context)
4. Add additional scopes as needed: `meetings:read`, `recordings:read`, `transcript:read`, `groups:read`, etc.
5. Upload an avatar image and set a display name — this is what users see in Roam chat.
6. Click **Create**. Copy both the **token** (`rmk-…`) and the **webhook signing secret**.

You'll add the bot to specific groups later in [Verify](#7-verify), once the OpenClaw side is wired up.

### 2. Install the channel plugin

If you haven't already:

```bash
openclaw plugins install @roamhq/openclaw-roam
```

### 3. Register an agent profile for the bot

Each bot gets its own agent — its own workspace directory, its own identity, its own system prompt. Pick an `agentId` (short string, used internally) and a workspace directory:

```bash
mkdir -p /Users/you/openclaw-orgbot-agent

openclaw config set agents.list '[
  {"id":"main","default":true,"workspace":"/Users/you/jarvis"},
  {"id":"orgbot","name":"orgbot","workspace":"/Users/you/openclaw-orgbot-agent","identity":{"name":"OnCallBot","emoji":"🚨"}}
]' --strict-json
```

`agents.list` is an array; `openclaw config set` replaces it wholesale, so pass the full list including every existing agent (preview the current list with `openclaw config get agents.list`). `identity.name` is what Roam users see as the agent's display name in chat.

### 4. Register the Roam account and bind it to the agent

```bash
ACCOUNT_ID=orgbot

openclaw config set channels.roam.accounts.$ACCOUNT_ID.apiKey         "$ORG_TOKEN"
openclaw config set channels.roam.accounts.$ACCOUNT_ID.webhookSecret  "$ORG_WHSEC"
openclaw config set channels.roam.accounts.$ACCOUNT_ID.webhookUrl     https://YOUR_HOST/roam-webhook-$ACCOUNT_ID
openclaw config set channels.roam.accounts.$ACCOUNT_ID.enabled        true
```

The webhook path **must** be `/roam-webhook-<accountId>` for non-default accounts (the plugin registers exactly one listener per account at that path). You can reuse the same public host as another bot — both terminate at the same gateway, on different paths.

Bind the new account to the new agent (so inbound from this account routes to its own agent rather than falling through to the default):

```bash
openclaw config set bindings '[
  {"agentId":"orgbot","comment":"OnCallBot Roam account","match":{"channel":"roam","accountId":"orgbot"}}
]' --strict-json
```

`bindings` is a top-level config array; same rule as `agents.list` — `config set` replaces, so include every existing binding.

Apply:

```bash
openclaw daemon restart
```

In `openclaw logs`, you should see a `Roam webhook monitor` start and a `Roam webhooks subscribed` line for the new account. The `Roam bot persona` line confirms the bot identity the plugin will use for self-message filtering:

```
[orgbot]  Roam bot persona: OnCallBot  (rmk-… bot id)
```

### 5. Pick a channel response mode

An Org Bot in a channel can respond in one of two modes:

- **Proactive** — bot replies to *every* message in the channel. Best for narrow-purpose channels (e.g. an `#alerts` channel where every message is something the bot should triage). Requires `requireMention: false` and that the bot be a member of the group.
- **Mention-only** — bot replies only when @-mentioned. Best for general channels where the bot is one tool among many.

Out of the box, the bot answers in any group it can hear (defaults are `groupPolicy: "open"` and mention-only). If that's what you want, you're done — add the bot to the group in Roam and it'll respond to @-mentions there.

To opt into **proactive** mode for a specific group, add an entry under the org account's `groups` map:

```bash
openclaw config patch --stdin <<EOF
{
  "channels": {
    "roam": {
      "accounts": {
        "$ACCOUNT_ID": {
          "groups": {
            "GROUP_ID": { "requireMention": false, "replyInThread": true }
          }
        }
      }
    }
  }
}
EOF

openclaw daemon restart
```

`GROUP_ID` is the group's ID, found at the bottom of the group's **Group Settings** page in Roam.

What each field does:

- `groups.GROUP_ID` — per-group overrides for the named group. Anything you don't set falls back to defaults.
- `requireMention: false` — bot reacts to every message in this group, not only @-mentions. Default is `true` (mention-only).
- `replyInThread: true` — bot replies in a new thread under the triggering message (or the existing thread if the message is already in one). Strongly recommended for proactive bots to avoid cluttering the channel top-level.[^1]

Want stricter group access (only specific groups respond)? Set `groupPolicy: "allowlist"` and list groups explicitly — see [Restricting which users can talk to the bot](#restricting-which-users-can-talk-to-the-bot) below.

[^1]: `replyInThread` defaults to `false` today. A planned plugin change makes it default to `true` when `requireMention: false` so proactive bots get threading automatically; mention-only bots keep the default of replying at top level.

### 6. Give the bot a persona

A new bot has no behavior of its own — it'll respond like a generic assistant. Set `systemPromptOverride` on its agent profile so the bot knows who it is and what to do.

Because `agents.list` is an array (not a map keyed by id), there's no `openclaw config set agents.list[orgbot].systemPromptOverride` shorthand. Either edit `~/.openclaw/openclaw.json` directly, or pull-modify-push the array:

```bash
$EDITOR ~/.openclaw/openclaw.json
# Find the entry for your agentId, add "systemPromptOverride": "..."
openclaw daemon restart
```

Or:

```bash
openclaw config get agents.list > /tmp/agents.json
# Edit /tmp/agents.json
openclaw config set agents.list "$(cat /tmp/agents.json)" --strict-json
openclaw daemon restart
```

Here's a starter prompt for an SRE-style bot that investigates Datadog alerts:

```text
You are OpenClaw SRE, an on-call assistant living in a Roam channel. 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` MCP server: 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. If a message is clearly not an alert, respond only if you can help;
   otherwise stay silent.
7. Never claim a fix is applied. You investigate and report; humans act.

When asked who you are, say you are OpenClaw SRE.
```

### Wire up tools (e.g. Datadog MCP)

For a proactive investigator-style bot to be useful, give it tools. OpenClaw supports both stdio MCP servers (local processes) and HTTP MCP servers (remote endpoints). Configure them globally under `mcp.servers` and they become available to every agent — including your new one.

Datadog's remote MCP server, authed with API key + Application key in headers:

```bash
openclaw mcp set datadog '{
  "url": "https://mcp.datadoghq.com/api/unstable/mcp-server/mcp",
  "transport": "streamable-http",
  "headers": {
    "DD-API-KEY": "<your-api-key>",
    "DD-APPLICATION-KEY": "<your-application-key>"
  }
}'
openclaw daemon restart
```

Get the credentials in Datadog:

- **API key** — Organization Settings → API Keys → New Key.
- **Application key** — Personal Settings → Application Keys → New Key. Scope it to the read endpoints the bot needs (`monitors_read`, `metrics_read`, `logs_read`, `events_read`).

For the EU site, use `mcp.datadoghq.eu`; for other sites, see Datadog's regional URL list.

**Auth caveats:** OpenClaw's MCP config supports `command/args/env` (stdio) and `url/transport/headers` (HTTP), but doesn't run an interactive OAuth flow for remote MCP servers. If a server requires OAuth, run a local stdio MCP proxy that handles the OAuth dance and exposes the same tools over stdio.

### 7. Verify

1. **DM the bot.** Open a direct message with the new bot in Roam and send it a hello. It should reply with the persona you set.
2. **Add the bot to a group.** Go to **Group Settings** for the group you want it to live in and add the new bot as a member. (Required for private groups; for public groups, mentioning the bot from anywhere already triggers it.)
3. **Post a message.** In proactive mode, post any message — the bot replies in a new thread. In mention-only mode, @-mention the bot — same result.
4. **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.

If something feels off, two log filters worth knowing:

```bash
# Did this account see the message?
openclaw logs --follow --plain 2>&1 | grep "roam\[orgbot\]"

# Did the agent reach Datadog (or whatever MCP server)?
openclaw logs --follow --plain 2>&1 | grep -i "datadog\|mcp"
```

Drop-reason log lines prefix the account id (`roam[default]:` / `roam[orgbot]:`) so you can tell exactly which bot dropped a message and why.

### Caveats for proactive bots

- **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 `sender` against the bot's address ID, which is fetched at startup via `/v1/token.info`. If that call fails, the log will say `Could not fetch bot identity from token.info; self-message filtering disabled` — in that state, with `requireMention: false`, the bot **will** loop on its own replies. Fix the token.info issue (check token scopes and approval) before enabling proactive mode.

- **Every message in the channel triggers a turn.** If the channel is busy, agent runs queue up and tokens burn fast. Watch `agents.defaults.maxConcurrent` and consider adding a `systemPrompt` per-group that tells the agent when *not* to reply.

- **Session/memory model.** By default, the channel maps to a single session — the bot has full context of the channel as one long conversation. For high-traffic channels you may want shorter context windows or a different session-scoping strategy.

- **Tokens stored in plaintext.** OpenClaw stores the API key directly in `~/.openclaw/openclaw.json`. For shared-host deployments, consider using `apiKeyFile` instead (point at a file with mode `0600` that only the gateway user can read).

### Restricting which users can talk to the bot

Org bots are open to the workspace by default. To restrict them, opt into one or both allowlists:

**Restrict senders in DMs.** Set `dmPolicy: "allowlist"` (or `"pairing"` if you want a UX-driven approval flow) and list the user IDs allowed to DM the bot:

```bash
openclaw config set channels.roam.accounts.orgbot.dmPolicy allowlist
openclaw config set channels.roam.accounts.orgbot.allowFrom \
  '["USER_ID_1","USER_ID_2"]' --strict-json
openclaw daemon restart
```

**Restrict senders in groups.** Same shape, group-side:

```bash
openclaw config set channels.roam.accounts.orgbot.groupPolicy allowlist
openclaw config set channels.roam.accounts.orgbot.groupAllowFrom \
  '["USER_ID_1","USER_ID_2"]' --strict-json
openclaw daemon restart
```

**Restrict to specific groups.** Setting `groupPolicy: "allowlist"` also turns the `groups` map into a strict allowlist — only listed groups are allowed; unlisted groups are dropped silently.

`USER_ID` is a Roam user ID, not a group ID. Most setups don't need any of this — workspace membership is usually a sufficient boundary for Org Bots.

## Run multiple bots from one OpenClaw install

You can run a Personal Bot and an Org Bot (or any combination) side by side from one OpenClaw instance. Each is its own account in the channel config, with its own webhook path, its own token, and optionally its own agent profile.

Set up each bot using the matching section above. The pieces fit together as follows:

```text
channels.roam.accounts.default          ← Personal Bot (rmp-…)
channels.roam.accounts.orgbot           ← Org Bot      (rmk-…)
channels.roam.accounts.<anotherId>      ← additional bot

bindings: [
  { match: { channel: "roam", accountId: "orgbot" }, agentId: "orgbot" },
  { match: { channel: "roam", accountId: "anotherId" }, agentId: "another" },
  // unbound accounts fall through to the default agent
]

agents.list: [
  { id: "main", default: true, workspace: "/Users/you/jarvis" },
  { id: "orgbot", workspace: "/Users/you/openclaw-orgbot-agent", identity: {...} },
  { id: "another", workspace: "/Users/you/another-agent",      identity: {...} },
]
```

### Webhook paths

| Account            | Path                              |
| ------------------ | --------------------------------- |
| `default`          | `/roam-webhook`                   |
| Any named account  | `/roam-webhook-<accountId>`       |

All paths terminate at the same gateway port — the plugin de-multiplexes by path. Configure each in Roam separately when you create the token (the plugin auto-subscribes on startup using the `webhookUrl` you set).

### Routing inbound to the right agent

If you don't bind an account to an agent, inbound from that account goes to the default agent (`agents.list[].default: true`). That's often what you want for a Personal Bot — it shares your main agent's workspace and memory. For an Org Bot you almost always want a separate agent profile (its own workspace directory and persona) bound via `bindings`.

### Verifying all bots came up

After `openclaw daemon restart`, you should see one `Roam webhook monitor` start and one `Roam bot persona` line per account in `openclaw logs`:

```
[default]  Roam webhook monitor starting
[default]  Roam webhooks subscribed
[default]  Roam bot persona: You          (rmp-…)
[orgbot]   Roam webhook monitor starting
[orgbot]   Roam webhooks subscribed
[orgbot]   Roam bot persona: OnCallBot    (rmk-…)
```

If an account's monitor doesn't start, double-check that `enabled` isn't set to `false` and that the webhook URL/path is unique across accounts.

## Use Roam as a data source from another surface

If you don't need a chat experience in Roam — you're driving OpenClaw from Telegram, Slack, the terminal, etc. — and just want the agent to read or write Roam data (meetings, transcripts, chat history, sending the occasional message), install the **Roam skill** instead of the channel plugin.

```bash
clawhub install roam
```

Configure your token by either telling the agent to save it on first use, or setting it manually in `~/.openclaw/openclaw.json`:

```json
{
  "env": {
    "ROAM_API_KEY": "YOUR_API_TOKEN"
  }
}
```

Use either a Personal Access Token (acts as you) or an Org API Key (acts as the org bot persona) — same trade-offs as in [Pick your bot](#pick-your-bot). No restart needed; the environment is read per session.

### What you can ask the agent to do

Once the skill is installed, try:

- "Summarize my meetings from this week."
- "What did we decide about pricing in last week's meeting?"
- "What action items came out of the product review?"
- "Send a message to Engineering: deploy is complete."
- "Post the weekly standup summary to General."

The skill uses the Roam API to list meetings, retrieve transcripts, run AI-powered transcript analysis, send messages, and browse chat history. See the [API documentation](/docs/roam-api/roam-hq-api) for a complete endpoint reference.

### Skill + channel plugin together

If you install both the channel plugin and the skill, the plugin auto-exports `channels.roam.<account>.apiKey` as `ROAM_API_KEY` at runtime, so the skill picks up the same token as the bot — no separate API key setup needed.