Thanks @tyler6204 Co-authored-by: Tyler Yust <64381258+tyler6204@users.noreply.github.com>
20 KiB
summary, read_when
| summary | read_when | |
|---|---|---|
| Discord bot support status, capabilities, and configuration |
|
Discord (Bot API)
Status: ready for DM and guild text channels via the official Discord bot gateway.
Quick setup (beginner)
- Create a Discord bot and copy the bot token.
- Set the token for Clawdbot:
- Env:
DISCORD_BOT_TOKEN=... - Or config:
channels.discord.token: "...". - If both are set, config takes precedence (env fallback is default-account only).
- Env:
- Invite the bot to your server with message permissions.
- Start the gateway.
- DM access is pairing by default; approve the pairing code on first contact.
Minimal config:
{
channels: {
discord: {
enabled: true,
token: "YOUR_BOT_TOKEN"
}
}
}
Goals
- Talk to Clawdbot via Discord DMs or guild channels.
- Direct chats collapse into the agent's main session (default
agent:main:main); guild channels stay isolated asagent:<agentId>:discord:channel:<channelId>(display names usediscord:<guildSlug>#<channelSlug>). - Group DMs are ignored by default; enable via
channels.discord.dm.groupEnabledand optionally restrict bychannels.discord.dm.groupChannels. - Keep routing deterministic: replies always go back to the channel they arrived on.
How it works
- Create a Discord application → Bot, enable the intents you need (DMs + guild messages + message content), and grab the bot token.
- Invite the bot to your server with the permissions required to read/send messages where you want to use it.
- Configure Clawdbot with
channels.discord.token(orDISCORD_BOT_TOKENas a fallback). - Run the gateway; it auto-starts the Discord channel when a token is available (config first, env fallback) and
channels.discord.enabledis notfalse.- If you prefer env vars, set
DISCORD_BOT_TOKEN(a config block is optional).
- If you prefer env vars, set
- Direct chats: use
user:<id>(or a<@id>mention) when delivering; all turns land in the sharedmainsession. Bare numeric IDs are ambiguous and rejected. - Guild channels: use
channel:<channelId>for delivery. Mentions are required by default and can be set per guild or per channel. - Direct chats: secure by default via
channels.discord.dm.policy(default:"pairing"). Unknown senders get a pairing code (expires after 1 hour); approve viaclawdbot pairing approve discord <code>.- To keep old “open to anyone” behavior: set
channels.discord.dm.policy="open"andchannels.discord.dm.allowFrom=["*"]. - To hard-allowlist: set
channels.discord.dm.policy="allowlist"and list senders inchannels.discord.dm.allowFrom. - To ignore all DMs: set
channels.discord.dm.enabled=falseorchannels.discord.dm.policy="disabled".
- To keep old “open to anyone” behavior: set
- Group DMs are ignored by default; enable via
channels.discord.dm.groupEnabledand optionally restrict bychannels.discord.dm.groupChannels. - Optional guild rules: set
channels.discord.guildskeyed by guild id (preferred) or slug, with per-channel rules. - Optional native commands:
commands.nativedefaults to"auto"(on for Discord/Telegram, off for Slack). Override withchannels.discord.commands.native: true|false|"auto";falseclears previously registered commands. Text commands are controlled bycommands.textand must be sent as standalone/...messages. Usecommands.useAccessGroups: falseto bypass access-group checks for commands.- Full command list + config: Slash commands
- Optional guild context history: set
channels.discord.historyLimit(default 20, falls back tomessages.groupChat.historyLimit) to include the last N guild messages as context when replying to a mention. Set0to disable. - Reactions: the agent can trigger reactions via the
discordtool (gated bychannels.discord.actions.*).- Reaction removal semantics: see /tools/reactions.
- The
discordtool is only exposed when the current channel is Discord.
- Native commands use isolated session keys (
agent:<agentId>:discord:slash:<userId>) rather than the sharedmainsession.
Note: Name → id resolution uses guild member search and requires Server Members Intent; if the bot can’t search members, use ids or <@id> mentions.
Note: Slugs are lowercase with spaces replaced by -. Channel names are slugged without the leading #.
Note: Guild context [from:] lines include author.tag + id to make ping-ready replies easy.
Config writes
By default, Discord is allowed to write config updates triggered by /config set|unset (requires commands.config: true).
Disable with:
{
channels: { discord: { configWrites: false } }
}
How to create your own bot
This is the “Discord Developer Portal” setup for running Clawdbot in a server (guild) channel like #help.
1) Create the Discord app + bot user
- Discord Developer Portal → Applications → New Application
- In your app:
- Bot → Add Bot
- Copy the Bot Token (this is what you put in
DISCORD_BOT_TOKEN)
2) Enable the gateway intents Clawdbot needs
Discord blocks “privileged intents” unless you explicitly enable them.
In Bot → Privileged Gateway Intents, enable:
- Message Content Intent (required to read message text in most guilds; without it you’ll see “Used disallowed intents” or the bot will connect but not react to messages)
- Server Members Intent (recommended; required for some member/user lookups and allowlist matching in guilds)
You usually do not need Presence Intent.
3) Generate an invite URL (OAuth2 URL Generator)
In your app: OAuth2 → URL Generator
Scopes
- ✅
bot - ✅
applications.commands(required for native commands)
Bot Permissions (minimal baseline)
- ✅ View Channels
- ✅ Send Messages
- ✅ Read Message History
- ✅ Embed Links
- ✅ Attach Files
- ✅ Add Reactions (optional but recommended)
- ✅ Use External Emojis / Stickers (optional; only if you want them)
Avoid Administrator unless you’re debugging and fully trust the bot.
Copy the generated URL, open it, pick your server, and install the bot.
4) Get the ids (guild/user/channel)
Discord uses numeric ids everywhere; Clawdbot config prefers ids.
- Discord (desktop/web) → User Settings → Advanced → enable Developer Mode
- Right-click:
- Server name → Copy Server ID (guild id)
- Channel (e.g.
#help) → Copy Channel ID - Your user → Copy User ID
5) Configure Clawdbot
Token
Set the bot token via env var (recommended on servers):
DISCORD_BOT_TOKEN=...
Or via config:
{
channels: {
discord: {
enabled: true,
token: "YOUR_BOT_TOKEN"
}
}
}
Multi-account support: use channels.discord.accounts with per-account tokens and optional name. See gateway/configuration for the shared pattern.
Allowlist + channel routing
Example “single server, only allow me, only allow #help”:
{
channels: {
discord: {
enabled: true,
dm: { enabled: false },
guilds: {
"YOUR_GUILD_ID": {
users: ["YOUR_USER_ID"],
requireMention: true,
channels: {
help: { allow: true, requireMention: true }
}
}
},
retry: {
attempts: 3,
minDelayMs: 500,
maxDelayMs: 30000,
jitter: 0.1
}
}
}
}
Notes:
requireMention: truemeans the bot only replies when mentioned (recommended for shared channels).agents.list[].groupChat.mentionPatterns(ormessages.groupChat.mentionPatterns) also count as mentions for guild messages.- Multi-agent override: set per-agent patterns on
agents.list[].groupChat.mentionPatterns. - If
channelsis present, any channel not listed is denied by default. - Use a
"*"channel entry to apply defaults across all channels; explicit channel entries override the wildcard. - Threads inherit parent channel config (allowlist,
requireMention, skills, prompts, etc.) unless you add the thread channel id explicitly. - Bot-authored messages are ignored by default; set
channels.discord.allowBots=trueto allow them (own messages remain filtered). - Warning: If you allow replies to other bots (
channels.discord.allowBots=true), prevent bot-to-bot reply loops withrequireMention,channels.discord.guilds.*.channels.<id>.usersallowlists, and/or clear guardrails inAGENTS.mdandSOUL.md.
6) Verify it works
- Start the gateway.
- In your server channel, send:
@Krill hello(or whatever your bot name is). - If nothing happens: check Troubleshooting below.
Troubleshooting
- First: run
clawdbot doctorandclawdbot channels status --probe(actionable warnings + quick audits). - “Used disallowed intents”: enable Message Content Intent (and likely Server Members Intent) in the Developer Portal, then restart the gateway.
- Bot connects but never replies in a guild channel:
- Missing Message Content Intent, or
- The bot lacks channel permissions (View/Send/Read History), or
- Your config requires mentions and you didn’t mention it, or
- Your guild/channel allowlist denies the channel/user.
requireMention: falsebut still no replies:channels.discord.groupPolicydefaults to allowlist; set it to"open"or add a guild entry underchannels.discord.guilds(optionally list channels underchannels.discord.guilds.<id>.channelsto restrict).- If you only set
DISCORD_BOT_TOKENand never create achannels.discordsection, the runtime defaultsgroupPolicytoopen. Addchannels.discord.groupPolicy,channels.defaults.groupPolicy, or a guild/channel allowlist to lock it down.
- If you only set
requireMentionmust live underchannels.discord.guilds(or a specific channel).channels.discord.requireMentionat the top level is ignored.- Permission audits (
channels status --probe) only check numeric channel IDs. If you use slugs/names aschannels.discord.guilds.*.channelskeys, the audit can’t verify permissions. - DMs don’t work:
channels.discord.dm.enabled=false,channels.discord.dm.policy="disabled", or you haven’t been approved yet (channels.discord.dm.policy="pairing").
Capabilities & limits
- DMs and guild text channels (threads are treated as separate channels; voice not supported).
- Typing indicators sent best-effort; message chunking uses
channels.discord.textChunkLimit(default 2000) and splits tall replies by line count (channels.discord.maxLinesPerMessage, default 17). - Optional newline chunking: set
channels.discord.chunkMode="newline"to split on blank lines (paragraph boundaries) before length chunking. - File uploads supported up to the configured
channels.discord.mediaMaxMb(default 8 MB). - Mention-gated guild replies by default to avoid noisy bots.
- Reply context is injected when a message references another message (quoted content + ids).
- Native reply threading is off by default; enable with
channels.discord.replyToModeand reply tags.
Retry policy
Outbound Discord API calls retry on rate limits (429) using Discord retry_after when available, with exponential backoff and jitter. Configure via channels.discord.retry. See Retry policy.
Config
{
channels: {
discord: {
enabled: true,
token: "abc.123",
groupPolicy: "allowlist",
guilds: {
"*": {
channels: {
general: { allow: true }
}
}
},
mediaMaxMb: 8,
actions: {
reactions: true,
stickers: true,
emojiUploads: true,
stickerUploads: true,
polls: true,
permissions: true,
messages: true,
threads: true,
pins: true,
search: true,
memberInfo: true,
roleInfo: true,
roles: false,
channelInfo: true,
channels: true,
voiceStatus: true,
events: true,
moderation: false
},
replyToMode: "off",
dm: {
enabled: true,
policy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["123456789012345678", "steipete"],
groupEnabled: false,
groupChannels: ["clawd-dm"]
},
guilds: {
"*": { requireMention: true },
"123456789012345678": {
slug: "friends-of-clawd",
requireMention: false,
reactionNotifications: "own",
users: ["987654321098765432", "steipete"],
channels: {
general: { allow: true },
help: {
allow: true,
requireMention: true,
users: ["987654321098765432"],
skills: ["search", "docs"],
systemPrompt: "Keep answers short."
}
}
}
}
}
}
}
Ack reactions are controlled globally via messages.ackReaction +
messages.ackReactionScope. Use messages.removeAckAfterReply to clear the
ack reaction after the bot replies.
dm.enabled: setfalseto ignore all DMs (defaulttrue).dm.policy: DM access control (pairingrecommended)."open"requiresdm.allowFrom=["*"].dm.allowFrom: DM allowlist (user ids or names). Used bydm.policy="allowlist"and fordm.policy="open"validation. The wizard accepts usernames and resolves them to ids when the bot can search members.dm.groupEnabled: enable group DMs (defaultfalse).dm.groupChannels: optional allowlist for group DM channel ids or slugs.groupPolicy: controls guild channel handling (open|disabled|allowlist);allowlistrequires channel allowlists.guilds: per-guild rules keyed by guild id (preferred) or slug.guilds."*": default per-guild settings applied when no explicit entry exists.guilds.<id>.slug: optional friendly slug used for display names.guilds.<id>.users: optional per-guild user allowlist (ids or names).guilds.<id>.channels.<channel>.allow: allow/deny the channel whengroupPolicy="allowlist".guilds.<id>.channels.<channel>.requireMention: mention gating for the channel.guilds.<id>.channels.<channel>.users: optional per-channel user allowlist.guilds.<id>.channels.<channel>.skills: skill filter (omit = all skills, empty = none).guilds.<id>.channels.<channel>.systemPrompt: extra system prompt for the channel (combined with channel topic).guilds.<id>.channels.<channel>.enabled: setfalseto disable the channel.guilds.<id>.channels: channel rules (keys are channel slugs or ids).guilds.<id>.requireMention: per-guild mention requirement (overridable per channel).guilds.<id>.reactionNotifications: reaction system event mode (off,own,all,allowlist).textChunkLimit: outbound text chunk size (chars). Default: 2000.chunkMode:length(default) splits only when exceedingtextChunkLimit;newlinesplits on blank lines (paragraph boundaries) before length chunking.maxLinesPerMessage: soft max line count per message. Default: 17.mediaMaxMb: clamp inbound media saved to disk.historyLimit: number of recent guild messages to include as context when replying to a mention (default 20; falls back tomessages.groupChat.historyLimit;0disables).dmHistoryLimit: DM history limit in user turns. Per-user overrides:dms["<user_id>"].historyLimit.retry: retry policy for outbound Discord API calls (attempts, minDelayMs, maxDelayMs, jitter).actions: per-action tool gates; omit to allow all (setfalseto disable).reactions(covers react + read reactions)stickers,emojiUploads,stickerUploads,polls,permissions,messages,threads,pins,searchmemberInfo,roleInfo,channelInfo,voiceStatus,eventschannels(create/edit/delete channels + categories + permissions)roles(role add/remove, defaultfalse)moderation(timeout/kick/ban, defaultfalse)
Reaction notifications use guilds.<id>.reactionNotifications:
off: no reaction events.own: reactions on the bot's own messages (default).all: all reactions on all messages.allowlist: reactions fromguilds.<id>.userson all messages (empty list disables).
Tool action defaults
| Action group | Default | Notes |
|---|---|---|
| reactions | enabled | React + list reactions + emojiList |
| stickers | enabled | Send stickers |
| emojiUploads | enabled | Upload emojis |
| stickerUploads | enabled | Upload stickers |
| polls | enabled | Create polls |
| permissions | enabled | Channel permission snapshot |
| messages | enabled | Read/send/edit/delete |
| threads | enabled | Create/list/reply |
| pins | enabled | Pin/unpin/list |
| search | enabled | Message search (preview feature) |
| memberInfo | enabled | Member info |
| roleInfo | enabled | Role list |
| channelInfo | enabled | Channel info + list |
| channels | enabled | Channel/category management |
| voiceStatus | enabled | Voice state lookup |
| events | enabled | List/create scheduled events |
| roles | disabled | Role add/remove |
| moderation | disabled | Timeout/kick/ban |
replyToMode:off(default),first, orall. Applies only when the model includes a reply tag.
Reply tags
To request a threaded reply, the model can include one tag in its output:
[[reply_to_current]]— reply to the triggering Discord message.[[reply_to:<id>]]— reply to a specific message id from context/history. Current message ids are appended to prompts as[message_id: …]; history entries already include ids.
Behavior is controlled by channels.discord.replyToMode:
off: ignore tags.first: only the first outbound chunk/attachment is a reply.all: every outbound chunk/attachment is a reply.
Allowlist matching notes:
allowFrom/users/groupChannelsaccept ids, names, tags, or mentions like<@id>.- Prefixes like
discord:/user:(users) andchannel:(group DMs) are supported. - Use
*to allow any sender/channel. - When
guilds.<id>.channelsis present, channels not listed are denied by default. - When
guilds.<id>.channelsis omitted, all channels in the allowlisted guild are allowed. - To allow no channels, set
channels.discord.groupPolicy: "disabled"(or keep an empty allowlist). - The configure wizard accepts
Guild/Channelnames (public + private) and resolves them to IDs when possible. - On startup, Clawdbot resolves channel/user names in allowlists to IDs (when the bot can search members) and logs the mapping; unresolved entries are kept as typed.
Native command notes:
- The registered commands mirror Clawdbot’s chat commands.
- Native commands honor the same allowlists as DMs/guild messages (
channels.discord.dm.allowFrom,channels.discord.guilds, per-channel rules). - Slash commands may still be visible in Discord UI to users who aren’t allowlisted; Clawdbot enforces allowlists on execution and replies “not authorized”.
Tool actions
The agent can call discord with actions like:
react/reactions(add or list reactions)sticker,poll,permissionsreadMessages,sendMessage,editMessage,deleteMessage- Read/search/pin tool payloads include normalized
timestampMs(UTC epoch ms) andtimestampUtcalongside raw Discordtimestamp. threadCreate,threadList,threadReplypinMessage,unpinMessage,listPinssearchMessages,memberInfo,roleInfo,roleAdd,roleRemove,emojiListchannelInfo,channelList,voiceStatus,eventList,eventCreatetimeout,kick,ban
Discord message ids are surfaced in the injected context ([discord message id: …] and history lines) so the agent can target them.
Emoji can be unicode (e.g., ✅) or custom emoji syntax like <:party_blob:1234567890>.
Safety & ops
- Treat the bot token like a password; prefer the
DISCORD_BOT_TOKENenv var on supervised hosts or lock down the config file permissions. - Only grant the bot permissions it needs (typically Read/Send Messages).
- If the bot is stuck or rate limited, restart the gateway (
clawdbot gateway --force) after confirming no other processes own the Discord session.