Distinct Identities
Each agent appears as a separate bot with its own name, avatar, and presence status
Connect your herdctl agents to Discord, allowing users to interact with AI agents through chat messages and slash commands. Each agent appears as a distinct bot with its own identity, presence, and configuration.
herdctl uses a per-agent bot architecture where each Discord-enabled agent has its own Discord Application and bot token. This design provides several benefits:
Distinct Identities
Each agent appears as a separate bot with its own name, avatar, and presence status
Independent Configuration
Different agents can monitor different channels with different response modes
Scalable Architecture
Add new agents without affecting existing bot configurations
Clear User Experience
Users always know which agent they’re talking to by the bot’s identity
Discord Server├── #support│ └── @SupportBot responds to mentions (support-agent)├── #dev-chat│ └── @CoderBot responds to all messages (coder-agent)└── DMs └── @SupportBot auto-responds (support-agent)Each agent connects to Discord with its own bot account. When a user messages or mentions the bot, herdctl triggers a Claude session to respond.
| Mode | Behavior | Best For |
|---|---|---|
mention | Responds only when @mentioned | Shared channels where multiple bots exist |
auto | Responds to all messages | Dedicated support channels, DMs |
herdctl supports two different Discord integrations:
| Integration | Type | Purpose | Configuration |
|---|---|---|---|
| Chat | Two-way | Interactive conversations | chat.discord |
| Notification Hooks | One-way | Job completion alerts | hooks.after_run |
The chat integration documented on this page enables interactive, two-way conversations:
chat.discord section of agent configNotification hooks send one-way alerts when jobs complete:
hooks.after_run sectionExample notification hook:
hooks: after_run: - type: discord channel_id: "${DISCORD_CHANNEL_ID}" bot_token_env: DISCORD_BOT_TOKEN when: "metadata.shouldNotify"Before setting up Discord integration, ensure you have:
Open the Discord Developer Portal
Navigate to https://discord.com/developers/applications and sign in with your Discord account.
Create a New Application
Click the New Application button in the top-right corner.
Enter a name for your application. This name appears in OAuth authorization screens but not as the bot’s display name.
Navigate to the Bot Section
In the left sidebar, click Bot.
Configure the Bot Username
Click Edit next to the bot’s username. This is the name users will see in Discord.
Example names:
Support Bot for a customer support agentCode Assistant for a development agentMarketing Bot for a content agentSet the Bot Avatar (Optional)
Click on the bot’s avatar to upload a custom image. This helps users identify the bot.
Copy the Bot Token
Click Reset Token to generate a new token (or Copy if one exists).
Save the Token Securely
Add the token to your environment:
# Add to your shell profile or .env fileexport SUPPORT_DISCORD_TOKEN="your-bot-token-here"herdctl requires these Privileged Gateway Intents to function properly:
In your Discord Application, go to Bot settings
Scroll down to Privileged Gateway Intents
Enable the following intents:
| Intent | Required | Purpose |
|---|---|---|
| Message Content Intent | Yes | Read message text to respond to users |
| Server Members Intent | No | Not required for basic functionality |
| Presence Intent | No | Not required for basic functionality |
Click Save Changes
When inviting the bot, herdctl needs these permissions:
| Permission | Purpose |
|---|---|
Send Messages | Reply to user messages |
Read Message History | Build conversation context |
Use Slash Commands | Handle /help, /reset, /status commands |
View Channels | See channels the bot has access to |
The combined permission integer is: 2147551232
In your Discord Application, go to OAuth2 > URL Generator
Under Scopes, select:
botapplications.commandsUnder Bot Permissions, select:
Or use permission integer: 2147551232
Copy the generated URL at the bottom of the page
Open the URL in your browser
Select the server where you want to add the bot
Click Authorize
Complete the CAPTCHA if prompted
The bot will now appear in your server’s member list (offline until you start herdctl).
Configure Discord in your agent’s YAML file under the chat.discord section:
name: support-agentdescription: "Customer support agent"
chat: discord: bot_token_env: SUPPORT_DISCORD_TOKEN guilds: - id: "123456789012345678" channels: - id: "987654321098765432" name: "#support" mode: mentionchat: discord: # Environment variable containing the bot token (required) bot_token_env: SUPPORT_DISCORD_TOKEN
# Session expiry in hours (default: 24) session_expiry_hours: 24
# Log level: minimal, standard, verbose (default: standard) log_level: standard
# Optional: slash command registration mode # global = available everywhere (slower to propagate) # guild = faster updates for one guild (recommended for local dev) command_registration: scope: global # global | guild # guild_id: "123456789012345678" # required when scope: guild
# Output configuration - control what SDK messages appear in Discord output: tool_results: true # Show tool result embeds (default: true) tool_result_max_length: 900 # Max chars in tool output (default: 900, max: 1000) system_status: true # Show system status embeds (default: true) result_summary: true # Show completion summary embed (default: true) errors: true # Show error embeds (default: true) typing_indicator: true # Show typing indicator while processing (default: true)
# Bot presence/activity (optional) presence: activity_type: watching # playing, watching, listening, competing activity_message: "for support requests"
# Guild (server) configurations guilds: - id: "123456789012345678" # Discord server ID channels: - id: "987654321098765432" # Channel ID name: "#support" # For documentation (optional) mode: mention # mention or auto context_messages: 10 # Messages to include as context
- id: "111222333444555666" name: "#general" mode: auto context_messages: 5
# DM settings for this guild's members (optional) dm: enabled: true mode: auto allowlist: ["user-id-1", "user-id-2"] # Only these users (optional) blocklist: ["spam-user-id"] # Block specific users (optional)
# Global DM settings (applies to all DMs) dm: enabled: true mode: auto| Field | Type | Default | Description |
|---|---|---|---|
bot_token_env | string | — | Required. Environment variable name containing the bot token |
session_expiry_hours | number | 24 | Hours before a conversation session expires |
log_level | string | standard | Logging verbosity: minimal, standard, verbose |
command_registration | object | { scope: global } | Slash command registration mode (global or guild) |
presence | object | — | Bot presence/activity configuration |
guilds | array | — | List of Discord servers to operate in |
dm | object | — | Global DM configuration |
| Field | Type | Description |
|---|---|---|
activity_type | string | Activity type: playing, watching, listening, competing |
activity_message | string | Activity text shown in the bot’s status |
| Field | Type | Default | Description |
|---|---|---|---|
scope | string | global | global registers app commands globally; guild registers to one guild for faster propagation |
guild_id | string | — | Required when scope: guild; target guild for registration |
| Field | Type | Description |
|---|---|---|
id | string | Required. Discord server (guild) ID |
channels | array | Channels to monitor in this guild |
dm | object | DM settings for members of this guild |
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Required. Discord channel ID |
name | string | — | Human-readable name (for documentation) |
mode | string | mention | Response mode: mention or auto |
context_messages | number | 10 | Number of recent messages to include as context |
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Allow direct messages |
mode | string | auto | Response mode for DMs |
allowlist | string[] | — | Only allow DMs from these user IDs |
blocklist | string[] | — | Block DMs from these user IDs |
Control which SDK messages are surfaced in Discord. When your agent uses tools (Bash, Read, Write, etc.) or the SDK emits system/error messages, these settings determine what appears in the channel.
| Field | Type | Default | Description |
|---|---|---|---|
output.tool_results | boolean | true | Show tool result embeds when the agent uses tools |
output.tool_result_max_length | number | 900 | Maximum characters shown in tool output (max: 1000) |
output.system_status | boolean | true | Show system status embeds (e.g., “Compacting context…”) |
output.result_summary | boolean | true | Show a summary embed when the agent finishes (duration, cost, tokens) |
output.errors | boolean | true | Show error embeds when the SDK reports errors |
output.typing_indicator | boolean | true | Show typing indicator while processing. Set to false on long-running jobs to prevent Discord timeout errors |
All output types appear as compact Discord embeds with color coding:
| Message Type | Embed Color | Example Content |
|---|---|---|
| Tool result (success) | Blurple | 🔧 Bash — > git status with output preview |
| Tool result (error) | Red | 🔧 Bash — command output with error |
| System status | Gray | ⚙️ System — “Compacting context…” |
| Result summary | Green/Red | ✅ Task Complete — duration, turns, cost, token usage |
| Error | Red | ❌ Error — error description |
Tool result embeds include the tool name with an emoji, the input summary (command, file path, or search pattern), execution duration, output length, and a truncated preview of the output in a code block.
Minimal output (text responses only):
chat: discord: bot_token_env: DISCORD_BOT_TOKEN output: tool_results: false system_status: false errors: falseFull visibility (all message types):
chat: discord: bot_token_env: DISCORD_BOT_TOKEN output: tool_results: true tool_result_max_length: 500 system_status: true result_summary: true errors: trueherdctl uses environment variables for Discord tokens to keep secrets out of configuration files.
Use a consistent naming pattern for your bot tokens:
# Pattern: {AGENT_NAME}_DISCORD_TOKENexport SUPPORT_DISCORD_TOKEN="your-support-bot-token"export CODER_DISCORD_TOKEN="your-coder-bot-token"export MARKETING_DISCORD_TOKEN="your-marketing-bot-token"Add to ~/.bashrc, ~/.zshrc, or equivalent:
export SUPPORT_DISCORD_TOKEN="your-token-here"Then reload:
source ~/.bashrc # or ~/.zshrcCreate a .env file in your project root:
SUPPORT_DISCORD_TOKEN=your-token-hereCODER_DISCORD_TOKEN=another-token-hereLoad with dotenv or similar tooling.
On macOS/Linux:
export SUPPORT_DISCORD_TOKEN="your-token-here"On Windows (PowerShell):
$env:SUPPORT_DISCORD_TOKEN="your-token-here"In your agent YAML, reference the environment variable name (not the value):
chat: discord: bot_token_env: SUPPORT_DISCORD_TOKEN # The variable NAMEherdctl reads the token from process.env.SUPPORT_DISCORD_TOKEN at runtime.
Discord IDs are unique 17-19 digit numbers. You’ll need IDs for servers (guilds), channels, and users.
Open Discord (desktop or web app)
Go to User Settings (gear icon near your username)
Navigate to App Settings > Advanced
Enable Developer Mode
With Developer Mode enabled, right-click on any server, channel, or user to see a Copy ID option:
| Item | How to Copy |
|---|---|
| Server ID | Right-click the server icon in the sidebar > Copy Server ID |
| Channel ID | Right-click the channel name > Copy Channel ID |
| User ID | Right-click a user’s name > Copy User ID |
| Message ID | Right-click a message > Copy Message ID |
guilds: - id: "1234567890123456789" # Server ID (18 digits) channels: - id: "9876543210987654321" # Channel ID (19 digits)herdctl startLook for connection messages in the logs:
[support-agent] Connecting to Discord...[support-agent] Connected to Discord: SupportBot#1234[support-agent] Slash commands registered successfullyThe bot should appear online in your Discord server with the configured presence:
SupportBot - Watching for support requestsIn a channel configured with mode: mention:
You: @SupportBot How do I reset my password?SupportBot: To reset your password, follow these steps...In a channel configured with mode: auto:
You: How do I reset my password?SupportBot: To reset your password, follow these steps...herdctl automatically registers slash commands for every Discord-enabled agent:
| Command | Description |
|---|---|
/help | Show available commands and usage |
/ping | Quick health check |
/config | Show runtime-relevant agent configuration |
/tools | Show allowed/denied tools and MCP servers |
/usage | Show last run stats and cumulative session totals |
/skills | List discovered skills for this agent |
/skill | Trigger a skill (with autocomplete) |
/status | Show bot connection status and session info |
/session | Show current session and run state for this channel |
/reset | Clear conversation context and start fresh |
/new | Alias for starting a fresh conversation |
/stop | Stop the active run in this channel |
/cancel | Alias for /stop |
/retry | Retry the last prompt in this channel |
Discord slash commands are not regular chat messages. Type / in the message box, then pick the command under your bot’s app name in Discord’s command picker. Discord sends this as an interaction event (not a normal message), which herdctl handles via the command manager.
Try them in any channel where the bot is active:
/statusShows available commands and basic usage information:
/helpExample output:
🤖 Support Bot Commands
/help - Show this message/usage - Show latest run usage/skills - List discovered skills/status - Show bot status and stats/reset - Clear conversation context
Chat with me:• @SupportBot your question - In channels• Just type in DMs - Direct messagesShows detailed connection status and statistics:
/statusExample output:
🟢 Support Bot Status
Connected: YesUptime: 2h 34mSession: Active (expires in 21h)
Stats:• Messages received: 47• Responses sent: 43• Current channel: #supportClears the conversation context for the current channel:
/resetExample output:
✨ Conversation reset! Starting fresh.Use /reset when:
If DMs are enabled, send a direct message to the bot:
You (DM): Hello!SupportBot: Hello! How can I help you today?You can have multiple agents (each with their own bot) in the same Discord server. This is useful for specialized agents:
name: support-agentchat: discord: bot_token_env: SUPPORT_DISCORD_TOKEN guilds: - id: "123456789012345678" channels: - id: "support-channel-id" name: "#support" mode: mention
# agents/coder-agent.yamlname: coder-agentchat: discord: bot_token_env: CODER_DISCORD_TOKEN guilds: - id: "123456789012345678" # Same server channels: - id: "dev-channel-id" name: "#dev-chat" mode: autoUse distinct bot names and avatars - Help users identify which bot they’re addressing
Use mention mode in shared channels - Prevents bots from responding to each other or creating confusion
Dedicate channels when possible - Assign specific channels to specific bots with auto mode
Document bot purposes - Add channel topics explaining which bot handles what
| Scenario | Recommendation |
|---|---|
| Two bots in same channel | Use mode: mention for both |
| Bot in dedicated channel | Use mode: auto |
| Support + Dev bots | Separate channels or mention mode |
Discord chat integration maintains conversation context so your agent can have multi-turn conversations and “remember” what was discussed.
session_expiry_hours (default: 24 hours)When a user sends a message, the agent receives:
This allows natural conversations:
User: What's the current price of the Hyken chair?Bot: The Hyken chair is currently $189 at Staples.
User: When did you last check?Bot: I checked prices 2 hours ago at 10:30 AM.
User: Is that below my target?Bot: Yes! Your target is $200, so $189 is $11 below target.The bot remembers the chair and target price from earlier in the conversation.
chat: discord: bot_token_env: DISCORD_BOT_TOKEN session_expiry_hours: 24 # Default: 24 hoursChoose expiry based on your use case:
| Use Case | Recommended Expiry |
|---|---|
| Support bot | 24-48 hours |
| Quick Q&A | 1-4 hours |
| Long-running projects | 72+ hours |
| Stateless responses | 1 hour |
Users can clear their session context using /reset:
/resetThis is useful when:
Control how many recent messages are included as context:
guilds: - id: "123456789012345678" channels: - id: "987654321098765432" mode: mention context_messages: 10 # Include last 10 messagesMore context = better continuity but higher token usage.
| Scenario | Recommended Mode |
|---|---|
| Shared channel with humans | mention |
| Shared channel with other bots | mention |
| Dedicated support channel | auto |
| Direct messages | auto |
| High-traffic general channel | mention |
For multiple agents in the same server:
Dedicated channels: Give each bot its own channel with auto mode
#support → SupportBot (auto)#dev-help → CoderBot (auto)Shared channels: Use mention mode so users choose which bot to address
#general → SupportBot (mention) + CoderBot (mention)Open DMs (friendly bot):
dm: enabled: true mode: autoRestricted DMs (team only):
dm: enabled: true mode: auto allowlist: - "user-id-1" - "user-id-2"No DMs (channel-only):
dm: enabled: falseBlock specific users:
dm: enabled: true mode: auto blocklist: - "spam-user-id"Discord has rate limits on message sending. To avoid issues:
If you see rate limit warnings in logs, consider:
Cause: The environment variable specified in bot_token_env is not set or is empty.
Solution:
# Check if the variable is setecho $SUPPORT_DISCORD_TOKEN
# Set it if missingexport SUPPORT_DISCORD_TOKEN="your-token-here"Cause: Message Content Intent is not enabled in the Discord Developer Portal.
Solution:
Cause: The bot lacks required permissions in the channel.
Solution:
Cause: herdctl isn’t running or connection failed.
Solution:
herdctl startCause: Various configuration issues.
Checklist:
mention requires @mention)Cause: The bot token is incorrect or has been reset.
Solution:
Enable verbose logging to troubleshoot issues:
chat: discord: bot_token_env: SUPPORT_DISCORD_TOKEN log_level: verbose # Shows detailed debug informationVerbose logs include:
Use the /status slash command in Discord to check the bot’s connection status and statistics.
Discord rate limits are handled automatically by the connector. If you see frequent rate limit warnings:
context_messages)Rate limit events are logged at the standard level:
[support-agent] Rate limited by Discord API: { route: '/channels/123/messages', timeToReset: 5000 }