How to connect an Agno agent to Slack
Add the Slack interface to AgentOS so your agent answers DMs and @mentions, including inside threads, as each user. Setup takes a Slack app manifest, two environment variables, and a few lines of code.
Cosette CresslerContent & Product Marketing LeadSeptember 22, 202610 min read
Slack's app manifest format and scopes change occasionally; check the Slack app you create against the manifest below.
To put an AI agent in Slack with Agno, add Slack(...) to the interfaces list on your AgentOS and point a Slack app at it. The interface receives DMs and @mentions, runs the agent with one session per thread and the sender's Slack identity, and posts the answer back where the question was asked. In a channel that includes replies inside a thread: by default every channel message needs the @mention, DMs don't. The agent itself doesn't change. You can use the same setup to let customers use your product from their own Slack workspace.
from agno.agent import Agent
from agno.os import AgentOS
from agno.os.interfaces.slack import Slack
agent = Agent(name="Invoicing", model="openai:gpt-5.4")
agent_os = AgentOS(agents=[agent], interfaces=[Slack(agent=agent)])
app = agent_os.get_app()That's all the Agno code you need. The rest of this guide covers setting up the Slack app, adding your credentials, and how sessions work once everything's connected.
Take the invoicing agent from How to build an agent for your product and serve it everywhere. It already answers "what does Northwind owe us?" inside your app. The finance lead who asks that question spends most of her day in Slack, and when it comes up in the #ops channel she asks a colleague instead, because the agent isn't in Slack. The six steps below fix that.
This guide replaces two earlier blog posts, How to Connect Your Agents to Slack and Turn your Slack into an AI-powered workspace. Those posts are still up, but this is the version we keep current.
What you need before you start
- An Agno agent that runs on AgentOS. If you don't have one yet, Build your first agent gets you there.
- Permission to create apps in your Slack workspace.
- A public HTTPS URL for AgentOS. Use ngrok or Cloudflare Tunnel locally and a deployed AgentOS in production.
- The Slack extra installed, plus the extra for your database:
uv add "agno[os,slack,sqlite]"for SQLite oruv add "agno[os,slack,postgres]"for PostgreSQL.
Step 1: Create the Slack app from a manifest
Go to api.slack.com/apps, select Create New App, then From a manifest. Paste this, replacing your-domain with your public host:
One thing to know before you do. This manifest declares assistant_view, which is Slack's legacy Assistant experience, and it's the manifest Agno ships. Slack's current equivalent for new apps is the Agent messaging experience (agent_view). The manifest below is the fastest way to a working agent and everything in this guide applies to it; if you'd rather start a new app on the current experience, build it through Agno's manual setup path instead and follow Slack's agent setup for the feature configuration.
{
"display_information": {
"name": "Your Agent"
},
"features": {
"app_home": {
"home_tab_enabled": false,
"messages_tab_enabled": true,
"messages_tab_read_only_enabled": false
},
"bot_user": {
"display_name": "Your Agent",
"always_online": true
},
"assistant_view": {
"assistant_description": "Ask me about invoices, balances, and payments."
}
},
"oauth_config": {
"scopes": {
"bot": [
"app_mentions:read",
"assistant:write",
"channels:history",
"channels:read",
"chat:write",
"files:read",
"files:write",
"groups:history",
"groups:read",
"im:history",
"users:read",
"users:read.email"
]
}
},
"settings": {
"event_subscriptions": {
"request_url": "https://your-domain.ngrok.app/slack/events",
"bot_events": ["app_mention", "assistant_thread_started", "message.im"]
},
"interactivity": {
"is_enabled": true,
"request_url": "https://your-domain.ngrok.app/slack/interactions"
},
"org_deploy_enabled": false,
"socket_mode_enabled": false,
"token_rotation_enabled": false
}
}What each scope does: app_mentions:read and im:history let the bot see mentions and DMs. channels:history and groups:history let it read the thread it's replying in, and channels:read and groups:read let it look up the channels it's in. The app_home block turns on the Messages tab, without which nobody can DM the bot. chat:write lets it answer. The two files:* scopes handle attachments. users:read and users:read.email let AgentOS map the Slack user to an email. assistant:write and the assistant_thread_started event turn on Slack's assistant pane, so the agent shows up as an assistant and not only as a bot.
Install the app to your workspace when Slack prompts you.
Step 2: Set the credentials
From the app's settings, copy the Bot User OAuth Token (it starts with xoxb-) from OAuth & Permissions, and the Signing Secret from Basic Information.
export SLACK_TOKEN="xoxb-your-bot-token"
export SLACK_SIGNING_SECRET="your-signing-secret"AgentOS uses the token to post replies and the signing secret to verify that each incoming event came from Slack.
Step 3: Add the Slack interface to AgentOS
The Slack interface docs cover every parameter; this is the shape most products start from.
from os import environ
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.os import AgentOS
from agno.os.interfaces.slack import Slack
db = SqliteDb(db_file="tmp/agent.db")
agent = Agent(
name="Invoicing",
model="openai:gpt-5.4",
db=db,
instructions="Answer questions about invoices, balances, and payment status.",
add_history_to_context=True,
markdown=True,
)
agent_os = AgentOS(
agents=[agent],
db=db,
interfaces=[
Slack(
agent=agent,
token=environ["SLACK_TOKEN"],
signing_secret=environ["SLACK_SIGNING_SECRET"],
resolve_user_identity=True,
)
],
tracing=True,
)
app = agent_os.get_app()
if __name__ == "__main__":
agent_os.serve(app="slack_agent:app", reload=True)The interface mounts two routes on AgentOS: /slack/events for messages and /slack/interactions for button and modal callbacks. Those are the two URLs in the manifest.
token and signing_secret fall back to SLACK_TOKEN and SLACK_SIGNING_SECRET when you leave them out, which is why the snippet at the top of this guide needs no credentials. They're passed explicitly here because a config that names what it reads is easier to move between environments than one that doesn't.
resolve_user_identity=True adds a users.info lookup per message to resolve the sender's email and sets it as the run's user_id, with the display name in run metadata. Leave it on when the same person also reaches the agent from your app or another interface, so memory and sessions follow the person rather than the Slack user ID. It's off by default.
To put a team behind Slack instead of a single agent, pass team= instead of agent=. The leader receives the message and routes it.
Step 4: Run it locally
Start AgentOS, then expose it:
uv run slack_agent.py
ngrok http 7777Copy the ngrok URL into the two request_url fields in your Slack app's manifest (Event Subscriptions and Interactivity) and save. Slack sends a verification request to /slack/events and AgentOS answers it. A green check next to the event URL in Slack's settings means the handshake worked.
Step 5: Test the three ways users will reach it
Slack routes messages to your agent along three paths, and each path has its own scope and event. Try all three before you deploy.
- DM the bot. Open a direct message with the app and ask a question. This exercises
message.imandim:history. - Mention it in a channel. Invite the bot to a channel (
/invite @Your Agent) and post@Your Agent what does Northwind owe us?. This exercisesapp_mentionandchannels:history. - Reply in the thread, @mentioning it again. Post
@Your Agent and what about last quarter?in the thread the bot started. The agent should remember the earlier turn, because a thread is a session. The @mention is not optional here: with the defaultreply_to_mentions_only=True, a bare follow-up in a channel thread is a message the agent never sees.
Then open the assistant pane (the sparkle icon in older Slack clients) and start a conversation there. That path uses assistant_thread_started, and it belongs to the legacy Assistant experience the manifest above configures.
Answering without being mentioned
If you want the agent to answer every message in the channels it's in, add the message.channels and message.groups events in Event Subscriptions, reinstall the app, and pass reply_to_mentions_only=False:
Slack(agent=agent, reply_to_mentions_only=False)Read that setting literally before you ship it. It is every message in every channel the bot is in, not just the threads it started.
Step 6: Deploy
ngrok is for development. For production, deploy AgentOS somewhere with a stable public HTTPS URL and update the two request URLs in the Slack app once. Deploy AgentOS to Railway is the shortest path. Docker, Fly.io, Render, and Kubernetes all work the same way.
Keep SLACK_TOKEN and SLACK_SIGNING_SECRET in your platform's secrets rather than in the image. And watch the acknowledgment timing: Slack retries an event if it doesn't get a 200 within three seconds. AgentOS acknowledges immediately and runs the agent in the background, so long-running agents don't produce duplicate replies, but a proxy in front of AgentOS that buffers responses can undo that.
How do sessions and users map to Slack threads?
Each Slack thread is one Agno session. The session ID is <entity_id>:<channel_id>:<thread_ts>, where the entity is the agent or team. A new top-level message starts a new thread and a new session. Replies in that thread share it, so add_history_to_context=True gives the agent the thread so far.
The user is the Slack user ID, or the resolved email when resolve_user_identity=True. That's the ID your agent's memory and any per-user tools key on.
How do I let my customers use my product from Slack?
Follow the same six steps, with two changes. In the Slack app's settings, turn on Public Distribution so a workspace other than yours can install it. Then treat each customer install as its own deployment: Slack issues a separate bot token per workspace, and the Slack interface takes one token, so run one AgentOS interface per customer workspace or put a small install handler in front that stores each workspace's token and routes events to the matching interface.
The rest of the setup stays the same. When the customer's finance lead asks @Your Agent what does Northwind owe us? in her #ops channel, resolve_user_identity maps her Slack email to the user your product already knows, and the agent answers with the data and permissions she already has.
Can the agent post to Slack on its own?
Yes, with the Slack toolkit. The interface handles inbound messages. If the agent should also post to channels, read history, or search the workspace without being asked in Slack, give it SlackTools:
from agno.tools.slack import SlackTools
agent = Agent(
...,
tools=[SlackTools()], # reads SLACK_TOKEN from the environment
)By default the toolkit enables send_message, send_message_thread, list_channels, get_channel_history, upload_file, and download_file. Turn on enable_search_messages, enable_get_thread, enable_list_users, or others as you need them, and add the matching scopes to the Slack app (search:read.*; channels:read and groups:read are already in the manifest above). Message search needs a user token in SLACK_USER_TOKEN.
The interface and the toolkit work independently. A bot that only posts notifications needs just the toolkit, and an assistant that only answers questions needs just the interface. Most product agents use both.
To pull channel context into a run without giving the agent a tool for it, see the Slack context provider.
Troubleshooting
Slack shows "Your URL didn't respond" on the event URL. AgentOS isn't reachable at that address. Check that the server is running, the tunnel is up, and the path ends in /slack/events.
The bot never answers mentions. The app is missing app_mentions:read or the app_mention event, or the bot isn't in the channel. Reinstall the app after any manifest change; scopes don't apply until you do.
The bot answers DMs but ignores channel threads. Most often the follow-up had no @mention. DMs reach the agent unprompted; channel messages don't, including replies in a thread the bot started. Either @mention it again or turn on reply_to_mentions_only=False as above. If the mention is there and it still ignores you, the app is missing channels:history (public) or groups:history (private), so it can't read the thread it was mentioned in.
The events endpoint returns 500. SLACK_SIGNING_SECRET isn't set. AgentOS checks it on each request rather than at startup, so the server starts cleanly and only fails once Slack calls it.
Every reply arrives twice. Slack retried the event because the first acknowledgment took longer than three seconds. Look for a proxy or middleware holding the response.
user_id is a Slack ID like U0123ABC instead of an email. resolve_user_identity is off, or the app lacks users:read.email.
Signature verification fails. The signing secret in the environment doesn't match the app, or a proxy rewrote the request body before AgentOS saw it.
Frequently asked questions
Create a Slack app from the manifest in this guide, export SLACK_TOKEN and SLACK_SIGNING_SECRET, add Slack(agent=...) to your AgentOS interfaces, and point the app’s event and interactivity URLs at /slack/events and /slack/interactions on your public host. The interface reads both credentials from the environment, so you only pass them explicitly if you want to.
Run the agent on AgentOS, add the Slack interface, and install a Slack app whose event URL points at /slack/events on your AgentOS host. The agent then answers DMs, and @mentions in the channels it has been invited to, including @mentions inside a thread. The six steps take about 20 minutes on a laptop with ngrok.
Turn on Public Distribution in the Slack app so customers can install it in their own workspaces, and run one Slack interface per installed workspace with that workspace’s bot token. The agent, its tools, and its permissions are the ones your product already runs.
Yes. The session ID is <entity_id>:<channel_id>:<thread_ts>, so replies in a thread share history and a new thread starts fresh.
Yes. Pass team= instead of agent= to the Slack interface. The leader receives the message and routes to members.
The interface handles messages coming in from Slack. SlackTools lets the agent act in Slack on its own, by posting, reading history, or searching. You can use either one or both.
Yes. Slack delivers events over HTTPS to a URL it can reach. ngrok or Cloudflare Tunnel gives you one in a minute.
Because resolve_user_identity=True is set. It costs one extra API call per message and lets memory follow the person across interfaces.
Use the Slack context provider. It pulls recent channel or thread messages into the run’s context before the agent answers, without giving the agent a tool it has to decide to call. Pair it with the interface when the agent should know what the channel was discussing, and with SlackTools when it should search on its own.