The built-in app, the standalone bot, and the cloud connector are three separate Slack apps (different manifest, different scopes, different transport) — do not point one at another’s server.
Built-in Slack App
The Slack app that ships inside thecognee package: slash commands and message shortcuts, served by your own self-hosted backend, with memory scoped to each linked member. Once a workspace is connected, anyone in it can search memory with /cognee-ask, save new facts with /cognee-remember, capture existing messages with the Remember this shortcut, and review every answer privately before sharing it with the channel. The member who connected the workspace can additionally import past conversations in bulk with /cognee-import.
Requires cognee >= 1.5.0. On older releases /cognee-remember falls through to “Command /cognee-remember is not yet supported.” while /cognee-ask and Remember this work. History import (/cognee-import, Remember entire thread, and the /api/v1/slack/history endpoints) requires cognee >= 1.6.2.
Why Use This Integration
- Ask from anywhere in Slack:
/cognee-ask <question>searches your memory and replies privately, with a Share button to post the answer to the channel once you’ve checked it, or Discard to drop it. - Save what Slack never saw:
/cognee-remember <text>stores free text nobody typed into Slack yet — a decision from a call, a conclusion recorded after the fact. - Save what Slack already has: the Remember this message shortcut (in a message’s ⋯ menu) stores any Slack message into Cognee memory, tagged with who said it and where.
- Import conversations that already happened:
/cognee-importpulls selected channels’ past days — or a whole thread via Remember entire thread — into a dataset you choose, keeping authors, timestamps and permalinks, and can keep that selection refreshed every 6 hours. - Per-user, not per-workspace: each member runs
/cognee-linkonce so their asks and saves use their own Cognee account — without it, Cognee refuses to act for them rather than falling back to anyone else’s memory. - Self-hosted, no middleman: your Slack app talks directly to your Cognee backend. No data passes through a third-party relay.
One Slack app is installed once per workspace, but memory is per-person — that’s what
/cognee-link exists to resolve. A member who skips it is refused, not silently attributed: the only unlinked member Cognee will act for is the one who completed the workspace’s OAuth Connect (their own account is used), and everyone else gets “I don’t know which Cognee account you are yet. Run /cognee-link to connect yours, then try again.” — for /cognee-ask, /cognee-remember, and Remember this alike. Nothing is written into someone else’s memory as a fallback. Workspaces connected before the installer’s Slack id was recorded have no installer exception at all, so every member there must link.Prerequisites
- Cognee
>= 1.5.0or adevcheckout — see the version note above. - A running Cognee backend (and frontend, for the
/cognee-linkconfirmation page) reachable over HTTPS from the public internet — Slack will not calllocalhost. For local development, use a tunnel like ngrok (below); for production, this is your deployment’s normal public domain. - A Cognee account for each Slack member who wants to link their own memory.
Local Development: ngrok Tunnel
Slack requires a public HTTPS URL for every request URL. ngrok tunnels a public HTTPS host to your local port 8000 — that host is what every<public-host> placeholder below refers to. Skip this section if your backend already has a public domain.
- Install ngrok:
brew install ngrokon macOS, or download the binary from ngrok.com/download. - Authenticate once: sign up for a free account, copy your authtoken from the dashboard, and run
ngrok config add-authtoken <token>. - Start the tunnel:
Forwarding https://<random>.ngrok-free.app -> http://localhost:8000. That https://<random>.ngrok-free.app host goes into the manifest and .env below. Leave the process running the whole time you’re testing — closing it kills the tunnel and every request URL stops resolving.
Free-tier ngrok shows a “Visit Site” interstitial page to browsers opening the tunnel URL directly. It does not apply to Slack’s server-to-server requests (commands, events, interactivity) — those go straight through, so it never blocks the integration itself.
Setup
1. Create the Slack App from the Manifest
- Go to api.slack.com/apps → Create New App → From an app manifest.
- Paste the manifest below, replacing every
<public-host>with your backend’s public host. - On the app’s Basic Information page, copy Client ID, Client Secret, and Signing Secret — you’ll need them for
.envin step 2. - Install the app to your workspace when prompted.
callback_ids are matched server-side (remember_this, remember_thread) — the shortcut names above are yours to choose. The Options Load URL (message_menu_options_url) is what fills the import dialog’s destination-dataset picker; without it the picker comes up empty.
token_rotation_enabled: false is still the right default — most Slack apps don’t rotate. If you do turn it on, Cognee handles it: a refresh writes the new tokens only, so the channel allowlist, saved sync selection, installer identity and workspace ownership all survive rotation, and an import pre-emptively refreshes a token within 120 seconds of expiry.
Granting the scopes does not start an import. Nothing is read until someone explicitly selects channels or threads — see Importing Channel and Thread History below. For loading history in bulk from your own code instead, dlt’s slack_source is still an option: see the dlt integration.
2. Configure Cognee
Copy the app’s credentials into your backend’s.env:
Then bring the stack up from source — it runs exactly the checkout you have, with no image layer in between to go stale:
uv sync matters more than it looks: branches move pyproject.toml/uv.lock, and a venv left over from another branch fails in ways that read as application bugs rather than a dependency mismatch. Restart the backend after any .env change.3. Install and Connect
- Installing the app from the manifest (step 1) prompts the workspace install once.
- In the Cognee frontend, go to Integrations → Connect under Slack. This runs the OAuth exchange and stores the workspace-level bot credential.
- In Slack, run
/cognee-linkand open the link it replies with in a browser where you’re already logged in to Cognee, then click Confirm. The link is a short-lived signed code (10-minute expiry), not an API key — nothing secret is ever typed into Slack. This is what points/cognee-askand the save actions at your memory — and for anyone who didn’t run Connect, what makes them work at all (see the per-person note above). - Try
/cognee-ask What does Cognee do?— you should get a private reply with Share / Discard buttons.
Using It
Ingest something before you ask.
/cognee-ask only searches — it never writes, so it cannot create the slack dataset. On a completely empty memory it fails with CollectionNotFoundError: Collection 'DocumentChunk_text' not found, surfaced as “Search failed. Please try again.” — which reads like a broken integration but just means there is nothing to search. Run /cognee-remember (or ingest via the CLI) first.app_home_opened, app_uninstalled, and tokens_revoked only — there is no app_mention handler. Use the slash commands. If you want @cognee <question> answered from channel memory, that is what the standalone channel-memory bot does.
No /invite @cognee is needed for any of the above either. Every reply — including Share, which posts to the channel — is delivered by Slack on the app’s behalf regardless of channel membership.
Restricting Which Channels Can Run Commands
An opt-in, per-workspace channel allowlist is available from the Integrations page, backed by two authenticated endpoints:GET /api/v1/slack/channels lists the workspace’s public channels and flags which are currently allowed, PUT /api/v1/slack/channels sets the list. An empty list (the default) means unrestricted — a workspace that never touches this setting keeps working everywhere.
This requires the channels:read scope (already in the manifest above). If a workspace connected before that scope existed, the channel list call fails — the fix is to disconnect and reconnect the workspace, not to re-save the allowlist.
Once a non-empty allowlist exists, every command in every other channel returns “Cognee isn’t enabled in this channel.” — worth remembering when testing from a scratch channel.
A non-empty allowlist also constrains history imports: it is checked against the source channels being imported, not just the channel a command was typed in. Selecting a channel outside it fails with “A selected channel is outside the configured Slack allowlist.”
Importing Channel and Thread History
/cognee-import imports past Slack conversations into an existing Cognee dataset. Unlike /cognee-remember, which captures one message at a time, this reads channel history in bulk — so it is restricted to the one Slack member who completed the workspace’s OAuth Connect. Everyone else gets “Only the person who connected this Slack workspace can import history.” In Slack, a workspace connected before the installer’s Slack id was recorded gets that same message even for its owner (see troubleshooting below); over the HTTP API it gets “Reconnect Slack to identify the user authorizing history access.”
The dialog. /cognee-import (or /cognee-import 14, where the number is the past-days default) opens a modal with:
- Destination dataset — a searchable picker listing only datasets the connecting user holds read, write and delete on. All three are required because re-importing an edited conversation replaces the stored document, which can fall back to a delete-and-re-add.
- Channels — a multi-select over public and private channels.
- Past days (for channel selection) — prefilled from the command argument.
- Thread links (one per line) —
https://<workspace>.slack.com/archives/…message or thread links. A selected thread is imported in full, including replies outside the date range. - Import options — two checkboxes: “Include older threads active during the period (slower)” and “Keep this selection synced every 6 hours”.
thread_mode: started) it selects threads started in the period. The “older threads active during the period” checkbox (thread_mode: active) also scans older roots and keeps those with a message in the window — more Slack calls, but it catches a late reply on an old thread.
Submitting clears the dialog immediately and the import runs in the background; the result comes back as an ephemeral message: “Imported N conversations (M messages) into Cognee.” A large import can outlive Slack’s response URL (valid for 30 minutes), so a missing message isn’t proof of failure — check the status endpoint below, and if the last run failed, retry; imports are idempotent.
Before importing, invite the Cognee app to every channel you select (“Invite the Cognee app to each selected channel before importing.”), and make sure the connecting Slack user is a member of each one too (“The connecting Slack user must be a member of every selected channel.”) — the installation’s bot may see private channels its installer cannot, so bot membership alone isn’t treated as authorization. DMs and group DMs are not supported.
How conversations are stored. One document per conversation, so replies keep their context. The body opens with the channel name and id, the workspace, the thread root and a permalink to it, then one entry per message as [ISO-8601 timestamp] <@author> (permalink) followed by the text. Joins, leaves, tombstones, empty messages and the Cognee bot’s own posts are skipped. Each document is tagged with the node sets slack, slack:<team_id> and slack:channel:<channel_id>. Document identity is derived from dataset + workspace + channel + thread root, so re-importing the same selection updates in place instead of duplicating; a conversation whose content digest is unchanged is skipped entirely.
Keeping a Selection Synced
Ticking “Keep this selection synced every 6 hours” saves the selection and enables a background refresh (interval_seconds, default 21600, range 600–604800). A refresh re-fetches previously imported conversations in the selected channels, so it catches edits, late replies and deletions — including changes made while the server was down. A stored document is deleted only when Slack explicitly answers thread_not_found/message_not_found for a channel it could still read, or when a re-fetch finds every message in the conversation now deleted, empty, or a join/leave; a lost scope, a transient error, or a conversation simply falling outside a shorter window never deletes anything.
The window grows rather than rolls: saving a sync resolves “last N days” into a fixed start date, and every refresh runs from that date to now. A long-lived sync therefore fetches more each time and can eventually hit max_requests/max_messages — save it again to move the start date forward. A refresh that fails is retried after 10 minutes rather than after the full interval.
An enabled sync must carry a selection and cannot pin a fixed latest date. Server-side, the worker runs inside the API process and is gated by SLACK_HISTORY_SYNC_ENABLED (default true; set it to false when an external scheduler drives refreshes instead). With the worker off, the dialog still saves the selection and tells you so: “Sync selection saved; this server’s refresh worker is disabled.”
HTTP Endpoints
Three authenticated endpoints sit under/api/v1/slack, alongside the channel-allowlist ones. All of them require a Cognee session or bearer token belonging to the Slack connection owner; JSON fields are snake_case.
The response reports
conversations, messages, added, updated, unchanged and deleted. A runnable client is in examples/python/integrations/slack_history.py.
Run one API process against local embedded stores for imports. Cognee’s dataset locks and the refresh worker are process-local, so a multi-worker deployment has to designate a single history worker and serialize external import requests itself.
Removing the App from a Channel vs. Uninstalling
- Removing/kicking the bot from a channel only stops it from posting there — it does not revoke anything; the workspace connection and everyone’s
/cognee-links stay intact. - Uninstalling the app (or a token revoke) fires
app_uninstalled/tokens_revoked, which revokes the stored workspace credential server-side — commands then fail with “not connected” until reconnected from Integrations.
Multiple Environments
Slack apps are tied to one set of OAuth credentials and one redirect URL each. If you run more than one Cognee deployment (local dev, staging, production), create a separate Slack app per environment rather than editing one app’s config back and forth — each environment gets its ownSLACK_CLIENT_ID/SLACK_CLIENT_SECRET/SLACK_SIGNING_SECRET/SLACK_REDIRECT_URI. This avoids local testing ever breaking a shared staging or production connection.
This is separate from how many Slack workspaces one deployment can serve: any number of teams can each connect their own workspace to the same Cognee deployment through the same Connect flow — that’s already multi-tenant by design and needs no extra setup per workspace.
Troubleshooting
Command `/cognee-remember` is not yet supported: most likely your backend is on a Cognee release older than1.5.0, which has the integration but not this command. Runpip install -U cognee, or run thedevcheckout directly.- Same message, correct version installed: either the manifest you installed from predates the command and the app was never reinstalled (see the reinstall warning above — this is the most common cause), or you are running stale code — restart the backend process (or rebuild the container if containerized).
- Same message, but a different app name answers: another Cognee app in the workspace owns the command. Slack shows all of them in autocomplete — pick yours explicitly, or test in a workspace where only your app is installed.
- Typing the command produces nothing at all, no request reaches your server: the command is not registered on your app. Check api.slack.com/apps → your app → Slash Commands; if it’s missing, the manifest save silently failed. Add it via Create New Command, then reinstall.
- “Something went wrong when authorizing Cognee” during Connect: the
redirect_uriyour backend sent (fromSLACK_REDIRECT_URI) isn’t byte-identical to an entry under the app’s redirect URLs, or.envstill holds a different app’sSLACK_CLIENT_ID/SLACK_CLIENT_SECRETthan the workspace you’re installing into. Re-sync.envto the exact app you’re testing and restart. - Callback redirects to
?slack=error_exchange_failed: the callback never 500s — it catches everything and redirects. The traceback is in the backend log. Usual causes:SLACK_REDIRECT_URImismatch, or a missing/malformedINTEGRATION_CREDENTIALS_KEY. - Signature verification fails on every request (401):
SLACK_SIGNING_SECRETin.envdoesn’t match the app you actually installed — easy to hit when juggling more than one test app. Copy it again from Basic Information and restart the backend. - Request URL mismatch after restarting ngrok: free-tier ngrok mints a new random host every restart. Update every request URL and redirect URL in the app’s Slack settings — or use a reserved/static domain so this never happens.
- Slack shows “dispatch_failed” instead of a reply: the slash command’s request URL is missing or wrong — it must point at
/api/v1/slack/commands, not/api/v1/slack/eventsor/api/v1/slack/interactive. - “This Slack workspace is not connected to Cognee”: the OAuth install (Connect in the Cognee UI) hasn’t completed for this workspace yet, or the connection was later disconnected/revoked.
- “I don’t know which Cognee account you are yet”: the member running the command has no active
/cognee-link, and isn’t the member who completed the workspace’s Connect — so the command is refused before it reads or writes anything, rather than falling back to the installer’s memory. Run/cognee-linkand confirm the link in a browser where you’re logged in to Cognee. No API key is involved; if the reply instead tells you to run`/cognee-link <api_key>`, you’re on code from before this was fixed — that argument never existed. - Can’t delete a dataset — deletion hangs or errors: a running Cognee backend holds an exclusive lock on the graph database via a worker subprocess. Stop the server first, then delete.
- Asking returns an “I don’t know”-phrased answer as if it were real: known limitation — the ask handler substring-matches common refusal phrasings (“cannot answer”, “no relevant information”, “does not contain”, …) before forwarding an answer, but new phrasings can still slip through.
- “Only the person who connected this Slack workspace can import history”: history import is restricted to the member who completed the OAuth Connect, not to anyone with
/cognee-link. If that is you, and you still see this, the connection predates the installer’s Slack id being recorded — reconnect from the Integrations page. /cognee-importfails on reading, or the import dialog’s dataset picker is empty: two different causes. Reading needs the newchannels:history/groups:history/groups:readscopes, which existing installs only get by disconnecting and reconnecting (see the reinstall warning above). An empty picker usually means the app’s Options Load URL isn’t set to/api/v1/slack/interactive, or the connecting user has no dataset with all three of read, write and delete.- “Slack request limit reached” / “Slack message limit reached” / “Slack returned an incomplete page. Nothing was imported.”: an import is all-or-nothing by design — it never stores a partial snapshot. Narrow the selection (fewer channels, fewer days, drop
thread_mode: active), or raisemax_requests/max_messageson the HTTP endpoint. - “The Slack token does not belong to the selected workspace” or “A thread link belongs to another Slack workspace”: the
{team_id}in the URL, the stored token, and the workspace host in every pasted permalink must all agree. Copying a link out of a different workspace is the usual cause. - “Slack access changed during the import. Retry with the current selection.”: permissions are re-checked after the fetch — the connection was reconnected, or the channel allowlist changed, while the import was running. Just retry; imports are idempotent.
- “Cognee could not ingest a conversation. Retry the import.”: fetching succeeded but indexing failed. Native writes are idempotent rather than transactional, so re-running the same selection resumes it — unchanged documents are skipped and indexing is retried.
- “An import for this workspace and dataset is already running.”: only one import (or scheduled refresh) runs per workspace and destination dataset at a time. Wait for it to finish — check
GET /api/v1/slack/history/{team_id}— then retry. - A command or shortcut is acknowledged, but the follow-up reply never arrives: every deferred reply — a
/cognee-askanswer, a/cognee-rememberor Remember this confirmation, a/cognee-importresult — is delivered by POSTing to theresponse_urlSlack sent in the webhook payload, and that URL is accepted only in its canonical form:https://hooks.slack.com/commands/…/…/…orhttps://hooks.slack.com/actions/…/…/…, exactly three path segments of letters, digits,_or-, no embedded credentials, no port other than 443, no query string and no fragment. Anything that fails those checks is dropped before any outbound request, with “Refusing to deliver to an invalid https://hooks.slack.com response_url” in the backend log; a valid one is rebuilt fromhttps://hooks.slack.complus the checked path rather than used as received. There is no error reply, because the original webhook has already been acked, so the member just sees nothing. Genuine Slack payloads always pass; this only bites a self-hosted deployment whose proxy or test harness rewritesresponse_url. Pass Slack’s value through unchanged.
Standalone Channel-Memory Bot
cognee-integration-slack is a separate Slack bot from the cognee-integrations repository. Instead of per-person slash commands, it gives a workspace per-channel memory: once a channel opts in, the bot silently ingests its messages into that channel’s own Cognee dataset, and anyone can ask @cognee <question> or /recall <question> to get an answer with cited links back to the source Slack messages.
It is a thin client to a running Cognee server over HTTP (POST /api/v1/add, /cognify, /search, /forget) — no in-process Cognee and no LLM key of its own, since the server holds those. Slack I/O runs over Socket Mode, so it needs no public URL, no tunnel, and none of the manifest or OAuth setup above.
Install
The package is not published on PyPI yet. Install it from the repository:slack-bolt, aiohttp, and httpx. uv sync creates a project venv without activating it, so run the bot through uv run (below) or activate .venv first; pip install -e . into an already active environment needs neither.
Create the Slack App
In api.slack.com/apps, create an app with Socket Mode enabled, then:- Copy the bot token (
xoxb-…) and the app-level app token (xapp-…). - Subscribe the bot to the
message.channelsandapp_mentionevents. - Add the slash commands
/recall,/cognee-optin,/cognee-optout, and/cognee-forget. - Install the app to the workspace and invite the bot to the channels it should remember.
Configure and Run
The bot reads everything from environment variables (a.env.example ships in the package directory):
Commands
An empty question gets a usage hint back. A channel with no dataset yet — never opted in, or opted in with nothing cognified — returns “I couldn’t find anything about that in this channel’s memory yet.”: the server answers the search with a
404 for the unknown dataset, which the bot treats as an empty result. Once the channel has memory, an unanswerable question gets whatever the LLM produces from the available context; unlike the built-in app, the bot does not filter refusal phrasings. A server-side failure (LLM auth, rate limit, a failed cognify) returns “Sorry — I hit an error answering that.” rather than silence; the cause is in the bot’s logs.
How It Works
- One dataset per channel. Messages land in the dataset
slack_<channel_id>, tagged with a node set of[<channel_id>]. The dataset is the boundary/cognee-forgetclears, so forgetting is per channel, never per person. - Batched graph building. Each message is added with a cheap
addcall;cognifyruns once a channel hasCOGNEE_SLACK_COGNIFY_BATCHpending messages, and always before answering a question, so an answer reflects everything said so far. Concurrent cognify runs on one channel are serialized. - Cited answers. Answering runs two searches:
GRAPH_COMPLETIONfor the prose, andCHUNKSfiltered to the channel’s node set for the source messages. Each stored message carries a one-line provenance header ([cognee-slack] channel=… ts=… author=… permalink=…) inside its text, which is parsed back out of the retrieved chunks to build the links. The reply is a Block Kit message: the answer, then up to five sources as links labelled#<channel ID> · <user ID> · <UTC time>(raw Slack IDs, not names — the link itself opens the original message) with a+N morenote for the rest. A message whose permalink could not be resolved shows as plain text, never a broken link. - What gets ingested. Messages and thread replies in opted-in channels. Edits, deletions, joins, bot messages, the bot’s own posts, messages that mention the bot, and empty messages are skipped. Public channels only, via
message.channels. Edited or deleted Slack messages are not re-synced.
Tests
The suite runs without Slack, Cognee, or LLM credentials — the adapter runs against a fake HTTP client, the handlers against mocks:Related
Channel-Memory Bot Source
Source and tests for
cognee-integration-slack.dlt Integration
Pull Slack channel history in bulk via dlt, instead of a live bot.
Cloud Integrations UI
Where “Connect Slack” lives in the Cognee UI.
Node Sets
How per-channel node-set tags scope retrieval.