Slack
Connecting Slack
Create the Wheems Slack app from its manifest, connect its two tokens, allow people, bind channels and test it end to end.
On this page
You talk to Wheems through Slack. By the end of this page you will have a Slack app called Wheems in your Slack workspace, its two tokens saved on your Mac, yourself on the list of people allowed to talk to agents, and a chief of staff answering you in Slack.
It takes about ten minutes, most of it in Slack’s app settings. You do it once: one Slack app serves every Wheems workspace.
Before you start
Link to “Before you start”You need:
- Wheems installed, with
wheems doctorpassing its System and Herdr core sections. See Installation. - At least one engine installed and signed in (Claude Code, Codex or opencode).
wheems setup→ Engines does this. See Engines and accounts. - A Slack workspace where you’re allowed to add apps. On some company Slack workspaces only admins can, or apps need an admin’s approval. A free Slack workspace of your own works well.
- The Slack desktop app or slack.com in a browser, signed in to that Slack workspace.
How Wheems talks to Slack
Link to “How Wheems talks to Slack”Wheems connects to Slack with Socket Mode. The Wheems service on your Mac opens a connection out to Slack, and Slack sends messages down it. Nothing connects in to your Mac, so you need no public address, no domain and no open port.
That needs two tokens, and Wheems checks both with Slack before saving them:
| Token | Looks like | Where you get it | What Wheems uses it for |
|---|---|---|---|
| Bot User OAuth Token | xoxb-… |
OAuth & Permissions, after installing the app | Everything the bot does: posting answers, adding and removing the 👀 reaction, finding channels, looking up members |
| App-Level Token | xapp-… |
Basic Information → App-Level Tokens, with the connections:write scope |
Opening the Socket Mode connection that receives messages |
The bot is the app’s user in Slack. You message it directly, or invite it to a channel. Its answers appear under the bot’s name, or under your workspace’s own name and icon (see Identities).
Connect Slack, step by step
Link to “Connect Slack, step by step”-
Open setup and choose Channels.
Terminal wheems setupPick Channels with the arrow keys and Enter, then Slack. Esc or Ctrl+C backs out of any step without saving it.
Setup writes the app’s manifest to
~/wheems/.wheems/slack-app-manifest.yaml, copies it to your clipboard, and shows this:Output Create the Slack appOne Slack app serves every workspace, so you do this once.1. At https://api.slack.com/apps?new_app=1, choose "From a manifest" and pick your Slack workspace.2. Paste the manifest: it is on your clipboard.3. Install the app to the workspace. Under OAuth & Permissions, copy theBot User OAuth Token (xoxb-…).4. Under Basic Information → App-Level Tokens, generate a token with theconnections:write scope (xapp-…).It uses Socket Mode, so it needs no public address.Say yes to Open the Slack app page in your browser?, or open api.slack.com/apps yourself. Leave setup waiting in the terminal.
-
Create the app from the manifest.
On Slack’s page, choose Create New App if asked, then From a manifest. Pick the Slack workspace to install it in and choose Next.
Choose the YAML tab, delete what’s there, and paste the manifest. If it isn’t on your clipboard, print it:
Terminal wheems slack manifestChoose Next, check the summary, then Create. The manifest is explained line by line below.
-
Install the app and copy the bot token.
In the app’s settings, open OAuth & Permissions (or Install App) and choose Install to your Slack workspace. Slack lists what the app may do; choose Allow.
Back on OAuth & Permissions, copy the Bot User OAuth Token. It starts with
xoxb-. -
Create the app-level token.
Open Basic Information, scroll to App-Level Tokens and choose Generate Token and Scopes. Give it any name, such as
socket, choose Add Scope, pickconnections:write, then Generate. Copy the token. It starts withxapp-. -
Paste both tokens into setup.
Back in the terminal, setup asks for the Bot User OAuth Token, then the App-Level Token. They’re masked as you type. Setup checks them with Slack:
Output ◇ Connected as wheems in AcmeThe first name is the bot’s Slack username, the second your Slack workspace. If Slack refuses a token, setup says which and why, and offers to try again. See Troubleshooting.
-
Say who may talk to your agents.
Only people on the allowed list can talk to agents. Messages from anyone else are ignored. Setup asks for Your Slack member ID:
Output ◇ Your Slack member ID│ U012ABCDEFTo find yours, see Finding a member ID. Setup looks you up in Slack and shows your name:
Output ◇ Allowed: Alex Example◆ Slack is connected. Its tokens are saved privately in ~/.config/wheems/secrets.jsonSetup has now written the tokens to the secrets file and your member ID to
install.yaml:~/wheems/install.yaml connections:slack:driver: slacksecret: slackallowed_users: [U012ABCDEF] -
Create a workspace that chats in Slack.
Setup moves on to Workspaces. Give it an id such as
personal, a name, then choose where you’ll chat with it:- The app’s direct messages: private between you and the bot. Only one Wheems workspace can have them.
- A channel: give its name, such as
acme-chat. Every other workspace chats in a channel.
Then pick the engine and model for its chief of staff. Setup offers to open the engine in the new folder so you can trust it: say yes, answer the engine’s trust question, then press Ctrl+C twice to come back. See Quick start for this step in full.
-
If you chose a channel, invite the bot to it.
Create the channel in Slack if it doesn’t exist, then type this in the channel:
Slack /invite @WheemsThe bot must be a member of the channel. In a public channel it can see the channel without being a member, but Slack only sends it messages from channels it’s in.
-
Start the service, or restart it.
In setup, choose Service. It installs the service under launchd and starts it. If it’s already running, setup offers to restart it with the new config. Or from the terminal:
Terminal wheems service install # the first timewheems service restart # after any change to Slack, channels or allowed usersThe service reads the tokens,
install.yamland everyworkspace.yamlwhen it starts. Restart it after changing any of them. -
Say hello.
In Slack, open the bot under Apps in the sidebar and use its Messages tab, or post in the workspace’s channel. Within a few seconds a 👀 reaction appears on your message: it has reached the agent. The answer arrives in the message’s thread, and the 👀 goes.
The manifest, line by line
Link to “The manifest, line by line”wheems slack manifest prints the manifest. It’s the same one setup copies to your clipboard and saves to ~/wheems/.wheems/slack-app-manifest.yaml:
display_information: name: Wheems description: Your agents' chief of staff, from Wheems. background_color: "#2c2d30"features: app_home: home_tab_enabled: false messages_tab_enabled: true messages_tab_read_only_enabled: false bot_user: display_name: Wheems always_online: trueoauth_config: scopes: bot: - chat:write - chat:write.customize - im:history - im:read - im:write - channels:history - channels:read - groups:history - groups:read - reactions:write - users:readsettings: event_subscriptions: bot_events: - message.im - message.channels - message.groups interactivity: is_enabled: true org_deploy_enabled: false socket_mode_enabled: true token_rotation_enabled: falseBot token scopes
Link to “Bot token scopes”| Scope | Why Wheems needs it |
|---|---|
chat:write |
Post the agent’s answers and Wheems’s notices in threads. |
chat:write.customize |
Post under a workspace’s own name and icon (identity in workspace.yaml). |
im:history |
Read the messages you send the bot directly. |
im:read |
See the direct-message conversation with the bot. |
im:write |
Reply in that direct-message conversation. |
channels:history |
Read messages in public channels the bot has been invited to. |
channels:read |
Find a public channel’s id from its name, as written in workspace.yaml. |
groups:history |
Read messages in private channels the bot has been invited to. |
groups:read |
Find a private channel’s id from its name. |
reactions:write |
Add the 👀 reaction when your message reaches the agent, and remove it when the agent answers. |
users:read |
Look up a member ID during setup, to show you whose it is. |
Events
Link to “Events”| Event | What it delivers |
|---|---|
message.im |
Messages you send the bot directly. |
message.channels |
Messages in public channels the bot is a member of. |
message.groups |
Messages in private channels the bot is a member of. |
Wheems acts only on plain messages from people. Edits, deletions, joins, other bots’ posts and its own posts are ignored.
Settings
Link to “Settings”| Setting | Value | Why |
|---|---|---|
socket_mode_enabled |
true |
Messages arrive over a connection the service opens, so your Mac needs no public address. |
messages_tab_enabled |
true |
You can message the bot directly from its Messages tab. |
messages_tab_read_only_enabled |
false |
You can type in that tab, not only read it. |
home_tab_enabled |
false |
Wheems has no Home tab. |
always_online |
true |
The bot shows as active. |
interactivity.is_enabled |
true |
For approval buttons, which aren’t built yet (milestone 3). Nothing uses it today. |
token_rotation_enabled |
false |
The bot token doesn’t expire, so the service keeps working without refreshing it. |
org_deploy_enabled |
false |
The app is for one Slack workspace, not a whole Enterprise Grid organisation. |
Where the tokens are kept
Link to “Where the tokens are kept”Setup writes the tokens to a secrets file outside your install folder, so they’re never committed and never part of what an agent is given:
{ "slack": { "bot_token": "xoxb-…", "app_token": "xapp-…" }}- The file is readable only by you (mode 600), in a folder only you can open (mode 700).
- It lives in
$XDG_CONFIG_HOME/wheems/whenXDG_CONFIG_HOMEis set. SetWHEEMS_SECRETSto use another file. slackis the entry’s name.secret: slackininstall.yamlpoints the connection at it; withoutsecret, the connection’s own name is used.- Tokens never pass through an agent: you type them into setup, and only the service reads them. They aren’t in any agent’s instructions, notes or knowledge base.
Rotating the tokens
Link to “Rotating the tokens”Rotate a token when it may have leaked, or when someone who had it leaves.
-
Get new tokens from Slack.
- Bot token: on OAuth & Permissions, choose Revoke All OAuth Tokens if the old one may have leaked, then Reinstall to your Slack workspace. Copy the new
xoxb-…token. - App-level token: on Basic Information → App-Level Tokens, generate a new token with
connections:write. Then open the old token and Revoke it.
- Bot token: on OAuth & Permissions, choose Revoke All OAuth Tokens if the old one may have leaked, then Reinstall to your Slack workspace. Copy the new
-
Give them to Wheems.
Terminal wheems setupChoose Channels, then Slack. Setup checks the saved tokens. If they still work it asks Keep using wheems in Acme?: answer no. If they don’t, it says so and asks for new ones straight away. Paste the new tokens.
If the new tokens are for a different Slack workspace, setup checks that everyone on the allowed list is a member there, and drops anyone Slack says isn’t.
-
Restart the service, which reads the tokens only when it starts:
Terminal wheems service restart -
Check:
Terminal wheems doctor --quickOutput Channels✓ slack: Slack: Signed in as wheems in Acme
Allowing people
Link to “Allowing people”allowed_users in install.yaml lists the Slack member IDs that may talk to agents. Everyone else is ignored: no reaction, no answer. The service log records each ignored message:
2026-11-13T09:12:04.512Z Ignored a message on slack from U034GHIJKL, who is not in allowed_usersFinding a member ID
Link to “Finding a member ID”A member ID starts with U or W, then letters and digits, such as U012ABCDEF. It isn’t your display name or email.
- In Slack, select your profile picture, then Profile. For someone else, select their name.
- Choose ⋯ (More).
- Choose Copy member ID.
Adding more people
Link to “Adding more people”Setup asks for one member ID, the first time. To allow more people, edit the list by hand, then restart the service:
connections: slack: driver: slack secret: slack allowed_users: [U012ABCDEF, U034GHIJKL]wheems validatewheems service restartEveryone on the list can talk to every workspace’s chief of staff. Wheems is built for one person today; roles for several people per workspace are planned for after version one.
Binding channels
Link to “Binding channels”Each workspace’s workspace.yaml says where you chat with its chief of staff, in channels.chat:
channels: chat: { connection: slack, direct: true }channels: chat: { connection: slack, channel: acme-chat }-
direct: truemeans the bot’s direct messages. Only one workspace per Slack app can have them.wheems initgives them to the first workspace, and refuses a second without--channel. -
channeltakes a name (acme-chator#acme-chat) or a channel id (C0123ABCDfor public,G0123ABCDfor private). An id keeps working if the channel is renamed. -
Public and private channels both work. Invite the bot to either:
/invite @Wheems. -
The service finds each channel when it starts. If it can’t find one, it logs this and skips the workspace until the next start:
~/wheems/.wheems/logs/service.log Workspace acme: the bot cannot see channel acme-chat; invite it to the channelWhen it does find it:
~/wheems/.wheems/logs/service.log Workspace acme: chat on slack C0123ABCD -
In a bound channel, every new message from an allowed person starts a conversation. You don’t need to mention the bot. Keep the channel for talking to the chief of staff.
To move a workspace to another channel, change channels.chat, invite the bot there, and restart the service.
Conversations and threads
Link to “Conversations and threads”- Each new message starts a conversation. In direct messages and in a channel alike, a message that isn’t in a thread is a new conversation, with an engine session of its own.
- Reply in the thread to carry on. Wheems answers in your message’s thread, and every reply in that thread goes to the same session, with everything said so far. A new top-level message starts afresh.
- 👀 means it has reached the agent. It goes when the agent answers.
- Answers come from the engine’s own record, not the screen. The agent’s last message of each turn is posted as its answer. Long answers are split into several posts.
- Progress notes. For long work, the agent posts a short note first with
wheems reply "…". - Messages while it’s working are passed on when it finishes its turn.
- Notices from Wheems itself start with Wheems:, such as when the agent is waiting for an answer at the Mac.
- Each conversation is a Herdr session. Watch it, or take over, with the command Wheems gives in its notices, such as
hc attach t7.hc lslists them; their names start with the workspace id. - At most four sessions per workspace run at once. The least recently used idle one is stopped to make room, and its conversation resumes where it left off when you next reply in its thread.
- Restarts don’t lose threads. Which thread belongs to which session is kept in
~/wheems/.wheems/conversations.json, so a reply after a restart, or days later, resumes the same conversation. - Quiet conversations write a note. After 30 minutes without new messages, Wheems asks the agent for a note for the knowledge base, as a turn of its own. That turn’s answer is never posted to Slack. See Knowledge base and search.
One Slack app for every workspace
Link to “One Slack app for every workspace”You create the Slack app once. Every Wheems workspace binds a channel on it: one gets its direct messages, the rest get a channel each. The tokens are saved once, under the connection slack in install.yaml.
wheems setup and wheems init assume this one connection, named slack. Connecting a second Slack workspace isn’t supported by setup today.
Identities: how posts appear
Link to “Identities: how posts appear”Each workspace’s posts can appear under its own name and icon, through the chat:write.customize scope. wheems init sets the name; add an icon if you like:
identity: { name: Acme chief of staff, icon: ':office:' }icon is an emoji code such as :house:, or the URL of an image. Without identity, answers appear under the bot’s own name. Notices from Wheems (Wheems: …) always use the bot’s own name.
Testing it end to end
Link to “Testing it end to end”Work down this list after connecting Slack, or whenever something seems wrong.
-
Doctor passes. Look at Channels and Service:
Terminal wheems doctorOutput Channels✓ slack: Slack: Signed in as wheems in AcmeService✓ service: Running and answering -
The service connected and bound your channels.
Terminal wheems service logs -n 20Output 2026-11-13T09:00:01.118Z Workspace personal: chat on slack (direct messages)2026-11-13T09:00:01.402Z Workspace acme: chat on slack C0123ABCD2026-11-13T09:00:02.007Z Connected slack2026-11-13T09:00:02.031Z Wheems 0.0.3 is running from /Users/you/wheems -
A message gets 👀. Send “hello” to the bot or in the channel. No 👀 within a few seconds means the message never reached the agent: see the table below.
-
An answer arrives in the thread, under the workspace’s identity.
-
A reply carries on. Reply in the thread: “what did I just say?”. The agent remembers.
-
You can watch it. Run
hc lsand find the session named after the workspace, thenhc attachit. Press Ctrl+\ to detach without stopping it.
Troubleshooting
Link to “Troubleshooting”Start with wheems doctor and wheems service logs. Most problems show up in one of them.
| Symptom or message | Cause | Fix |
|---|---|---|
Slack refused the bot token: invalid_auth |
The bot token is wrong, from another app, or was revoked. | Copy the Bot User OAuth Token again from OAuth & Permissions. It starts with xoxb-, not xoxp-. |
Slack refused the app-level token: … It needs the connections:write scope. |
The app-level token is missing connections:write, or was revoked. |
Generate a new app-level token with connections:write under Basic Information. |
That starts with xapp-, so it's the app-level token. … (or xoxb-, so it's the bot token) |
The tokens were pasted the wrong way round. | The bot token is under OAuth & Permissions, the app-level token under Basic Information → App-Level Tokens. Setup asks for the bot token first. |
That starts with xoxp-, so it's a user token, not the bot token. … |
A user token (or a refresh token, xoxe-) was pasted. |
Copy the Bot User OAuth Token from OAuth & Permissions. |
That looks like more than one paste. / There's something before the xoxb- at the start. |
The field keeps what’s in it after a failed check, so pasting again adds to the end. | Clear the field with Backspace, then paste the token once. |
A member ID starts with U or W, then letters and digits |
A name, email or channel id was given. | Use Copy member ID from the profile. |
Slack has no member U012ABCDEF |
The ID is from another Slack workspace, or mistyped. | Copy it again in the Slack workspace the app is installed in. |
Doctor: slack: Not connected yet: no credentials saved for it |
Slack was never connected, or WHEEMS_SECRETS points at another file. |
Run wheems setup → Channels → Slack. |
Doctor: slack allowed users: Nobody is allowed to talk to agents yet |
allowed_users is empty. |
Run wheems setup, or add your member ID to allowed_users and restart the service. |
Doctor: service: Not running, so nothing answers in chat |
The service isn’t installed. | wheems service install |
No 👀, and the log says Ignored a message on slack from U…, who is not in allowed_users |
That person isn’t allowed. | Add their member ID to allowed_users, then wheems service restart. |
No 👀 in a channel; the log says the bot cannot see channel … |
The channel name is wrong, or it’s a private channel the bot isn’t in. | Fix channels.chat, or /invite @Wheems, then wheems service restart. |
No 👀 in a public channel; the log says chat on slack C… |
The bot can see the channel but isn’t a member, so Slack sends it nothing. | /invite @Wheems in the channel. |
| No 👀 after inviting the bot or editing config | The service reads channels and config only when it starts. | wheems service restart |
| No 👀 on a message with a file | Messages with files attached are ignored today. | Send text only. |
| No 👀 in direct messages, but channels work | Another workspace has direct: true, or none has. |
Check channels.chat in each workspace.yaml. Only one may have direct: true. |
| Sending messages to this app has been turned off in the Messages tab | The app was created without messages_tab_read_only_enabled: false, or Slack hasn’t caught up. |
Check App Home → Messages Tab allows messages, then quit and reopen Slack. |
Wheems: The agent hasn’t started on this yet. It may be waiting for an answer at the Mac: hc attach t7. |
The engine is showing a question, usually whether to trust the workspace folder. | Run the hc attach command, answer it, and detach with Ctrl+. wheems doctor checks folder trust. |
| Wheems: The agent is waiting for an answer at the Mac, such as a permission prompt | The agent runs with permissions: ask, or met something risky on auto. |
Answer it with hc attach. For a chief of staff you use from your phone, use permissions: auto. |
| Wheems: The agent finished without replying here. | The turn ended with no answer Wheems could read. | Attach to the session to see what happened, then reply in the thread. |
| Wheems: Something went wrong: … | An error while handling your message. | Read the rest of the notice and the service log. |
| Answers appear under the bot’s name, not the workspace’s | No identity in workspace.yaml, or the app lacks chat:write.customize. |
Add identity, and recreate or update the app from the current manifest. |
| Messages reach Wheems only some of the time | Two services are connected with the same app-level token, such as on two Macs. Slack shares events between open connections. | Run one service per Slack app: wheems service uninstall on the other Mac. |
Slack’s free plan
Link to “Slack’s free plan”Wheems works on Slack’s free plan:
- Apps: a free Slack workspace can add up to 10 apps and integrations. Wheems uses one, however many Wheems workspaces you have.
- History: a free plan shows the last 90 days of messages. Older threads disappear from view. What matters is kept on your Mac: conversations in
~/wheems/.wheems/conversations.jsonand what was learned in each workspace’s knowledge base. - Channels: each workspace after the first needs a channel. Projects with their own lead and channel (milestone 4) will need more.
Next
Link to “Next”- Quick start: the rest of setup, from nothing to a first reply.
- Running as a service: logs, restarts and launchd.
- Configuration reference: every key in
install.yamlandworkspace.yaml.