lua channels.
Verified against lua-cli 3.41.0. The CLI steps on this page (creating the app from Lua’s manifest, the signing secret, and the update and re-check actions) ship in lua-cli 3.42.0. lua-cli 3.41.0 can no longer connect Slack, because the signing secret is now required: until 3.42.0 is out, connect Slack from the admin dashboard.
Before you begin
- A Slack workspace where you can create and install apps.
- An agent with a promoted production version; see Release an agent.
Create the app from Lua's manifest
- Dashboard
- CLI
Provider console: copy the values Lua needs
- Signing Secret, always: Basic Information → App Credentials → Signing Secret (select Show). It is not the Client Secret just above it.
- Private: install the app first with Install App → Install to Workspace → Allow, then copy the Bot User OAuth Token, which starts with
xoxb-. - Public: the App ID, Client ID and Client Secret from Basic Information → App Credentials.
Connect it
Public only: install through Lua
https://auth.heylua.ai/slack/install/<appId>.Verify
hello; it answers in a thread under your message. That first message also confirms the signing secret: the connection’s card stops showing Verifying. Invite the app to a channel and mention it; it answers in a thread under that message. lua channels list shows a SLACK entry.Signing secret
Slack signs every event it sends with the app’s signing secret. The first event that matches the saved secret confirms it; from then on Lua rejects any event that doesn’t match, including one with no signature at all, so nobody can make the agent act on a forged message. Until that first match, an event that doesn’t match is still answered, and the card says Signing secret doesn’t match: paste it again from Basic Information. After it was confirmed, an event that doesn’t match is rejected and the connection shows Signature check failed. If you regenerated the secret in Slack, paste the new one. If you didn’t, it was most likely a forged request, and the message clears with the next real event. Saving a different secret for a connection that is already verified never turns the check off. Until Slack sends an event signed with the new secret, events signed with either the previous or the new one are accepted, and the status shows Verified; new secret not confirmed yet. The first real event signed with the new secret makes it the only one. So a mistyped secret doesn’t stop the bot, and forged events stay rejected. While Signature check failed shows, though, Slack is already signing with a secret Lua doesn’t have: only the right one brings the bot back. Connections made before Lua asked for the secret keep working as before and show Update available; see Update an existing app.Update an existing app
Lua can’t change an app in your workspace; you apply each new version of the manifest. The Slack connection under Channels shows Update available until Slack has shown Lua the current manifest and the signing secret is saved, and it says which of the two is missing. Select Update; the dialog walks you through it:- Open App Manifest in Slack, switch to JSON and keep a copy of what’s there. Pasting it back and reinstalling undoes the update.
- Apply Lua’s manifest on the same JSON tab and Save Changes. It keeps your app’s name and every permission the app already has. If your app has features of its own, such as slash commands, shortcuts or agent mode switched on in Slack, the dialog first asks for your current manifest and gives you a merged one that keeps them, with a list of what it changes in Slack.
- Save the signing secret.
- Reinstall: a public app with Install in Slack; a private app with the banner Slack shows, or Install App → Reinstall to Workspace. The bot keeps its token and keeps answering.
- Open the bot’s DM in Slack. Slack sends Lua a signed event that confirms both the signing secret and the new manifest, and the dialog says Done.
When the app is uninstalled
If someone removes the app from the workspace or revokes its token, Slack tells Lua, Lua checks with Slack that the token no longer works, and the card shows Uninstalled in Slack. Replies that fail because the token is dead trigger the same check. The agent’s settings and conversations are kept. Select Reinstall: a public app reinstalls through Lua; a private app needs a new bot token from Install App → Reinstall to Workspace, pasted with the signing secret, and the same connection comes back.Settings
Open Agents, select the agent, and in the Channels section of its Overview tab select the Slack connection, or select it on the Channels page. The panel shows the connection’s status first, then every setting with its default. A choice saves when you pick it and can be undone from the confirmation under it; a text field saves when you leave it. Change history lists who changed which setting, and when.null resets a setting to its default. The response is the connection’s full status and settings, each with its value and its source. <channel> is the connection’s id or its identifier (T…-A…).
400 with errors, keyed by setting, and changes nothing. GET on the same path reads the settings, and GET …/settings/history lists past changes, newest first. The older PUT …/channels/<identifier> with {"slack":{…}} still takes responseMode, autoRespondChannels, reactions and externalUsers, checks them the same way and records them in the history. Changing settings needs the Builder role on the agent; Operators can read them.
While the agent works
As soon as the agent takes a message, it puts 👀 on it, and Slack shows “Your app is working…” (with your app’s name) under it (inside the thread once the thread has replies). The status goes away when the answer arrives; 👀 then becomes ✅. If the turn fails, both go away and no ✅ is added. A button click shows the status only. The status needs onlychat:write, which every install has. The reaction needs reactions:write, an optional permission: an app installed before it was in the manifest shows the status alone until it is updated, and so does an app whose admin left it unticked.
Turn both off with Show that the agent is working, and pick other emoji under Settings.
Agent mode
Slack’s agent mode shows the bot as an agent: an AGENT badge next to its name, up to four prompt buttons at the top of its direct message, and a Stop button under “Your app is working…” while it answers. It is off by default. Leave it off for workspaces with guests or Slack Connect partners. Turn it on- A new app: in the Create your Slack app step, tick Agent mode. Check the description (filled in from the agent, up to 300 characters, shown in Slack’s Agents & tools list) and add up to four prompts, each a button label and the message a click sends. The create link carries them.
- An app you already have: tick Agent mode on the step where you paste its values.
- A connected app: open the connection’s settings and select Set up next to Agent mode. Save it there, then apply the updated manifest in Slack and reinstall, as in Update an existing app. The same step changes the prompts or turns agent mode off.
- CLI (from the lua-cli release after 3.42.0-beta.1):
lua channelsasks about agent mode when you connect Slack; on a connection, Turn agent mode on or off, or change its prompts saves it and prints the manifest to apply.
[Reply stopped by <name> before it was sent], so a later question knows what happened. What a tool already did, such as an email sent or a message posted from code, is not undone. A Stop in the first second or two, before the agent has started, also ends the turn, but the message is not kept in the agent’s memory.
While a turn runs, an agent-mode app shows Slack’s agent status in place of the “is working…” status described above. It clears when the answer arrives, when the turn fails, and on Stop. An app switched to agent mode in Slack without the agent_session_stopped event keeps the plain “is working…” status and shows no Stop button.
Home tab and greetings
Home tab. Selecting the bot’s name in Slack opens its Home tab. The page shows:- the bot’s name and one line about it;
- how to reach it: a message in the Messages tab (left out when the bot answers mentions only), a mention in a channel it’s in (it answers in the thread), or
/inviteto add it to a channel; - example questions, under Try asking;
- a support line, when you wrote one;
- the note “Answers come from AI and can be wrong.”
Conversations
A direct-message thread is a private conversation between one person and the agent. A channel thread and a group DM (a direct message between several people and the app) are shared conversations: the agent keeps one transcript for everyone in them and knows who said what. Channels here are public and private channels, including Slack Connect channels shared with other workspaces.- Later messages are context. After the first mention, every message in that thread or group DM joins the transcript without a reply, so the next mention knows the whole discussion. In a channel the agent follows the thread, not later top-level messages.
- Edits and deletes follow Slack. When someone edits a message, the stored copy is replaced by
(edited) <new text>; the old wording is gone. When someone deletes a message, including the first message of a thread that has replies, it stays in the transcript as[message deleted], with its sender and time. This applies in channel threads, group DMs and direct messages, to the agent’s memory, the admin dashboard, and your org memory. An edit never gets a reply, even when it adds a mention. What is not changed: the agent’s own earlier replies (edit or delete them in Slack), messages sent before 29 September 2026 (a delete or edit of those is not applied), a long conversation’s summary notes, files already copied when the message arrived, and facts the agent saved to a person’s memory (remove them from memory settings). - Speakers are named. Each message reaches the agent with the sender’s name, and people mentioned in it appear by name. Posts from other Slack apps, such as alerts or reports, are kept as context under the app’s name, and an app that mentions the agent gets an answer. An auto-respond channel answers people only, so two agents there never answer each other. A message an app posts for someone with their own Slack sign-in shows in Slack as theirs, and the agent treats it as theirs: it answers it, remembers it under their name, and follows their edits. Slackbot’s own posts, such as reminders, are not kept.
- Who each person is. Lua knows a Slack person by their own workspace and Slack member id (their organisation, on Enterprise Grid). The first time the bot sees them, the email Slack shows for them links them to the Lua account with that email; after that the member id decides, so a changed or hidden email never turns them into someone new. People from other companies are recognised the same way. Only the person talking to the bot counts as signing in: others writing in the thread, people mentioned, earlier authors and people you message from code are recorded without it. Each other app is one Lua user, whichever way Slack names it. If Slack can’t say who someone is, their message in a thread isn’t kept, and a person talking to the bot is asked to send it again.
- Slack Connect. People from other workspaces in a shared channel are answered like anyone else, unless People from other companies says otherwise (see Settings), and the files they attach reach the agent too. A file the bot can’t open reaches the agent as a note naming it, even when the message also has text, so the agent can tell the person.
- Personal memory stays personal. Nothing a person told the agent in a direct message is brought into a channel or group DM. Each channel thread is its own conversation: nothing carries over from one thread to another, or from a thread into direct messages.
- Direct messages. To continue a conversation, reply in the thread under the agent’s answer; a new top-level message starts a fresh conversation. Each thread has its own transcript, though an agent with personal memory can still recall what it learned about the person earlier.
link-me-to:<agentId>andremove-my-linkwork in a direct message; see Test an agent on Lua’s channels. - Your code sees the room. In a tool, a condition, or a processor,
Lua.request.conversationlists the people in a channel thread or group DM and marks the current speaker. It isundefinedin a direct message. ItsexternalIdis the thread’s key,T…:A…:C…:threadTs(workspace, app, channel, thread), orT…:A…:G…for a group DM; pass it toChannels.sendasto: { conversationId }to post there later through the same bot (Post from code). - One reply per message. Slack sometimes delivers the same event twice, and a mention in a channel arrives both as a mention and as a message; the agent answers each message once. When a turn fails, the agent posts a short notice instead of staying silent.
- The admin dashboard shows the room. Activity → Group chats lists each Slack thread and group DM, titled
#channel · opening wordsorGroup DM · opening words, with its transcript and the people in it. The tab is read-only. Direct-message conversations appear under Conversations. - Rooms close when the agent leaves. Disconnecting Slack in the dashboard archives the connection’s threads and group DMs (Disconnected in Group chats); removing the app from a channel archives that channel’s threads (Bot removed), and uninstalling the app in Slack archives them all (Uninstalled). Their transcripts are kept, and a mention in the same thread after the app is back reopens the room with its memory. Someone who leaves a channel leaves its threads, and a renamed channel renames them. The channel events behind this reach apps created from, or updated to, the current manifest.
Channel behavior
Files
When the agent’s answer carries adocuments block, the files follow the answer as one more message in the same thread or direct message.
- With
files:write(in the manifest since 29 September 2026; see Update an existing app), each file stored on Lua’s CDN, up to 25 MB and 10 per message, is uploaded as a Slack file under a📎line naming the files. People preview and download it in Slack, under Slack’s access rules. - Without it, and for a file hosted elsewhere, larger than 25 MB, or one Slack refuses (a workspace that blocks uploads or that file type), the message lists each file as a link:
📎 Loan Agreement (loan_agreement.pdf). A CDN link opens for anyone who has it. - After you re-apply the manifest, the first answer can still send links; the agent learns the new permission from that answer’s post and uploads from the next one.
- The agent remembers the block it wrote: the file’s name and link. Each uploaded file message has its own delivery record, keyed by Slack’s file id.
Post from code
Channels.send({ channel: 'slack', to, text }) posts through the agent’s own Slack bot, the app you connected above, never a Lua bot. It works from a tool, a job, a webhook, or a workflow code step. Address exactly one of to.conversationId, to.userId, or to.email; a phoneNumber is refused.
Lua.request.conversation.externalId names its bot. Otherwise the send uses the agent’s only active Slack connection; an agent with more than one must pass options.channelIdentifier, the connection identifier T…-A…, or the send answers 400 with a list of its connections.
The bot must be in the channel. Someone invites it with /invite @your-bot. Posting in a public channel the bot was never invited to needs the chat:write.public permission: apps set up from the dashboard’s manifest have it, and older apps get it by applying the updated manifest and reinstalling. A private channel always needs the invite.
What arrives. The text renders exactly like the agent’s replies: Slack Markdown, ::: components (a ::: documents block included, see Files), and a long text split into consecutive messages. A text that is only a documents block posts a 📎 line naming the files first, and the files under it. One send makes one delivery record, and messageId is the first message’s ts. If a later part of a long text fails, the send still returns delivered: true, with a warning.
What the agent remembers.
- A post into a thread or group DM where someone has already mentioned the bot is recorded in that conversation, so the next mention knows what the agent posted. The result has
persisted: true. - Where nobody has mentioned the bot yet, the result has
persisted: falseand awarning. When someone later mentions the bot in that thread, the history read at the first mention brings in the bot’s earlier posts. - A DM is recorded under the person’s Lua user, in the conversation of the post’s own thread: a reply in that thread continues with the context, and a new top-level message starts a fresh conversation, as with any direct message.
- Posts your code makes by calling Slack’s API directly are not recorded. Use
Channels.sendwhen the agent should remember them.
X-Idempotency-Key makes a retry safe when a call got no answer: a replayed key returns the first send’s outcome instead of posting again.
Elsewhere. user.send() never picks Slack. A workflow run started with replyTo: { channel: 'slack', threadId } posts its result through the same bot (Workflows REST). With the outboundChannels feature on, the model gets sendSlackMessage.
Limits
- Permissions: twelve required, eight optional, and
chat:write.public, listed with what each is for on Slack permissions. - One private connection per workspace and app; one public connection per App ID per agent.
imagesrenders up to 10 images.documentsuploads up to 10 files per answer, each up to 25 MB and stored on Lua’s CDN; the rest arrive as links.Channels.sendposts about one message per second per channel, Slack’s own limit for the bot.- Link commands (
link-me-to:,remove-my-link) and the links they create apply to direct messages only, not to channels or group DMs.
Troubleshooting
Slack says the Request URL didn't respond
Slack says the Request URL didn't respond
Missing required scope: users:read
Missing required scope: users:read
xoxb- token.A public channel stays Pending setup
A public channel stays Pending setup
This install link has expired
This install link has expired
lua channels.Signing secret doesn't match
Signing secret doesn't match
Signature check failed
Signature check failed
This token was revoked
This token was revoked
The agent answers DMs but ignores a channel
The agent answers DMs but ignores a channel
dms_and_mentions mode. Mention the app, or set responseMode to auto_respond and add the channel’s ID to autoRespondChannels.The agent forgot what we discussed
The agent forgot what we discussed
Channels.send answers not_in_channel
Channels.send answers not_in_channel
/invite @your-bot in the channel and send again.The agent answered someone from another workspace
The agent answered someone from another workspace

