Skip to content

Workflow Operator on Discord

Workflow Operator on Discord uses a Discord bot to map each Discord channel to a persistent Dagu AI agent session over Discord's gateway (WebSocket, no public HTTP endpoint required). Messages sent in Discord are forwarded to the built-in AI agent, and agent responses are posted back. When a DAG run completes, Workflow Operator can also send AI-generated notifications so follow-up stays in the same conversation.

Prerequisites

Before setting up Workflow Operator on Discord, configure the AI agent in the Web UI. Go to Agent Settings (/agent-settings) and set up the agent model, tool policy, and other defaults first. The Discord bot forwards messages to the built-in agent, so it must be configured before Discord can use it. Start with 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

  1. Go to https://discord.com/developers/applications and click New Application.
  2. Name it (e.g., Dagu) and click Create.

2. Create the bot user

  1. In the left sidebar, go to Bot.
  2. Click Reset Token (or Add Bot on older-style applications) and copy the token. This is the value for bots.discord.token in Dagu config, or the DAGU_BOTS_DISCORD_TOKEN environment 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:

  1. Scroll to Privileged Gateway Intents.
  2. Toggle MESSAGE CONTENT INTENT to ON.
  3. 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

  1. In the left sidebar, go to OAuth2 → URL Generator.
  2. Under Scopes, check bot and (optionally) applications.commands.
  3. Under Bot Permissions, check at least:
    • View Channels
    • Send Messages
    • Send Messages in Threads
    • Read Message History
    • Use Slash Commands (if you plan to add slash commands)
  4. Copy the generated URL, open it in a browser, pick the server, and authorize.

5. Finding your Channel ID

  1. In Discord, enable Developer Mode under User Settings → Advanced.
  2. Right-click the channel name and choose Copy Channel ID. The ID is a numeric string (e.g., 1234567890123456789). This is what you put in allowed_channel_ids.

Running

The Discord connector for Workflow Operator starts automatically when bots.provider is set to discord and you run either:

bash
dagu server

or

bash
dagu start-all

In 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.

yaml
bots:
  provider: discord
  safe_mode: true
  discord:
    token: "your-discord-bot-token"
    allowed_channel_ids:
      - "1234567890123456789"
      - "9876543210987654321"
    respond_to_all: true

bots fields

FieldTypeDefaultDescription
providerstring"" (disabled)Which connector to run. Set to "discord" for Discord. If empty, no bot starts.
safe_modebooltruePassed to the agent's ChatRequest.SafeMode field. Applies to all bot connectors.

bots.discord fields

FieldTypeDefaultDescription
tokenstring(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_allbooltrueWhen 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

VariableConfig equivalent
DAGU_BOTS_PROVIDERbots.provider
DAGU_BOTS_SAFE_MODEbots.safe_mode
DAGU_BOTS_DISCORD_TOKENbots.discord.token
DAGU_BOTS_DISCORD_ALLOWED_CHANNEL_IDSbots.discord.allowed_channel_ids
DAGU_BOTS_DISCORD_RESPOND_TO_ALLbots.discord.respond_to_all

Environment variables take precedence over the config file for token. For allowed_channel_ids, use a comma-separated string:

bash
export DAGU_BOTS_DISCORD_ALLOWED_CHANNEL_IDS=1234567890123456789,9876543210987654321

Event 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. With respond_to_all: true, every message is forwarded to the agent. With respond_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):

TextBehavior
newResets the channel's session state and clears the current agent session
cancelCancels 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

The bot tracks token usage across all messages in a session. When total tokens exceed 50% of the assumed context limit (200,000 tokens), the bot automatically:

  1. Collects the last 3 user/assistant exchanges from the old session (each truncated to 200/300 characters respectively).
  2. Resets the chat state.
  3. Creates a new session with the summary prepended to the user's message.
  4. Sends a notice: (Session context limit reached — continuing with recent context carried forward)

DAG Run Notifications

When the centralized event store is available (the default in both server and start-all modes), the bot starts a DAG run monitor that reads persisted DAG-run events and sends notifications. If the event store is disabled or unavailable, Discord chat works normally but DAG-run notifications stay disabled.

Monitored statuses

Notifications are sent for these DAG run statuses:

  • succeeded
  • failed
  • aborted
  • partially_succeeded
  • rejected
  • waiting (for human approval requests)

How notifications work

  1. The monitor polls the event store every 10 seconds and reads only new DAG-run events, using durable on-disk state so restarts do not lose pending notifications.
  2. On first startup, it seeds its cursor at the current event-store head, so existing channels only receive future events.
  3. For each new completion, 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).
  4. 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".
  5. 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>

Where the emoji is: succeeded/partial, failed/rejected, ⚠️ aborted, waiting.

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.

Released under the MIT License.