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.

You need:

  • Wheems installed, with wheems doctor passing 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.

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).

  1. Open setup and choose Channels.

    Terminal
    wheems setup

    Pick 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 app
    One 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 the
    Bot User OAuth Token (xoxb-…).
    4. Under Basic Information → App-Level Tokens, generate a token with the
    connections: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.

  2. 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 manifest

    Choose Next, check the summary, then Create. The manifest is explained line by line below.

  3. 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-.

  4. 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, pick connections:write, then Generate. Copy the token. It starts with xapp-.

  5. 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 Acme

    The 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.

  6. 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
    │ U012ABCDEF

    To 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.json

    Setup has now written the tokens to the secrets file and your member ID to install.yaml:

    ~/wheems/install.yaml
    connections:
    slack:
    driver: slack
    secret: slack
    allowed_users: [U012ABCDEF]
  7. 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.

  8. 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 @Wheems

    The 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.

  9. 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 time
    wheems service restart # after any change to Slack, channels or allowed users

    The service reads the tokens, install.yaml and every workspace.yaml when it starts. Restart it after changing any of them.

  10. 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.

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:

Slack app manifest
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: true
oauth_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:read
settings:
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: false
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.
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.

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.

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:

~/.config/wheems/secrets.json
{
"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/ when XDG_CONFIG_HOME is set. Set WHEEMS_SECRETS to use another file.
  • slack is the entry’s name. secret: slack in install.yaml points the connection at it; without secret, 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.

Rotate a token when it may have leaked, or when someone who had it leaves.

  1. 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.
  2. Give them to Wheems.

    Terminal
    wheems setup

    Choose 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.

  3. Restart the service, which reads the tokens only when it starts:

    Terminal
    wheems service restart
  4. Check:

    Terminal
    wheems doctor --quick
    Output
    Channels
    ✓ slack: Slack: Signed in as wheems in Acme

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:

~/wheems/.wheems/logs/service.log
2026-11-13T09:12:04.512Z Ignored a message on slack from U034GHIJKL, who is not in allowed_users

A member ID starts with U or W, then letters and digits, such as U012ABCDEF. It isn’t your display name or email.

  1. In Slack, select your profile picture, then Profile. For someone else, select their name.
  2. Choose ⋯ (More).
  3. Choose Copy member ID.

Setup asks for one member ID, the first time. To allow more people, edit the list by hand, then restart the service:

~/wheems/install.yaml
connections:
slack:
driver: slack
secret: slack
allowed_users: [U012ABCDEF, U034GHIJKL]
Terminal
wheems validate
wheems service restart

Everyone 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.

Each workspace’s workspace.yaml says where you chat with its chief of staff, in channels.chat:

~/wheems/workspaces/personal/workspace.yaml
channels:
chat: { connection: slack, direct: true }
~/wheems/workspaces/acme/workspace.yaml
channels:
chat: { connection: slack, channel: acme-chat }
  • direct: true means the bot’s direct messages. Only one workspace per Slack app can have them. wheems init gives them to the first workspace, and refuses a second without --channel.

  • channel takes a name (acme-chat or #acme-chat) or a channel id (C0123ABCD for public, G0123ABCD for 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 channel

    When 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.

  • 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 ls lists 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.

Slack app “Wheems” connection: slack direct messages #acme-chat #sales-chat personal chief of staff on Claude Code acme chief of staff on Claude Code sales chief of staff on Codex channel workspace Slack app “Wheems” connection: slack direct messages #acme-chat #sales-chat personal chief of staff on Claude Code acme chief of staff on Claude Code sales chief of staff on Codex

wheems setup and wheems init assume this one connection, named slack. Connecting a second Slack workspace isn’t supported by setup today.

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:

~/wheems/workspaces/acme/workspace.yaml
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.

Work down this list after connecting Slack, or whenever something seems wrong.

  1. Doctor passes. Look at Channels and Service:

    Terminal
    wheems doctor
    Output
    Channels
    ✓ slack: Slack: Signed in as wheems in Acme
    Service
    ✓ service: Running and answering
  2. The service connected and bound your channels.

    Terminal
    wheems service logs -n 20
    Output
    2026-11-13T09:00:01.118Z Workspace personal: chat on slack (direct messages)
    2026-11-13T09:00:01.402Z Workspace acme: chat on slack C0123ABCD
    2026-11-13T09:00:02.007Z Connected slack
    2026-11-13T09:00:02.031Z Wheems 0.0.3 is running from /Users/you/wheems
  3. 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.

  4. An answer arrives in the thread, under the workspace’s identity.

  5. A reply carries on. Reply in the thread: “what did I just say?”. The agent remembers.

  6. You can watch it. Run hc ls and find the session named after the workspace, then hc attach it. Press Ctrl+\ to detach without stopping it.

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.

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.json and 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.