Slack
Let people talk to your agent in Slack channels, threads and DMs, and approve its actions with a button.
Add slack() to a host and each Slack conversation becomes a thread. The agent replies in place, and tool calls that need approval show Approve / Deny buttons.
Setup
Create a Slack app
In api.slack.com/apps, create an app and install it to your workspace. Give the bot token the chat:write scope, plus the *:history scopes for the conversations it joins (channels:history, groups:history, im:history). The history scopes let threads confirm whether a reply was already posted after a crash.
Set the secrets
export SLACK_SIGNING_SECRET=... # Basic Information → Signing Secret
export SLACK_BOT_TOKEN=xoxb-... # OAuth & Permissions → Bot User OAuth TokenAdd the channel to your host
import { secret } from "@threads/core";
import { slack } from "@threads/slack";
const slackChannel = slack({
agent: "support",
signingSecret: secret("SLACK_SIGNING_SECRET"),
botToken: secret("SLACK_BOT_TOKEN"),
botUserId: "U0123456789",
});Pass it to host({ channels: { slack: slackChannel } }). See Host server.
Point Slack at the webhook
Run threads dev and copy the printed URL, https://<your-host>/channels/slack/events. Use it as both the Event Subscriptions request URL (subscribe to message.channels, message.groups and message.im) and the Interactivity request URL. Slack's URL check is answered automatically.
slack extra (uv sync --extra slack).Options
agentstringrequiredThe host agent key this channel routes to.
signingSecret / signing_secretSecretrequiredVerifies every request over its raw bytes, with a 5-minute replay window.
botToken / bot_tokenSecretrequiredUsed to post replies.
botUserIdstringTypeScript only. The bot's own user id, so its messages are ignored.
tenantstring | (teamId) => string | undefinedTypeScript only. The tenant for a workspace. Default slack:<team_id>. A function that returns undefined refuses that workspace.
What the agent sees
- A message in a channel or DM starts or continues that conversation's thread. A reply inside a Slack thread continues that Slack thread's own conversation.
- Bot messages, edits and other subtypes are ignored.
- Each workspace is its own tenant (
slack:<team_id>), so data from two workspaces never mixes. On Enterprise Grid the org and workspace together identify the installation.
Approvals
When a tool call needs approval, the reply carries Approve and Deny buttons. By default only the person who started the run can press them. To let others approve, list them on the agent:
const approving = agent({
name: "support",
model,
approvers: [{ issuer: "slack:T024BE7LD", tenant: "slack:T024BE7LD", subject: "U0123456789" }],
});T024BE7LD is the Slack team id and U0123456789 the user id. A press from anyone else changes nothing, and a second press of the same button is ignored. More in Human-in-the-loop.