Slack
Connect an agent to Slack: Socket Mode setup, in-thread replies and slash commands.
Triggers turn an agent into a long-lived service that waits for events, acts on them, delivers the result, and goes idle. The slack trigger connects that loop to a Slack workspace over Socket Mode — an outbound WebSocket, so the agent needs no public endpoint. It watches channels and replies in a thread under the messages that wake it. (A second transport, events_api, trades the always-on socket for an inbound HTTPS endpoint — see Transports.)
triggers:
- type: slack
channels: ["help"] # names or ids; omit for all channels the bot is in
# require_mention: true # only respond when @-mentioned (DMs always respond)
# token_env: SLACK_BOT_TOKEN (default)
# app_token_env: SLACK_APP_TOKEN (default)
# from_users: ["U0HAPPY"] # only handle these authors (Slack user ids)
# reply_channel: "C0REVIEW" # post replies here instead of the source thread
# allow_silence: true # a reply of exactly __NO_REPLY__ posts nothingOptions
Every key is optional; the defaults give you a bot that answers everyone, in every channel it has been invited to.
| Key | Default | What it does |
|---|---|---|
channels | all channels | Channel names or ids to listen in. DMs always pass. |
require_mention | false | Only respond when the bot is @-mentioned. Gates channels only; a DM always addresses the bot. |
from_users | anyone | Only handle messages from these Slack user ids (the U… kind - display names don't match). The filter runs before the model is called, so everyone else's messages cost no tokens. |
token_env | SLACK_BOT_TOKEN | The env var holding the bot token (xoxb-…) - reads channel info, posts replies. |
app_token_env | SLACK_APP_TOKEN | The env var holding the app-level token (xapp-…, scope connections:write) that opens the Socket Mode connection. |
reply_channel | the source thread | Channel id to post replies into. Out-of-channel replies quote the triggering message and link back to it. |
allow_silence | false | Post nothing when the agent replies with exactly __NO_REPLY__, or with nothing at all. Tell the agent about the sentinel in purpose. |
transport | socket | How events arrive: socket (Socket Mode) or events_api (inbound HTTP from Slack's Events API). See Transports. |
signing_secret_env | SLACK_SIGNING_SECRET | Events API transport: the env var holding the app's signing secret. Every delivery's X-Slack-Signature is verified before parsing. |
path | /slack | Events API transport: the HTTP path Slack POSTs deliveries to. |
port | 8080 | Events API transport: the port to listen on. |
Transports
socket holds a Socket Mode WebSocket open — outbound only, no public endpoint, but the process must be running to receive anything. events_api inverts that: the trigger serves Slack's Events API over inbound HTTP, verifying each delivery against the app's signing secret, answering the url_verification challenge (so the request URL can be enabled in the app config), and acking within Slack's 3-second deadline before running the agent. Slack retries deliveries it thinks failed (up to 3× — and on a scale-to-zero host a cold boot often eats the first attempt), so events are deduped on their event_id. Everything after ingestion — channels, from_users, require_mention, reply_channel, allow_silence — behaves identically, and app_token_env is not needed in this mode (there is no Socket Mode connection).
The trade-off: the socket transport needs an always-on process; the Events API transport needs a public HTTPS endpoint, and in exchange the host can scale to zero. On platforms that stop idle machines and autostart them on inbound HTTP (Fly.io and friends), Slack's retry is exactly what boots the machine back up.
triggers:
- type: slack
transport: events_api
# signing_secret_env: SLACK_SIGNING_SECRET (default)
# path: /slack (default)
# port: 8080 (default)To set it up, skip the Socket Mode step below; instead, under Event Subscriptions, enable events and set the Request URL to your public endpoint + path (the running agent answers the verification challenge), and export the Signing Secret from Basic Information as SLACK_SIGNING_SECRET. Slash commands work here too: point each command's request URL at the same endpoint.
Multiple HTTP-serving triggers on one agent need distinct port/path combinations, the same rule as the webhook, github and email triggers.
Setup
Setting up the Slack app takes about 10 minutes:
- In api.slack.com/apps, create a new app (from scratch) in your workspace.
- Under Socket Mode, enable it and generate an app-level token with the
connections:writescope — this isSLACK_APP_TOKEN(xapp-…). - Under OAuth & Permissions, add the bot scopes
chat:write,channels:history,groups:history,im:history, andchannels:read, then install the app to the workspace. The bot token it produces isSLACK_BOT_TOKEN(xoxb-…). - Under Event Subscriptions, enable events and subscribe to the bot events
message.channels,message.groups, andmessage.im. Missing event subscriptions are the most common setup failure — the socket connects but nothing arrives. - Export both tokens, invite the bot to a channel (
/invite @your-bot), and run the agent:af run agent.yaml
Replies go into a thread under the triggering message; conversations are keyed per thread (a DM is one rolling conversation), and memory.scope: thread continues them (Memory). The agent ignores bots, itself, message edits, and empty messages; long replies split at Slack's recommended 4000-char limit. If you change scopes later, reinstall the app — Slack applies them only on install.
Observer agents
from_users, reply_channel and allow_silence together turn the trigger from a chatbot into an observer: an agent that watches channels, reacts to specific people and reports elsewhere. Observer agents covers the pattern, with a full example.
Slash commands
Slack treats any message starting with / as a slash command and never delivers it as a message event, so commands need one manual step here: add each command (with its description) to your Slack app configuration. They work over Socket Mode with no public URL. The trigger acknowledges the command and posts the agent's reply through the command's response URL when the run finishes.
Are you an AI? Visit llms.txt — these docs as plain markdown.