Features
Memory, files, streaming, search, and user identity for your Slack agent.
A basic agent receives a message, thinks, and replies. A production agent handles the messy reality of workplace chat: conversations that span days, files dropped mid-thread, long-running tasks that need visibility, and sensitive actions that need approval.
Configure each capability for your app and account for the integration limits below:
| Capability | What it does | Quick setup |
|---|---|---|
| Memory | Conversations persist across messages | db=SqliteDb(...) |
| Files | Read attachments, send files back | SlackTools(enable_upload_file=True) |
| Streaming | Show progress on long tasks | On by default |
| Search | Query workspace data through the Real-time Search API | Review the current token and storage limits before enabling |
| Identity | Resolve Slack profile identity when available | resolve_user_identity=True |
| Response control | Control when bot responds | reply_to_mentions_only=True |
| Approvals | Pause for human approval | @tool(requires_confirmation=True) |
| Context provider | Query and update Slack from any agent | SlackContextProvider() |
Memory
Slack conversations span hours or days. With a database configured, the agent remembers previous messages in the thread and can reference earlier context without the user restating it.
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
agent = Agent(
name="Support Bot",
model=...,
db=SqliteDb(db_file="sessions.db"),
add_history_to_context=True,
num_history_runs=5,
)Each Slack thread maps to one session. DMs work the same way. All responses are sent as thread replies, keeping channel conversations organized. Conversations persist across server restarts.
New session format: {entity_id}:{channel_id}:{thread_ts}, for example support-bot:C123:1719000000.000100. Existing legacy {entity_id}:{thread_ts} sessions are reused when found.
Files
The agent can read files that users attach and generate files in response. The interface extracts attachments from incoming messages and makes them available to the agent. Generated files upload back to the thread.
from agno.tools.slack import SlackTools
agent = Agent(
tools=[SlackTools(enable_upload_file=True, enable_download_file=True)],
)Streaming
Responses stream live, so users see text appear as the agent generates it, with task cards showing progress on long-running work.
Slack(agent=agent, streaming=True) # on by defaultConfigure the Slack agent feature and scopes needed by the streaming APIs. If opening the Slack stream fails, Agno reports an error; it does not automatically retry in non-streaming mode. Set streaming=False explicitly to use a complete-response message. See current app setup for the prompt lifecycle limitation.
Search
The search_workspace tool calls Slack's Real-time Search API. The constructor flag below enables the tool, but the current event-token and storage limitations must be resolved before using it.
The current Slack interface copies event.assistant_thread.action_token into run metadata. It does not read the top-level event.action_token used in Slack's current agent example. Events carrying only that top-level token reach search_workspace without the required credential and return a no-token error.
Slack's Real-time Search guide prohibits retaining data retrieved by that API. Agno's ordinary session persistence can retain raw tool results; the Slack Team interface also enables member-response storage. Configure and verify event-token handling and the complete storage path before enabling this search. The workspace-tools example keeps it disabled pending those integration changes.
from agno.tools.slack import SlackTools
agent = Agent(
tools=[SlackTools(enable_search_workspace=True)],
)Slack selects keyword or semantic retrieval according to the query and workspace capabilities. Semantic search requires a workspace plan with Slack AI Search; check assistant.search.info for the available capabilities. See Slack's retrieval rules.
Search requires additional OAuth scopes (search:read.public, search:read.files, search:read.users). Add them under OAuth & Permissions in your Slack App settings. See the Reference for the full list.
Identity
Identity resolution looks up a Slack user profile. When an email is available, Agno uses it as user_id; otherwise it retains the Slack user ID.
Slack(agent=agent, resolve_user_identity=True)Email lookup needs users:read, users:read.email, and a successful profile response. Cross-platform memory requires your application to map identities to the same canonical user ID and share a memory store. WhatsApp phone IDs are not automatically matched to Slack email addresses, and thread sessions remain separate.
Response control
By default, the bot only responds when @mentioned in channels. In DMs, it responds to every message.
| Setting | Behavior |
|---|---|
reply_to_mentions_only=True (default) | Responds to @mentions in channels, all DMs |
reply_to_mentions_only=False | Responds to every message in joined channels |
Setting reply_to_mentions_only=False means the bot responds to every channel message. Use this only for dedicated bot channels.
Troubleshooting
Check: Server running? ngrok active? Request URL matches {your-url}/slack/events?
Fix: Verify you've subscribed to app_mention and message.im events in your Slack App.
Cause: Wrong signing secret, or request timestamp too old.
Fix: SLACK_SIGNING_SECRET must match Basic Information > App Credentials in your Slack App settings. If using ngrok, restart it to clear stale connections. Signature verification rejects requests older than 5 minutes.
Check: Review Slack API errors, feature availability, and installed scopes using the setup guide. Set streaming=False explicitly if your app cannot use the streaming API.
Cause: reply_to_mentions_only=False makes the bot respond to every message in channels it can see, including @mentions of other users.
Fix: Set reply_to_mentions_only=True (the default). Only set it to False for dedicated bot channels.
Cause: This tool requires run_context.metadata["action_token"]. The current interface only copies the nested event token, so a top-level-only token is lost.
Next step: Check the event shape and the search integration limits. A standalone call must supply valid user-scoped metadata explicitly; changing to a legacy search API does not resolve the RTS integration's token and retention requirements.
Cause: System Python missing root certificates.
Fix:
export SSL_CERT_FILE=$(python3 -c "import certifi; print(certifi.where())")