Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Discord

Run your ZeroClaw agent as a Discord bot. This guide walks you through it click by click, no prior bot experience needed. By the end you’ll have a bot sitting in your server that replies when people talk to it.

Who can talk to the agent

Inbound senders are gated against the peer set resolved for the bound agent, drawn from the peer_groups config the agent belongs to. Matching strips a leading @ and is case-insensitive against the channel’s native sender identifier. An empty set denies everyone; a set containing "*" accepts anyone; otherwise only the listed external peers (and peer agents) are accepted. This is separate from gateway pairing (gateway.require_pairing), which authenticates HTTP/WebSocket clients, not chat-channel senders.

A peer group for discord sets channel to discord, lists the allowed senders in external_peers (for discord, the Discord user ID (snowflake); ["*"] accepts anyone), optionally names peer agents for cross-agent dispatch, an ignore blocklist, and an output_modality (mirror, voice, or text). See Peer Groups for the field reference.

Where to set this:

Gateway dashboard

Open /config/peer_groups in the web dashboard.

zerocode

In the Config pane, under Peer groups.

Quickstart

Five steps: make the bot, copy its token, turn on two switches, invite it, and start ZeroClaw.

1. Create the bot

  1. Go to the Discord Developer Portal.
  2. Click New Application, give it a name, and Create.
  3. In the left sidebar, click Bot.
  4. Click Reset Token, then Copy. This long string is your bot_token. Keep it somewhere safe for step 3, you cannot see it again later (only reset it).

The bot token is a password for your bot. Anyone who has it can control your bot. Never paste it into a public chat, screenshot, or commit it to git.

2. Turn on the two switches the bot needs

Still on the Bot page, scroll to Privileged Gateway Intents and toggle both of these on:

  • Message Content Intent so the bot can read what people type.
  • Server Members Intent so it can see who is in the server.

Click Save Changes. If you skip this, the bot connects but never sees any messages, which is the single most common “my bot does nothing” cause.

3. Tell ZeroClaw about the bot

Put the token from step 1 into your config. The token is a secret, so set it through a surface that encrypts it rather than typing it into config.toml:

Gateway dashboard

Open /config/channels/discord in the web dashboard.

zerocode

In the Config pane, under Channels.

channels.discord.<alias>.bot_token is a secret. Stored encrypted, never in plain config.toml. Set it through one of these, which encrypt on write:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.bot_token field there.

zerocode

In the Config pane, set the channels.discord.<alias>.bot_token field (input is masked).

zeroclaw config

zeroclaw config set channels.discord.<alias>.bot_token    # prompts for masked input, stores encrypted

4. Invite the bot to your server

  1. Back in the Developer Portal, open OAuth2 -> URL Generator.
  2. Under Scopes, check bot.
  3. Under Bot Permissions, check at least Send Messages, Read Message History, and View Channels.
  4. Copy the URL at the bottom, open it in your browser, pick your server, and Authorize.

The bot now shows up in your member list (offline until you start ZeroClaw).

5. Start and test

Start ZeroClaw (zeroclaw service restart or zeroclaw daemon), then send a message in a channel the bot can see. It should reply. If it doesn’t, jump to Troubleshooting.

Configuration

The full field list, derived from the live schema. Most have sensible defaults; for a basic bot you only ever set bot_token.

approval_timeout_secs integer · default 300

Seconds to wait for operator approval on always_ask tools before auto-denying.

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.approval_timeout_secs field.

zerocode

In the Config pane, set the channels.discord.<alias>.approval_timeout_secs field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.approval_timeout_secs <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__approval_timeout_secs=
archive bool · default false

When true, the channel opens a sidecar discord.db SQLite memory backend, archives every non-bot message it sees, and registers the discord_search tool against it. Default: false. Folded in from the legacy [channels.discord-history] block.

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.archive field.

zerocode

In the Config pane, set the channels.discord.<alias>.archive field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.archive <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__archive=
bot_token* 🔑 secret · default

Discord bot token (from Discord Developer Portal).

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.bot_token field.

zerocode

In the Config pane, set the channels.discord.<alias>.bot_token field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.bot_token    # masked input, stored encrypted

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__bot_token=
channel_ids string[] · default []

Channel IDs to watch. Empty = watch every channel the bot can see. Used by the archive sidecar (when archive = true) and by the in-channel filter when set.

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.channel_ids field.

zerocode

In the Config pane, set the channels.discord.<alias>.channel_ids field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.channel_ids <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__channel_ids=
draft_update_interval_ms integer · default 1000

Minimum interval (ms) between draft message edits to avoid rate limits. Only used when stream_mode = "partial".

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.draft_update_interval_ms field.

zerocode

In the Config pane, set the channels.discord.<alias>.draft_update_interval_ms field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.draft_update_interval_ms <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__draft_update_interval_ms=
excluded_tools string[] · default []

Tools excluded from this channel’s tool spec. When set, these tools are not exposed to the model when responding via this channel.

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.excluded_tools field.

zerocode

In the Config pane, set the channels.discord.<alias>.excluded_tools field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.excluded_tools <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__excluded_tools=
guild_ids string[] · default []

Guild (server) IDs to restrict the bot to. Empty = listen across all guilds the bot is invited to. Migrated from the legacy guild_id singular field.

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.guild_ids field.

zerocode

In the Config pane, set the channels.discord.<alias>.guild_ids field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.guild_ids <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__guild_ids=
interrupt_on_new_message bool · default false

When true, a newer Discord message from the same sender in the same channel cancels the in-flight request and starts a fresh response with preserved history.

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.interrupt_on_new_message field.

zerocode

In the Config pane, set the channels.discord.<alias>.interrupt_on_new_message field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.interrupt_on_new_message <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__interrupt_on_new_message=
listen_to_bots bool · default false

When true, process messages from other bots (not just humans). The bot still ignores its own messages to prevent feedback loops.

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.listen_to_bots field.

zerocode

In the Config pane, set the channels.discord.<alias>.listen_to_bots field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.listen_to_bots <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__listen_to_bots=
mention_only bool · default false

When true, only respond to messages that @-mention the bot. Other messages in the guild are silently ignored.

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.mention_only field.

zerocode

In the Config pane, set the channels.discord.<alias>.mention_only field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.mention_only <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__mention_only=
multi_message_delay_ms integer · default 800

Delay (ms) between sending each message chunk in multi-message mode. Only used when stream_mode = "multi_message".

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.multi_message_delay_ms field.

zerocode

In the Config pane, set the channels.discord.<alias>.multi_message_delay_ms field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.multi_message_delay_ms <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__multi_message_delay_ms=
proxy_url string? · default null

Per-channel proxy URL (http, https, socks5, socks5h). Overrides the global [proxy] setting for this channel only.

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.proxy_url field.

zerocode

In the Config pane, set the channels.discord.<alias>.proxy_url field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.proxy_url <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__proxy_url=
reply_min_interval_secs integer · default 0

Per-(channel, recipient) outbound pacing floor in seconds. Range: 0..=REPLY_MIN_INTERVAL_MAX_SECS (0 disables).

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.reply_min_interval_secs field.

zerocode

In the Config pane, set the channels.discord.<alias>.reply_min_interval_secs field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.reply_min_interval_secs <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__reply_min_interval_secs=
reply_queue_depth_max integer · default 0

Per-(channel, recipient) outbound pacing queue depth. Range: 0..=REPLY_QUEUE_DEPTH_CEILING. When reply_min_interval_secs > 0 and this value is 0, the pacing wrapper substitutes DEFAULT_REPLY_QUEUE_DEPTH (16). When the queue is full, the newest send is dropped and a WARN is logged.

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.reply_queue_depth_max field.

zerocode

In the Config pane, set the channels.discord.<alias>.reply_queue_depth_max field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.reply_queue_depth_max <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__reply_queue_depth_max=
stall_timeout_secs integer · default 0

Stall-watchdog timeout in seconds. When non-zero, the bot will abort and retry if no progress is made within this duration. 0 = disabled.

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.stall_timeout_secs field.

zerocode

In the Config pane, set the channels.discord.<alias>.stall_timeout_secs field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.stall_timeout_secs <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__stall_timeout_secs=
stream_mode StreamMode · default "off"

Streaming mode for progressive response delivery. off (default): single message. partial: editable draft updates. multi_message: split response into separate messages at paragraph boundaries.

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.stream_mode field.

zerocode

In the Config pane, set the channels.discord.<alias>.stream_mode field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.stream_mode <value>

Environment variable

Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:

export ZEROCLAW_channels__discord__<alias>__stream_mode=

Narrowing where the bot listens

By default the bot listens in every server it’s invited to and every channel it can see. To scope it down:

  • guild_ids: limit the bot to specific servers (guilds). Empty means all.
  • channel_ids: limit it to specific channels. Empty means all visible.

To find an ID, enable Developer Mode in Discord (User Settings -> Advanced), then right-click a server or channel and Copy ID.

Threads and context

When a Discord conversation happens in a thread, that thread is its own conversation. ZeroClaw derives a distinct session key per thread, so every thread carries an independent context window and history: messages in one thread never bleed into another, and the agent does not see a sibling thread’s earlier turns. For Discord, threads are native channels, so each thread is already a separate conversation: no toggle to set.

  • Isolation is the point. Each thread’s context is self-contained: it does not leak outside the thread, and nothing from outside the thread leaks in. Parallel threads hold separate conversational state, so unrelated tasks never contaminate each other.
  • Long threads grow context. A thread accumulates history while it stays active, so a very long thread eventually fills the model’s context window like any other long conversation. Start a new thread to reset.
  • In-flight work is scoped per thread. A new message in one thread does not cancel an in-flight response in another; each thread’s task stands alone.

Set archive = true and the channel opens a sidecar discord.db memory store, records every message it sees, and registers a discord_search tool the agent can use to look up past conversation. Leave it off if you don’t need history search; the bot still replies normally either way.

Streaming

Discord streams replies via the stream_mode setting:

  • off (default): the whole reply posts as one message once the agent finishes. Simplest, and it never shows a half-written answer.
  • partial: the bot posts a draft immediately and edits it in place as the answer streams in. draft_update_interval_ms paces the edits; raise it if Discord rate-limits them.
  • multi_message: each paragraph posts as its own message, separated by multi_message_delay_ms. Good for long answers that would otherwise be one wall of text.

Set it on any surface:

Gateway dashboard

Open /config/channels/discord and set the channels.discord.<alias>.stream_mode field.

zerocode

In the Config pane, set the channels.discord.<alias>.stream_mode field.

zeroclaw config

zeroclaw config set channels.discord.<alias>.stream_mode <value>

Replies that feel natural

  • mention_only: when true, the bot only answers messages that @-mention it, so it stays quiet in busy channels.
  • reply_min_interval_secs: a minimum gap between replies to the same person, useful if instant responses feel robotic.

Troubleshooting

SymptomLikely causeFix
Bot is online but never repliesMessage Content Intent is offTurn it on in the Developer Portal (step 2) and restart
Bot replies nowhereNot invited, or missing View Channels / Send MessagesRe-run the invite (step 4) with the right permissions
Bot ignores most messagesmention_only = true@-mention the bot, or set it to false
“Invalid token” at startupToken mistyped or resetReset the token in the portal, set it again (step 3)

See also