Single Bot
One Slack App handles all agents — no need to create multiple bots
Connect your herdctl agents to Slack, allowing users to interact with AI agents through channel messages, @mentions, and threaded conversations. herdctl uses Slack’s Socket Mode so no public URL or reverse proxy is needed.
herdctl uses a per-agent connector model for Slack, matching the Discord integration architecture. Each agent gets its own SlackConnector instance, but all agents share the same Slack App identity (one bot token for the whole workspace). Different channels are assigned to different agents.
Single Bot
One Slack App handles all agents — no need to create multiple bots
Channel Routing
Each channel maps to a specific agent, so users talk to the right agent automatically
Socket Mode
Connects via WebSocket — no public URL, domain, or reverse proxy required
Threaded Replies
Responses appear in threads, keeping channels organized
Slack Workspace (single bot)├── #support│ └── @HerdBot → routed to support-agent├── #dev-help│ └── @HerdBot → routed to coder-agent└── #general └── @HerdBot → routed to general-agentAll channels use the same bot identity. When a user messages or @mentions the bot in a channel, herdctl routes the message to the agent assigned to that channel and triggers a Claude session to respond.
| Aspect | Slack | Discord |
|---|---|---|
| Bots per workspace | 1 shared bot identity, 1 connector per agent | 1 bot per agent |
| Token model | Bot token + App token (shared across agents) | Separate bot token per agent |
| Connection | Socket Mode (WebSocket) | Gateway (WebSocket) |
| Channel structure | Flat channels | Guild → Channels |
| Commands | Prefix commands (!help) | Slash commands (/help) |
| DM support | Supported | Supported |
| Public URL needed | No | No |
| Mode | Behavior | Best For |
|---|---|---|
mention | Responds only when @mentioned | Shared channels where you want explicit interaction |
auto | Responds to all messages | Dedicated channels for a specific agent |
herdctl supports two different Slack integrations:
| Integration | Type | Purpose | Configuration |
|---|---|---|---|
| Chat | Two-way | Interactive conversations | chat.slack |
| Notification Hooks | One-way | Job completion alerts | hooks.after_run |
The chat integration documented on this page enables interactive, two-way conversations:
chat.slack section of agent configNotification hooks send one-way alerts when jobs complete:
hooks.after_run sectionExample notification hook:
hooks: after_run: - type: slack channel_id: "${SLACK_CHANNEL_ID}" bot_token_env: SLACK_BOT_TOKEN when: "metadata.shouldNotify"Before setting up Slack integration, ensure you have:
Open the Slack API Portal
Navigate to https://api.slack.com/apps and sign in with your Slack account.
Create a New App
Click Create New App and choose From scratch.
Enter a name for your app (e.g., “HerdBot” or “My Fleet Bot”) and select the workspace where you want to install it.
Enable Socket Mode
In the left sidebar, go to Settings > Socket Mode.
Toggle Enable Socket Mode on.
You’ll be prompted to generate an App-Level Token:
connections:write scopexapp-)Add Bot Token Scopes
In the left sidebar, go to Features > OAuth & Permissions.
Scroll to Bot Token Scopes and add:
| Scope | Purpose |
|---|---|
app_mentions:read | Detect when users @mention the bot |
chat:write | Send messages and replies |
channels:history | Read channel messages for context |
files:write | Upload files (for long responses) |
im:history | Read DM messages for direct message support |
Subscribe to Events
In the left sidebar, go to Features > Event Subscriptions.
Toggle Enable Events on.
Under Subscribe to bot events, add:
| Event | Purpose |
|---|---|
app_mention | Triggers when the bot is @mentioned |
message.channels | Triggers on all channel messages (for auto mode) |
message.im | Triggers on direct messages to the bot |
Click Save Changes.
Install the App to Your Workspace
In the left sidebar, go to Settings > Install App.
Click Install to Workspace and authorize the app.
Copy the Bot User OAuth Token (starts with xoxb-).
Add the Bot to Channels
In Slack, go to each channel where you want the bot to operate:
@YourBotName and send the message, then click Invite to Channel when promptedThe bot must be a member of a channel to receive messages from it.
You’ll need channel IDs to configure which channels route to which agents.
Open Slack (desktop or web app)
Right-click on a channel name in the sidebar
Select View channel details (or Open channel details)
The channel ID is shown at the bottom of the details panel — click to copy
Alternatively, you can find the channel ID in the URL when viewing a channel in the Slack web app:
https://app.slack.com/client/T01234567/C0123456789 ^^^^^^^^^^^ This is the channel IDchannels: - id: "C0123456789" # Channel ID (starts with C) name: "#support"Configure Slack in your agent’s YAML file under the chat.slack section.
name: support-agentdescription: "Customer support agent"
chat: slack: bot_token_env: SLACK_BOT_TOKEN app_token_env: SLACK_APP_TOKEN channels: - id: "C0123456789" name: "#support" mode: mentionchat: slack: # Environment variable containing the Bot User OAuth Token (required) # Token starts with xoxb- bot_token_env: SLACK_BOT_TOKEN
# Environment variable containing the App-Level Token for Socket Mode (required) # Token starts with xapp- app_token_env: SLACK_APP_TOKEN
# Session expiry in hours (default: 24) session_expiry_hours: 24
# Log level: minimal, standard, verbose (default: standard) log_level: standard
# Output configuration - control what SDK messages appear in Slack output: tool_results: true # Show tool results (default: true) tool_result_max_length: 900 # Max chars in tool output (default: 900, max: 1000) system_status: true # Show system status messages (default: true) errors: true # Show error messages (default: true)
# Channel configurations channels: - id: "C0123456789" # Slack channel ID (required) name: "#support" # Human-readable name (optional) mode: mention # mention or auto (default: mention) context_messages: 10 # Messages to include as context (default: 10)
- id: "C9876543210" name: "#dev-help" mode: auto context_messages: 5
# Direct message settings (optional) dm: enabled: true # Accept DMs (default: true) mode: auto # mention or auto (default: auto) allowlist: # Only accept DMs from these user IDs (optional) - "U0123456789" blocklist: # Block DMs from these user IDs (optional) - "U9876543210"| Field | Type | Default | Description |
|---|---|---|---|
bot_token_env | string | — | Required. Environment variable name containing the Bot User OAuth Token (xoxb-) |
app_token_env | string | — | Required. Environment variable name containing the App-Level Token (xapp-) for Socket Mode |
session_expiry_hours | number | 24 | Hours before a conversation session expires |
log_level | string | standard | Logging verbosity: minimal, standard, verbose |
output | object | — | Control what SDK messages appear in Slack (tool results, system status, errors) |
channels | array | — | List of Slack channels to operate in |
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Required. Slack channel ID (starts with C or G) |
name | string | — | Human-readable name (for documentation only) |
mode | string | mention | Response mode: mention or auto |
context_messages | number | 10 | Number of recent messages to include as context |
| Field | Type | Default | Description |
|---|---|---|---|
dm.enabled | boolean | true | Whether to accept direct messages |
dm.mode | string | auto | Response mode for DMs: mention or auto |
dm.allowlist | string[] | — | Only accept DMs from these Slack user IDs |
dm.blocklist | string[] | — | Block DMs from these Slack user IDs |
Control which SDK messages are surfaced in Slack. 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 results 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 messages (e.g., “Compacting context…”) |
output.errors | boolean | true | Show error messages when the SDK reports errors |
All output types appear as formatted Slack messages with emoji indicators:
| Message Type | Emoji | Example Content |
|---|---|---|
| Tool result (success) | Tool emoji | 🔧 Bash — > git status with output preview |
| Tool result (error) | Tool emoji | 🔧 Bash — command output with error |
| System status | ⚙️ | ⚙️ System — “Compacting context…” |
| Error | ❌ | ❌ Error — error description |
Tool result messages 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.
Minimal output (text responses only):
chat: slack: bot_token_env: SLACK_BOT_TOKEN app_token_env: SLACK_APP_TOKEN output: tool_results: false system_status: false errors: falseFull visibility (all message types):
chat: slack: bot_token_env: SLACK_BOT_TOKEN app_token_env: SLACK_APP_TOKEN output: tool_results: true tool_result_max_length: 500 system_status: true errors: trueherdctl uses environment variables for Slack tokens to keep secrets out of configuration files.
Slack requires two tokens (compared to Discord’s one):
# Bot User OAuth Token — authenticates as the bot user# Found at: Slack App > OAuth & Permissions > Bot User OAuth Tokenexport SLACK_BOT_TOKEN="xoxb-your-bot-token-here"
# App-Level Token — enables Socket Mode connection# Found at: Slack App > Settings > Basic Information > App-Level Tokensexport SLACK_APP_TOKEN="xapp-your-app-token-here"If you have multiple workspaces or want descriptive names:
# Pattern: {PURPOSE}_SLACK_BOT_TOKEN / {PURPOSE}_SLACK_APP_TOKENexport SUPPORT_SLACK_BOT_TOKEN="xoxb-..."export SUPPORT_SLACK_APP_TOKEN="xapp-..."Create a .env file in your project root (next to herdctl.yaml):
SLACK_BOT_TOKEN=xoxb-your-bot-token-hereSLACK_APP_TOKEN=xapp-your-app-token-hereSLACK_CHANNEL_ID=C0123456789herdctl automatically loads .env files when you run herdctl start.
Add to ~/.bashrc, ~/.zshrc, or equivalent:
export SLACK_BOT_TOKEN="xoxb-your-bot-token-here"export SLACK_APP_TOKEN="xapp-your-app-token-here"Then reload:
source ~/.bashrc # or ~/.zshrcOn macOS/Linux:
export SLACK_BOT_TOKEN="xoxb-your-bot-token-here"export SLACK_APP_TOKEN="xapp-your-app-token-here"On Windows (PowerShell):
$env:SLACK_BOT_TOKEN="xoxb-your-bot-token-here"$env:SLACK_APP_TOKEN="xapp-your-app-token-here"In your agent YAML, reference the environment variable names (not the values):
chat: slack: bot_token_env: SLACK_BOT_TOKEN # The variable NAME, not the token app_token_env: SLACK_APP_TOKEN # The variable NAME, not the tokenherdctl reads the tokens from process.env.SLACK_BOT_TOKEN and process.env.SLACK_APP_TOKEN at runtime. You can also use ${VAR_NAME} substitution in channel IDs for flexibility:
channels: - id: "${SLACK_CHANNEL_ID}"Slack’s Socket Mode is a key advantage of the herdctl Slack integration. Instead of requiring a public URL to receive webhook events, Socket Mode uses a WebSocket connection initiated by the bot.
Traditional Webhooks: Slack ──HTTP POST──→ https://your-server.com/slack/events (Requires public URL, domain, SSL, reverse proxy)
Socket Mode: Your Bot ──WebSocket──→ Slack (Works behind NAT, firewalls, on localhost)The App-Level Token (xapp-) is specifically for establishing this Socket Mode connection. The Bot User OAuth Token (xoxb-) is used for API calls (sending messages, reading history, etc.).
herdctl startLook for connection messages in the logs:
[slack] Connecting to Slack via Socket Mode...[slack] Connected to Slack: HerdBot[slack] Listening in channels: #support (C0123456789), #dev-help (C9876543210)The bot should appear as active in the channels you configured. You can check with:
!statusIn a channel configured with mode: mention:
You: @HerdBot How do I reset my password?HerdBot: To reset your password, follow these steps...In a channel configured with mode: auto:
You: How do I reset my password?HerdBot: To reset your password, follow these steps...herdctl provides built-in prefix commands for every Slack-enabled agent:
| Command | Description |
|---|---|
!help | Show available commands and usage |
!status | Show bot connection status and session info |
!reset | Clear conversation context and start fresh |
Try them in any channel where the bot is active:
!statusShows available commands and basic usage information:
!helpExample output:
Available commands: !help - Show this help message !status - Show bot status !reset - Reset conversation context
Chat with me: @HerdBot your question — in channelsShows connection status and session information:
!statusExample output:
Bot Status Connected: Yes Uptime: 2h 34m Session: Active (expires in 21h) Channel: #supportClears the conversation context for the current channel:
!resetExample output:
Conversation reset! Starting fresh.Use !reset when:
Like Discord, Slack creates a separate connector for each agent internally. However, all agents share the same Slack App identity (single bot token). Different agents are assigned to different channels.
Each channel maps to exactly one agent. Each agent’s connector manages its own channels directly, so messages are handled by the correct agent without centralized routing.
name: support-agentchat: slack: bot_token_env: SLACK_BOT_TOKEN app_token_env: SLACK_APP_TOKEN channels: - id: "C111111111" name: "#support" mode: mention
# agents/coder-agent.yamlname: coder-agentchat: slack: bot_token_env: SLACK_BOT_TOKEN # Same tokens! app_token_env: SLACK_APP_TOKEN # Same tokens! channels: - id: "C222222222" name: "#dev-help" mode: autoBoth agents share the same bot tokens because they use the same Slack App. The routing is determined by channel ID:
#support go to support-agent#dev-help go to coder-agentUse distinct channels — Each agent should have its own dedicated channel(s). Since all agents share the same bot identity, channel separation is how users interact with different agents.
Name channels clearly — Use descriptive channel names like #support-bot, #code-review, #dev-help so users know which agent they’re talking to.
Set channel topics — Add the agent’s purpose to the channel topic (e.g., “Ask me about code reviews — powered by herdctl”).
Use mention mode in shared channels — If a channel must be monitored by multiple agents (not recommended), use mention mode.
| Scenario | Recommendation |
|---|---|
| Two agents need separate channels | Assign different channels to each agent |
| One agent needs multiple channels | List multiple channels in that agent’s config |
| Shared discussion channel | Don’t assign it to an agent, or use mention mode |
Slack chat integration maintains conversation context so your agent can have multi-turn conversations and “remember” what was discussed.
session_expiry_hours (default: 24 hours).herdctl/slack-sessions/ and survive bot restarts within the expiry windowWhen 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: slack: bot_token_env: SLACK_BOT_TOKEN app_token_env: SLACK_APP_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:
channels: - id: "C0123456789" mode: mention context_messages: 10 # Include last 10 messagesMore context = better continuity but higher token usage.
Slack agents can receive and respond to direct messages (DMs) from users.
DM support is enabled by default. To configure it:
chat: slack: bot_token_env: SLACK_BOT_TOKEN app_token_env: SLACK_APP_TOKEN channels: - id: "C0123456789" mode: mention dm: enabled: true mode: auto# Accept DMs from everyone (default)dm: enabled: true mode: auto
# Only accept DMs from specific usersdm: enabled: true allowlist: - "U0123456789" - "U9876543210"
# Block specific usersdm: enabled: true blocklist: - "UBADUSER001"
# Disable DMs entirelydm: enabled: falseSlack uses its own markup format called mrkdwn (not standard Markdown). herdctl automatically converts the agent’s Markdown output to Slack’s mrkdwn format, so you don’t need to worry about formatting differences.
| Markdown | Slack mrkdwn | Rendered |
|---|---|---|
**bold** | *bold* | bold |
*italic* | _italic_ | italic |
`code` | `code` | code |
~~strike~~ | ~strike~ | |
[link](url) | <url|link> | link |
Code blocks, lists, and blockquotes are also converted automatically.
Slack has a message length limit of approximately 4,000 characters (compared to Discord’s 2,000). When a response exceeds this limit, herdctl automatically splits it into multiple messages.
| Scenario | Recommended Mode |
|---|---|
| Dedicated agent channel | auto |
| Shared team channel | mention |
| High-traffic channel | mention |
| Testing/development | auto |
For multiple agents in the same workspace:
Dedicated channels: Give each agent its own channel with auto mode
#support-bot → support-agent (auto)#dev-help → coder-agent (auto)Shared channels: Use mention mode so users explicitly address the bot
#general → general-agent (mention)Slack has rate limits on API calls. 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 $SLACK_BOT_TOKEN
# Set it if missingexport SLACK_BOT_TOKEN="xoxb-your-token-here"Cause: The environment variable specified in app_token_env is not set or is empty.
Solution:
# Check if the variable is setecho $SLACK_APP_TOKEN
# Set it if missing — generate one at Slack App > Settings > Socket Modeexport SLACK_APP_TOKEN="xapp-your-token-here"Cause: The bot token is incorrect, expired, or the app was uninstalled.
Solution:
Cause: Various configuration issues.
Checklist:
@BotName or via channel settings)mention requires @mention)app_mention and message.channels)Cause: The bot is not a member of the channel, or the channel ID is wrong.
Solution:
@BotName in the channel)Cause: App-Level Token is missing, invalid, or lacks the connections:write scope.
Solution:
connections:write scopeCause: The bot lacks required OAuth scopes.
Solution:
Enable verbose logging to troubleshoot issues:
chat: slack: bot_token_env: SLACK_BOT_TOKEN app_token_env: SLACK_APP_TOKEN log_level: verbose # Shows detailed debug informationVerbose logs include:
Use the !status command in any channel where the bot is active to check connection status and session information.