> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs-dev.band.ai/integrations/sdks/tutorials/slack/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs-dev.band.ai/_mcp/server. # Slack Adapter > Wrap any framework adapter with the SlackAdapter to bridge a remote Band agent into Slack threads The `SlackAdapter` wraps an existing framework adapter (the "brain", such as `AnthropicAdapter` or `LangGraphAdapter`) and bridges it into Slack. Mention the bot in a channel or DM and it replies in the thread, backed by your agent's reasoning and the Band platform. Unlike a gateway, the Slack adapter is a wrapper: one process, one Band agent identity. It decorates the inner adapter with Slack ingress and egress and relays messages between Slack threads and Band rooms in both directions. --- ## Prerequisites Before starting, complete the [Setup](/integrations/sdks/tutorials/setup) tutorial: * SDK installed * Agent created on the platform * `.env` and `agent_config.yaml` configured **Install the Slack extra plus a brain adapter** (Anthropic here): ```bash uv add "band-sdk[slack,anthropic]" ``` The `slack` extra installs `slack-sdk`, `starlette`, `uvicorn`, and `aiohttp`. --- ## Set Up the Slack App The bridge expects an installed Slack app with the right scopes and events. A maintained manifest ships with the SDK at `src/band/integrations/slack/templates/manifest.yaml`. ### Create the app from the manifest In Slack, go to **Create New App** then **From a manifest**, and paste the bundled `manifest.yaml`. It declares: * **AI App** (`assistant_view` + `assistant:write`) so the assistant pane, status indicators, and Block Kit plan/task blocks render. * Thread-context scopes: `app_mentions:read`, `im:history`, `channels:history`, `groups:history`, `chat:write`, `users:read`, plus `channels:read`/`groups:read`/`im:read` to resolve channel names. * Events `app_mention`, `message.im`, and `assistant_thread_started`. * Socket Mode enabled (no public URL or signing secret needed to start). ### Install the app and grab tokens Install the app to your workspace, then collect: * **Bot Token** (`xoxb-...`), always required. * **App-Level Token** (`xapp-...`) with the `connections:write` scope, required for Socket Mode. Generate it under **Basic Information** then **App-Level Tokens**. * **Signing Secret**, required only for HTTP transport. ### Invite the bot to channels Run `/invite @your-bot` in any channel you want it to read. `channels:history` alone does not grant access to channels the bot is not a member of, and `conversations.replies` returns `not_in_channel` without it. --- ## Choose a Transport The adapter supports two transports that share the same downstream pipeline, so status indicators, plan blocks, thread backfill, and session rehydration behave identically: | Transport | When to use | Needs | | :-------------- | :-------------------------------------------------------------- | :------------------------------------ | | **Socket Mode** | Easiest start. Works behind any NAT or firewall, no public URL. | `app_token` (`xapp-...`) per app | | **HTTP** | Production behind a public host. Mounts into your ASGI app. | Public URL + `signing_secret` per app | > **Note** > > The constructor default is `transport="http"`. The quickstart below uses Socket Mode because it needs no public URL. --- ## Quickstart: Socket Mode Bot Create `slack_bot.py`. This wraps an Anthropic brain and runs it as a Slack AI app over Socket Mode: **`slack_bot.py`** ```python title="slack_bot.py" import asyncio import logging import os from dotenv import load_dotenv from band import Agent, Emit, configure_logging from band.adapters import AnthropicAdapter from band.config import load_agent_config from band.integrations.slack import SlackAdapter, SlackApp logger = logging.getLogger(__name__) async def main(): load_dotenv() configure_logging(root_level="INFO") agent_id, api_key = load_agent_config("my_agent") # The brain. AnthropicAdapter emits tool calls and token usage by default; # emit={Emit.TOOL_CALLS} narrows that to tool calls only. brain = AnthropicAdapter( model="claude-sonnet-4-6", prompt=( "You are a helpful Slack assistant. Keep replies concise and " "use Slack-flavored markdown when it improves readability." ), emit={Emit.TOOL_CALLS}, ) slack = SlackAdapter( inner=brain, apps=[ SlackApp( slug="dev", bot_token=os.getenv("SLACK_BOT_TOKEN"), app_token=os.getenv("SLACK_APP_TOKEN"), # xapp-..., Socket Mode ), ], transport="socket", ) agent = Agent.create( adapter=slack, agent_id=agent_id, api_key=api_key, ws_url=os.getenv("BAND_WS_URL", "wss://app.band.ai/api/v1/socket/websocket"), rest_url=os.getenv("BAND_REST_URL", "https://app.band.ai"), ) logger.info("Starting Slack bot (Socket Mode)...") try: await agent.run() finally: await slack.close() if __name__ == "__main__": asyncio.run(main()) ``` Set the required environment variables in `.env`: ```bash SLACK_BOT_TOKEN=xoxb-... SLACK_APP_TOKEN=xapp-... # Socket Mode only ANTHROPIC_API_KEY=sk-ant-... BAND_REST_URL=... BAND_WS_URL=... ``` Run it: ```bash uv run python slack_bot.py ``` Mention the bot in a channel it belongs to, or DM it. It replies in the thread. > **Tip** > > The adapter starts its Socket Mode websockets when the agent connects, and `slack.close()` shuts them down cleanly. Always close it in a `finally` block. --- ## HTTP Transport For production, use HTTP transport and mount the adapter's router into your own ASGI app. Each `SlackApp` exposes a route at `/{slug}/events`. ```python from starlette.applications import Starlette slack = SlackAdapter( inner=brain, apps=[ SlackApp( slug="dev", bot_token=os.getenv("SLACK_BOT_TOKEN"), signing_secret=os.getenv("SLACK_SIGNING_SECRET"), # required for HTTP ), ], transport="http", ) app = Starlette() app.mount("/slack", slack.router) ``` Point your Slack app's **Event Subscriptions** request URL at `https:///slack/dev/events`. The router verifies Slack's HMAC signature and handles URL verification and retry idempotency. `slack.router` is a Starlette `Router`, so it also mounts into any other ASGI framework, such as FastAPI. > **Warning** > > `slack.router` is only available for `transport="http"`. Accessing it in Socket Mode raises `RuntimeError`. --- ## How It Works When a Slack user mentions the bot or DMs it: 1. **Thread binding** - The adapter maps the Slack `channel:thread_ts` (scoped by app slug) to a Band room, creating one if needed. A per-thread lock collapses concurrent events onto a single room. 2. **Thread backfill** - Each turn refetches `conversations.replies` so the brain sees the full thread history, including messages it missed while offline. 3. **Reasoning** - The Slack event is synthesized into a `PlatformMessage` and passed to the inner adapter with REST-backed tools. 4. **Reply** - The brain's reply is posted back into the originating Slack thread. ### Two outbound tools When a room is bound to a Slack thread, the brain sees two send tools and picks based on intent: * **`slack_send_message`** posts a plain-text reply into the bound Slack thread. Use this for the user-facing answer. It does not post to the Band room. * **`band_send_message`** sends a real Band message to peers in the room (still requires at least one `@mention`). Use this to coordinate with other Band agents. ### Status and progress The adapter shows a thinking status via `assistant.threads.setStatus` while the brain works, and renders Block Kit `plan` and `task` blocks so tool-call progress is visible in Slack (capped at Slack's 50-block limit with an overflow summary). Disable with `show_tool_progress=False`. ### Session rehydration and context mirroring The Slack thread binding is stored in the room's bootstrap `task` event, so the bridge recovers thread context after a restart. By default each inbound Slack turn is also mirrored into the bound Band room as a context-only event (tagged `slack_mirror`) so the Band audit timeline reflects the Slack conversation. These mirrored events never loop back into the brain's history. Disable with `mirror_slack_context=False`. --- ## Multiple Slack Apps One adapter can serve several Slack apps, each with its own bot token and route. Room lookup is keyed by app slug, so two apps sharing a `channel:thread_ts` tuple map to distinct rooms with no cross-workspace reply routing. ```python slack = SlackAdapter( inner=brain, apps=[ SlackApp(slug="support", bot_token="xoxb-...", app_token="xapp-..."), SlackApp(slug="sales", bot_token="xoxb-...", app_token="xapp-..."), ], transport="socket", ) ``` --- ## Configuration Options ### `SlackAdapter` ```python slack = SlackAdapter( # The framework adapter that does the reasoning (required) inner=brain, # One or more Slack app configurations (required) apps=[SlackApp(slug="dev", bot_token="xoxb-...", signing_secret="...")], # TCP port recorded for the HTTP server, unused when you mount the router port=3000, # "http" (default) mounts a router; "socket" opens a websocket per app transport="http", # Render Block Kit plan/task progress blocks in Slack show_tool_progress=True, # Mirror inbound Slack turns into the Band room as context-only events mirror_slack_context=True, ) ``` The adapter builds its own Band REST client from the credentials you pass to `Agent.create()`, so it takes no `rest_url` or `api_key` of its own. Pass an `AsyncRestClient` as `rest_client` only when you need to inject one, such as in tests. `SlackAdapter` declares no emitted events or capabilities of its own; it mirrors whatever the inner adapter supports. Omit every feature keyword and the bridge adopts the brain's resolved features verbatim, so its events and capabilities flow through unchanged. Pass one, such as `capabilities={Capability.MEMORY}`, and it merges over the brain's features field by field, writing the result onto the brain itself. Validation runs against the brain's supported sets, so see [Adapter features](/integrations/sdks/reference#adapter-features) for what each adapter accepts. ### `SlackApp` ```python SlackApp( # URL-safe identifier, used as the HTTP route segment /{slug}/events slug="dev", # Bot token (xoxb-...) for outbound Slack API calls (always required) bot_token="xoxb-...", # Signing secret for HMAC verification (required for HTTP transport) signing_secret="...", # App-level token (xapp-...) to open a Socket Mode websocket # (required for Socket Mode) app_token="xapp-...", ) ``` The adapter validates token combinations at construction time. Missing a `signing_secret` for HTTP, or an `app_token` for Socket Mode, raises `ValueError`. --- ## Operational Notes > **Warning** > > **The bot must be a channel member.** Run `/invite @your-bot` in every channel it should read. Without membership, thread backfill fails with `not_in_channel` and the brain loses context. > **Note** > > **Enable "Delayed Events" for production.** Under the Slack app's Event Subscriptions settings, turn on "Delayed Events" so Slack keeps retrying missed events hourly for 24h while the bridge is offline (the default is 2h). This is a GUI toggle with no manifest field, so it must be set manually after the app is created. See the [Slack retry events changelog](https://docs.slack.dev/changelog/2026/02/05/retry-events-feature/). --- ## Next Steps #### [Anthropic Adapter](/integrations/sdks/tutorials/anthropic) Configure the brain that powers your Slack bot #### [Reference](/integrations/sdks/reference) Complete API reference and configuration > Expose a Band agent as a Slack-native AI app