Skip to content

Discord

Bridges Discord to the Workstacean bus. @mentions and slash commands become inbound bus messages; agent replies come back as Discord responses.

User @mentions Quinn in Discord
→ DiscordPlugin publishes message.inbound.discord.{channelId}
→ RouterPlugin routes to Quinn or Ava based on skillHint / keywords
→ Agent processes, publishes message.outbound.discord.{channelId}
→ DiscordPlugin sends Discord reply (👀 → ✅)
User runs /quinn bugs
→ DiscordPlugin publishes message.inbound.discord.slash.{interactionId}
→ RouterPlugin routes to Quinn (skillHint: bug_triage)
→ Agent replies
→ DiscordPlugin calls interaction.editReply()
VariableRequiredDescription
DISCORD_BOT_TOKENYesBot token (enables the plugin)
DISCORD_GUILD_IDYesGuild ID for slash command registration
DISCORD_DIGEST_CHANNELNoFallback channel ID for cron pushes (overridden by discord.yaml)

The plugin is automatically skipped if DISCORD_BOT_TOKEN is not set.

Place a discord.yaml in your workspace directory (default: workspace/discord.yaml). If absent, the plugin loads with empty commands and default moderation settings.

# ── Channel IDs ───────────────────────────────────────────────────────────────
channels:
# Default channel for cron-triggered posts (daily digest, etc.)
# Falls back to DISCORD_DIGEST_CHANNEL env var if blank.
digest: "1234567890123456789"
# Channel for new member welcome messages. Leave blank to disable.
welcome: ""
# Channel for moderation log. Leave blank to disable.
modLog: ""
# ── Moderation ────────────────────────────────────────────────────────────────
moderation:
rateLimit:
maxMessages: 5
windowSeconds: 10
spamPatterns:
- "free\\s*nitro"
- "discord\\.gift/"
- "@everyone.*https?://"
- "steamcommunity\\.com/gift"
# ── Slash commands ────────────────────────────────────────────────────────────
# Each command is registered to the guild on startup.
# subcommands[].content supports {optionName} interpolation.
# subcommands[].skillHint routes to a specific skill (optional).
#
# Option types: string | integer | boolean
commands:
- name: mybot
description: "My bot — project status and reports"
subcommands:
- name: status
description: "Current project status"
content: "/status"
skillHint: qa_report
- name: report
description: "Generate a report for a version"
content: "/report {version}"
skillHint: qa_report
options:
- name: version
description: "Version tag (e.g. v1.2.0)"
type: string
required: false
FieldDescription
digestChannel ID for message.outbound.discord.push.* and cron-triggered posts
welcomeChannel ID for new member welcome messages
modLogReserved for future moderation logging
FieldDescription
maxMessagesMax messages per user within the window
windowSecondsRolling window in seconds

Array of regex strings (escaped for YAML). Matched case-insensitively. Matching messages are silently deleted.

FieldRequiredDescription
nameYesSubcommand name (lowercase, no spaces)
descriptionYesShown in Discord’s command picker
contentYesText sent as the message payload. Use {optionName} for interpolation.
skillHintNoTells RouterPlugin which skill to route to
optionsNoSlash command options (see below)
FieldRequiredDescription
nameYesOption name — also the interpolation key in content
descriptionYesShown in Discord
typeYesstring, integer, or boolean
requiredNoDefaults to false

Commands can use top-level options instead of subcommands to get Discord’s autocomplete UX. Set autocomplete: true on any string option to enable live filtering.

When project is an autocomplete option, the plugin loads projects.yaml and returns matching projects as choices (filtered by slug or title). On submission, the project slug is resolved to devChannelId (from discord.dev) and projectRepo (from the github field) and included in the bus payload.

commands:
- name: report-bug
description: Report a bug against a project
options:
- name: project
description: Project to report against (start typing to filter)
type: string
required: true
autocomplete: true
- name: description
description: Brief description of the bug
type: string
required: true
content: "Bug report for {project}: {description}"
skillHint: bug_triage

commands[].options[] (flat/autocomplete commands)

Section titled “commands[].options[] (flat/autocomplete commands)”
FieldRequiredDescription
nameYesOption name — also the interpolation key in content
descriptionYesShown in Discord
typeYesstring, integer, or boolean
requiredNoDefaults to false
autocompleteNoWhen true, Discord sends autocomplete interactions. Only supported on string options. For project options, choices come from projects.yaml.

When project is resolved from projects.yaml, two extra fields are added to the inbound payload:

{
devChannelId?: string; // discord.dev channel ID for the matched project
projectRepo?: string; // GitHub full name (e.g. "protoLabsAI/protoUI")
}
TopicDirectionDescription
message.inbound.discord.{channelId}Inbound@mention or DM
message.inbound.discord.slash.{interactionId}InboundSlash command
message.outbound.discord.{channelId}OutboundReply to @mention or DM
message.outbound.discord.slash.{interactionId}OutboundReply to slash command
message.outbound.discord.push.{channelId}OutboundUnprompted push (cron, etc.)

Subscribe to message.outbound.discord.# to handle all outbound Discord delivery.

{
sender: string; // Discord user ID
channel: string; // Discord channel ID
content: string; // Cleaned message text (mentions stripped)
skillHint?: string; // Set by slash commands and 📋 reactions
isReaction?: boolean; // true when triggered by 📋 reaction
isThread?: boolean; // true when message is in a thread
guildId?: string; // null for DMs
}
{
content: string; // Reply text (truncated to 2000 chars)
channel?: string; // Channel ID for push messages (no correlationId)
}

For replies to @mentions and slash commands, match correlationId from the inbound message — no channel needed.

Reacting to any message with 📋 triggers a bug_triage skill request using the message content. Useful for quick bug filing from a chat log.

Cron events routed through RouterPlugin that include a channel in their payload are delivered to Discord via push:

workspace/crons/daily-digest.yaml
- name: daily-digest
schedule: "0 14 * * *"
topic: cron.daily-digest
payload:
content: "Generate the daily QA digest"
skillHint: qa_report
channel: "1234567890123456789"

If channel is omitted, the plugin falls back to channels.digest from discord.yaml, then to DISCORD_DIGEST_CHANNEL.

The bot requires the following:

Scopes: bot, applications.commands

Bot Permissions:

  • Read Messages / View Channels
  • Send Messages
  • Create Public Threads
  • Add Reactions
  • Manage Messages (for spam deletion)
  • Read Message History
  • Manage Guild Members intent (for welcome messages, if used)

Privileged Gateway Intents (enable in Discord Developer Portal):

  • Message Content Intent
  • Server Members Intent (only if using welcome channel)