Looped Docs

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 nothing

Options

Every key is optional; the defaults give you a bot that answers everyone, in every channel it has been invited to.

KeyDefaultWhat it does
channelsall channelsChannel names or ids to listen in. DMs always pass.
require_mentionfalseOnly respond when the bot is @-mentioned. Gates channels only; a DM always addresses the bot.
from_usersanyoneOnly 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_envSLACK_BOT_TOKENThe env var holding the bot token (xoxb-…) - reads channel info, posts replies.
app_token_envSLACK_APP_TOKENThe env var holding the app-level token (xapp-…, scope connections:write) that opens the Socket Mode connection.
reply_channelthe source threadChannel id to post replies into. Out-of-channel replies quote the triggering message and link back to it.
allow_silencefalsePost nothing when the agent replies with exactly __NO_REPLY__, or with nothing at all. Tell the agent about the sentinel in purpose.
transportsocketHow events arrive: socket (Socket Mode) or events_api (inbound HTTP from Slack's Events API). See Transports.
signing_secret_envSLACK_SIGNING_SECRETEvents API transport: the env var holding the app's signing secret. Every delivery's X-Slack-Signature is verified before parsing.
path/slackEvents API transport: the HTTP path Slack POSTs deliveries to.
port8080Events 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:

  1. In api.slack.com/apps, create a new app (from scratch) in your workspace.
  2. Under Socket Mode, enable it and generate an app-level token with the connections:write scope — this is SLACK_APP_TOKEN (xapp-…).
  3. Under OAuth & Permissions, add the bot scopes chat:write, channels:history, groups:history, im:history, and channels:read, then install the app to the workspace. The bot token it produces is SLACK_BOT_TOKEN (xoxb-…).
  4. Under Event Subscriptions, enable events and subscribe to the bot events message.channels, message.groups, and message.im. Missing event subscriptions are the most common setup failure — the socket connects but nothing arrives.
  5. 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.

On this page