main answers you in a DM, in a channel and in threads, and only you and the channels you choose can reach it.
Slack is the recommended channel. Ravi connects to Slack itself: messages do not go through Omni, and you need no phone and no public URL.
Below, slack-main names both the channel config and the instance, main is the agent, U0123ABCD is your Slack member ID and C0123ABCD a channel ID.
Before you start
-
Ravi installed and answering in the terminal (Install Ravi; its Run without Omni section skips Omni and PostgreSQL for Slack-only use):
-
PM2 (
ravi setupinstalls it) and permission to install apps in your Slack workspace. -
A place for the tokens: the macOS Keychain, or a HashiCorp Vault server (KV version 2) on Linux. Ravi reads Slack tokens only from its credential broker, never from
~/.ravi/.env.
slack-main points to the tokens (credential connection slack:main). The channel runner, a separate PM2 process called ravi-channels, reads it and connects to Slack, then routes each message through the instance with the same name, which holds the agent, the policies and the routes. If the names differ, every message is held for review. ravi daemon start does not start the runner; ravi channels start does.
Set it up
1
Create the Slack app
At api.slack.com/apps, choose Create New App > From scratch. Ravi does not ship an app manifest, so set these by hand:
- Socket Mode: turn it on and create the app-level token with the
connections:writescope. Copy it (xapp-...). - Event Subscriptions: turn it on and subscribe to the bot events
message.im,message.channels,message.groups,message.mpimandapp_mention(plusreaction_addedfor reaction triggers). - Interactivity & Shortcuts: turn it on. Approval buttons need it.
- App Home: turn on the Messages Tab and allow users to send messages from it, or you cannot DM the app.
- OAuth & Permissions: add the bot scopes below. Leave token rotation off: Ravi does not refresh tokens.
- Install to Workspace and copy the Bot User OAuth Token (
xoxb-...).
Optional:
reactions:read (reaction events), canvases:read/canvases:write (ravi slack canvas-*), channels:manage/groups:write (ravi slack channels-create), links:read/links:write (unfurls), assistant:write (Working indicator).After changing scopes or events, reinstall the app, store the bot token again if it changed, and run ravi channels restart.2
Store the tokens
- macOS
- Linux
The tokens go to your login Keychain. The CLI, the channel runner and the daemon (which posts approval messages) read them from there, so run Ravi as your own user.
Stored connection slack:main (keychain) (or vault), then executed: slack:main socket_mode.connect and secretResolved=yes. That proves the secret is readable, not that Slack accepts it; step 4 checks that.If connections add prints only Command failed unexpectedly., check that you passed --backend, and that VAULT_ADDR and VAULT_TOKEN are set and valid (Linux) or the security tool exists (macOS).3
Create the channel config and the instance
show should list Agent: main, DM Policy: pairing and Group Policy: allowlist. These policies hold every DM and channel until you route it. With the default open, anyone in the workspace who DMs the app, and every human message in any channel it is in, reaches the agent.4
Start the channel runner
- The first
channels startdoes not save PM2’s process list, henceravi daemon install(andpm2 startup, see Install Ravi). permissions-listtests only the bot token. A wrong app token shows as an adapter that never gets toconnected.- Run
ravi channels restartafterchannels createorchannels set, after storing new tokens, and after changing Slack settings in~/.ravi/.env. Instances, routes and contacts reload on their own. ravi daemon statusdoes not list the runner;ravi daemon status --jsondoes.ravi channels logsfollows the log until Ctrl+C. For a one-off read:pm2 logs ravi-channels --nostream --lines 200.- Do not run
ravi channels probewhile the runner is up: it opens a second Slack connection.
5
Make yourself the owner
Find your member ID in Slack: open your profile, click ⋮ (More) and choose Copy member ID. It starts with
U.contacts addprints the<contact-id>.--instancemust be exactlyslack-main. A typo is accepted silently, and then your messages do not resolve to you: the agent answers without tools.ravi contacts check U0123ABCD --jsonmust show"instanceId": "slack-main".- Already have an owner contact, for example from WhatsApp? Link your Slack ID to it instead:
ravi contacts link <contact-id> --channel slack --id U0123ABCD --instance slack-main. - The route is what lets your DMs through
pairing. On Slack, anallowedcontact alone is not enough.
permission-owner tag and the risks.6
Add a channel
In Slack, invite the app to the channel, public or private:
/invite @Ravi (your app’s name). It never joins on its own. Find the channel ID at the bottom of the channel’s About tab, or with ravi slack channels-list --channel slack-main. Then:- Keep the
group:prefix: a bareC0123ABCDroute never matches. Private channels withG...IDs work the same way. - In a channel with other people, every member who resolves to a contact gets the agent’s whole ceiling. Govern it first (Make yourself the owner).
How the agent behaves in Slack
- With
--session <name>on the DM route, your DMs go to that session and each DM thread gets a child session. To make a routed channel feed a session that already exists, runravi sessions attach main --chat C0123ABCD(Sessions). - To answer channel root messages in a new thread:
ravi channels set slack-main defaults '{"rootReplyMode":"new_thread"}', thenravi channels restart. This replaces the wholedefaultsobject. - The agent keeps a DM or channel on its subject: work that strays from the conversation (an investigation, a fix, a long run of tools, another subject) goes to a new thread it opens with
ravi sessions create-thread, where a thread session does the work. Inside a thread, it stays on that thread’s subject. This needs the agent to be allowed to runravi sessions. - The DM or channel session orchestrates the threads it opens. It lists them with
ravi sessions threadsand gives one new instructions withravi sessions thread-send <thread-session> "...". A thread sends progress back withravi sessions report "..."and its result withravi sessions close-thread --return "..."; both arrive in the channel session naming the thread by its first line. - A thread the agent opens shows in the channel as a card: the first line of its initial message as the title, a short summary, who asked for it, and its status (in progress, done, reopened). The whole message opens the thread when it does not fit the card. The card updates itself when the thread reports progress, closes (showing the result) or is reopened.
- There is no mention-only mode: every human message in a routed channel starts a turn. Other bots are ignored unless they mention your app.
- Edited messages are ignored. Send a new one.
ravi contacts blockdoes not stop Slack messages, and reply mode is not enforced. Keep people out with policies and routes (Instances and routing).
Questions with options (polls, the agent’s
AskUserQuestion) do not work on Slack: Ravi cannot post a poll there (Polls are not supported on Slack), so the agent gets an error instead of your answer. Ask the agent to ask in plain text.Working indicator
By default Ravi sets Slack’s assistant status (“is working…”) while the agent works. Slack shows it only for apps with the Agents & AI Apps feature and theassistant:write scope. If you see nothing, use a reaction instead:
RAVI_SLACK_REACTION_PRESENCE=always shows both.
Approvals
When a turn asks for something the agent’s ceiling does not cover and the request goes to approval, Ravi posts a message with Approve and Reject buttons in the chat the request came from. Only agents on the Pi and Grok providers ask. On Claude and Codex, a shell command outside the ceiling is refused at once, and on every provider aravi command group outside it is refused without asking.
- Approving a shell command does not make it run today: it is still refused. To let the agent run an executable, add it to the agent’s ceiling.
- Only clicks count; reactions and text replies are ignored.
- Only a contact whose capabilities cover the request, or who has
admin:system:*(yourpermission-ownertag), can decide. Other clicks are ignored without any message in Slack. - No click within 5 minutes means not approved.
- Buttons need Interactivity on. The daemon posts the message, so it must read the tokens too (Keychain, or the Vault variables in
~/.ravi/.env).
full-access few requests need approval. See Permissions.
Smoke test
- DM. In the app’s Messages tab, send
hi; the agent answers. Ask it to runravi agents list; it runs, because you are the owner. - Channel. In the routed channel, send
@Ravi hi. The answer appears at the channel root. - Thread. Reply in a thread under that answer. The agent answers in the thread, and
ravi sessions list --agent mainshows a new session for the thread: its name contains-t-followed by the thread timestamp without the dot, and its key ends in:thread:<ts>. - Ceiling. Lower the ceiling with
ravi agents permissions main bootstrap(it applies at once) and ask the agent in the DM to rununame -a. The command is refused, becauseunameis outsidebootstrap(on Pi or Grok, Ravi first posts Approve and Reject buttons; click Reject). Restore it withravi agents permissions main full-access --execute.
If it does not answer
health: stopped (not_running): runravi channels start.- Adapter
slack:slack-mainisfailed: the runner cannot read the tokens. Repeat thebroker execcheck from step 2 (on Linux, check the Vault variables), thenravi channels restart. - Adapter stuck in
reconnecting (open_failed): the app token is wrong or revoked, or Socket Mode is off. - Log says
Slack inbound held for owner review: the chat has no route, or the instance name differs from the channel config. Checkravi routes explain slack-main group:C0123ABCD. - Nothing in the log: the app is not in the channel, an event subscription is missing, or the app was not reinstalled after a scope change.
- Answers without tools: your Slack ID is not linked to your contact on
slack-main. Check withravi contacts check <your contact>. - Approve does nothing: Interactivity is off in the Slack app, or the request was for a shell command, which approving does not unblock today (see Approvals).