Workflow Operator on Discord
Workflow Operator on Discord connects Discord channels and DMs to the built-in Dagu agent over Discord's gateway. Each conversation keeps its own running context, so follow-up questions and DAG-run notifications stay in the same place.
Prerequisites
Before setting up Workflow Operator on Discord, configure AI agent in the Web UI. Go to AI agent Settings (/agent-settings) and set up the model, tool policy, and other defaults first. The Discord bot forwards messages to the built-in steward, so it must be configured before Discord can use it. Start with AI agent Settings, then use Models & Providers and Tool Permissions & Bash Policy for the concrete setup pieces.
Creating a Discord App
Before configuring Dagu, you need to create a Discord application and bot user.
1. Create the application
- Go to https://discord.com/developers/applications and click New Application.
- Name it (e.g.,
Dagu) and click Create.
2. Create the bot user
- In the left sidebar, go to Bot.
- Click Reset Token (or Add Bot on older-style applications) and copy the token. This is the value for
bots.discord.tokenin Dagu config, or theDAGU_BOTS_DISCORD_TOKENenvironment variable. Treat it like a password — it cannot be retrieved again once you leave the page.
3. Enable privileged intents
The bot needs to read message content. In the Bot tab:
- Scroll to Privileged Gateway Intents.
- Toggle MESSAGE CONTENT INTENT to ON.
- Click Save Changes.
Without this intent, the bot will receive message events but Content will be empty for messages that do not @mention it, and respond_to_all will not work.
4. Invite the bot to your server
- In the left sidebar, go to OAuth2 → URL Generator.
- Under Scopes, check bot and (optionally) applications.commands.
- Under Bot Permissions, check at least:
View ChannelsSend MessagesSend Messages in ThreadsRead Message HistoryUse Slash Commands(if you plan to add slash commands)
- Copy the generated URL, open it in a browser, pick the server, and authorize.
5. Finding your Channel ID
- In Discord, enable Developer Mode under User Settings → Advanced.
- Right-click the channel name and choose Copy Channel ID. The ID is a numeric string (e.g.,
1234567890123456789). This is what you put inallowed_channel_ids.
Running
The Discord connector for Workflow Operator starts automatically when bots.provider is set to discord and you run either:
dagu serveror
dagu start-allIn both modes, the connector shares the server's agent API instance.
Configuration
Set provider: discord under bots and configure the Discord-specific fields. Only one connector can be active at a time.
bots:
provider: discord
safe_mode: true
discord:
token: "your-discord-bot-token"
allowed_channel_ids:
- "1234567890123456789"
- "9876543210987654321"
respond_to_all: truebots fields
| Field | Type | Default | Description |
|---|---|---|---|
provider | string | "" (disabled) | Which connector to run. Set to "discord" for Discord. If empty, no bot starts. |
safe_mode | bool | true | Passed to the agent's ChatRequest.SafeMode field. Applies to all bot connectors. |
bots.discord fields
| Field | Type | Default | Description |
|---|---|---|---|
token | string | (required) | Bot token from the Discord Developer Portal. |
allowed_channel_ids | []string | (required) | Discord channel IDs authorized to use the bot. Messages from other guild channels are silently ignored. Direct messages to the bot are always handled regardless of this list. |
respond_to_all | bool | true | When true, the bot responds to every message in allowed guild channels. When false, the bot only responds to messages that @mention it. DMs are always handled regardless of this setting. |
Environment variables
| Variable | Config equivalent |
|---|---|
DAGU_BOTS_PROVIDER | bots.provider |
DAGU_BOTS_SAFE_MODE | bots.safe_mode |
DAGU_BOTS_DISCORD_TOKEN | bots.discord.token |
DAGU_BOTS_DISCORD_ALLOWED_CHANNEL_IDS | bots.discord.allowed_channel_ids |
DAGU_BOTS_DISCORD_RESPOND_TO_ALL | bots.discord.respond_to_all |
Environment variables take precedence over the config file for token. For allowed_channel_ids, use a comma-separated string:
export DAGU_BOTS_DISCORD_ALLOWED_CHANNEL_IDS=1234567890123456789,9876543210987654321Event Handling
The bot subscribes to Discord's gateway and handles two types of events:
Messages (MessageCreate)
- DMs (
GuildID == ""): always handled. - Guild channel messages: only handled when the channel ID is in
allowed_channel_ids. Withrespond_to_all: true, every message is forwarded to the agent. Withrespond_to_all: false, only messages that @mention the bot are handled — the<@BOT_ID>prefix is stripped from the text before it reaches the agent.
Messages authored by bots (including this bot itself) are ignored to avoid loops.
Interactive Components (Button Presses)
When the agent emits a UserPrompt with options, the bot renders it as a message with one or more ActionsRow components containing Button elements. Each button's custom_id is formatted as prompt:<promptID>:<optionID>.
When a user clicks a button, the bot submits the selected option to the agent and updates the original message in-place (via InteractionResponseUpdateMessage) to show the selection and remove the now-consumed buttons.
Button labels are truncated to 80 characters (Discord's limit for button labels). Up to 5 buttons are shown per row, and up to 5 rows per message — prompts with more than 25 options have the extras silently dropped.
If the prompt allows free text (AllowFreeText: true), the user can also reply with a regular text message instead of clicking a button.
Text Commands
Type these as plain messages (or after @mentioning the bot when respond_to_all is false):
| Text | Behavior |
|---|---|
new | Starts a fresh conversation in the current channel |
cancel | Cancels the currently active agent session |
Any text starting with new or cancel is treated as a command. All other text is forwarded to the agent.
Session Rotation
When a conversation gets close to the model's context limit, Dagu automatically rolls it over to a fresh behind-the-scenes session and carries forward a short summary. Users can keep chatting in the same Discord channel or DM without doing anything manually.
DAG Run Notifications
When event tracking is available (the default in both server and start-all modes), the bot can send DAG-run notifications into Discord. If event tracking is unavailable, Discord chat still works; only automatic run notifications are skipped.
Monitored statuses
Notifications are sent for these DAG run statuses:
failedabortedrejectedwaiting(for human approval requests)
Successful and partially successful completions are tracked only to suppress stale pending notifications. They do not post a Discord message.
How notifications work
- Dagu checks for new run events every 10 seconds and remembers what it has already delivered, so restarts do not resend old notifications or drop pending ones.
- On first startup, existing channels only receive future events.
- For each notification that should be delivered, it creates a dedicated agent session per allowed channel, sends a structured prompt with the run details (DAG name, status, error, start/finish times, step results), and waits for the agent to generate a notification message (up to 10 minutes).
- The notification session is adopted as the channel's active session, so users can send follow-up messages like "show me the logs" or "retry it".
- Delivered entries are retained for 2 hours to suppress duplicate event replays, while failed deliveries remain pending and are retried.
Fallback
If the agent API is unavailable or times out, the bot sends a plain text fallback:
<emoji> DAG '<name>' <status>
Error: <error message if any>The fallback uses status-appropriate text and emoji.
Message Length
Messages longer than 2,000 characters are split at paragraph boundaries (\n\n), falling back to line boundaries (\n) if no paragraph break exists in the second half of the text. Discord's message limit for bot content is 2,000 characters per message.
User Identity
All Discord users are mapped to agent sessions with auth.RoleAdmin. The user ID format is discord:<channel_id> — the session is scoped to the channel, not to an individual user, so everyone posting in an allowed channel shares the same agent session.
