How messages are routed
Binding a single channel needs the project Admin role (
project:manage). The organization-wide defaults —
Slack Direct messages, a custom app’s Everywhere the app is, Teams Direct messages and
unassigned chats — and creating Slack channels from Luno need an organization owner or admin
(like connecting a WhatsApp number), because they route the whole organization’s chat.
Each thread is its own conversation (agent session), so follow-ups in the thread keep context.
Moving a channel to another agent starts fresh conversations with the new agent. Replies of the
shared app carry the name of the agent that answered (Slack shows it as the sender name).
While the agent works you see it: Slack shows a 👀 reaction on your message, the “is typing…”
status in the thread, and the answer appearing as it is written; Teams shows the typing indicator.
Approval buttons appear in the thread when an action needs a human (see Approvals in
chat). A message Slack or Teams delivers twice is answered once.
Several agents in one workspace
One company can run several agents in the same Slack workspace or Teams tenant — for example a contracts agent people DM with documents, a hot-leads agent that posts in#ventas and chats there,
and an HR assistant that only works in DMs.
Channels
-
Bind each agent where it should answer.
#ventas→ leads (All messages if it should follow every message there),#contracts→ contracts. -
Restrict an agent to DMs with
channelsin its definition; mentioned anywhere else it replies with a short notice instead of running. The rule belongs to the agent, so it holds however the agent was reached — a channel binding, a DM pick, or a handoff: -
Let an agent speak first with
notify: the thread it starts keeps answering with that agent, whatever the channel is bound to.
Direct messages: one bot, several agents
Direct messages to the one Luno bot need a choice. There are three ways, and they combine:Agents people can switch to
On the workspace’s Direct messages binding (Teams: Direct messages and unassigned chats), open Can switch to and tick the agents of the same project people may talk to in DMs. From the CLI:- Slack:
/lunolists the agents (and who answers you now),/luno hrswitches your DMs to HR,/luno resetgoes back to the default. The app’s Home tab shows the same list with a button per agent. - Teams (1:1 chats):
agentsshows a card with a button per agent,use hrswitches,use defaultgoes back. - Handoffs: whoever answers a DM gets a
handoff_to_<slug>tool for each listed agent (see handoffs), so “can I talk to contracts?” just works and an agent can pass the conversation on when the question is not its own.
channels rules.
Receptionist
Make the DM agent a front desk whose only job is to route. Start from the template, deploy, bind it to Direct messages and list the specialists under Can switch to:/luno. The receptionist is a normal agent in your project: edit its
prompt, pick a cheaper model, or add handoffs in code so it also routes in the web chat. Any
agent can be the DM default; listing other agents is what makes it able to route.
Dedicated bot per agent (Slack)
A separate Slack app named after the agent (@HR), created for you with Slack’s App Manifest API:
- A workspace admin creates an app configuration token at api.slack.com/apps → Your App Configuration Tokens → Generate Token (valid 12 hours).
- Run
luno channels create-slack-app hr --config-token xoxe.xoxp-…(or the custom app wizard in the dashboard). Luno creates the app (--name/--handledefault to the agent’s) and prints an install URL. - Open the URL and Allow. The agent is bound to the app everywhere it is: its DMs and mentions go to HR.
SLACK_APP_CONFIGURATION_TOKEN so
the token is optional — the apps are then owned by the operator’s Slack account and install in
the operator’s workspace unless distribution is enabled for them.
Teams: each Teams bot is its own Azure Bot registration, and Teams lets one bot id belong to
only one app in a tenant (two app packages sharing the shared bot would also share the same 1:1
chat). Dedicated Teams bots per agent are not supported yet; in Teams use the picker or a
receptionist with the shared Luno bot.
Slack connection kinds
In the web app, Integrations → Connect Slack offers three options. The CLI mirrors them withluno connect slack --kind <kind> (no --kind opens the same chooser):
The Luno OS Slack App and the
slackbot catalog slug are the same thing. The Integrations page and
chooser say “Luno OS Slack App”; luno credentials list shows the underlying catalog slug
slackbot. luno connect slackbot still works and opens the same dialog as
connect slack --kind luno.Custom Slack Apps use the same credential key. Luno OS and custom installs both materialize
slackbot credentials; the OAuth connection behind the instance (gateway slack vs
slack-app:<handle>) is what changes the bot identity. Existing custom apps may need a
reinstall to pick up scopes such as chat:write.public or users:read.email.--kind luno). For a dedicated agent identity, use the Custom Slack App flow. A Personal
Slack Account connection does not register a gateway workspace
(luno channels accounts --platform slack stays empty).
Example requests
Ask your coding agent which agent to put in Slack and how it should respond.“Put our support agent in the #support channel and have it reply only when mentioned.”
“Connect the data analyst agent to #data so the team can ask questions there.”
“Give Delbert its own Slack handle so it can be mentioned in any channel.”
Connect agents to Slack (web)
Each agent’s detail page has an External Channels panel (right side).1
Open the agent
Go to Agents and select your agent. Open the External Channels panel (configure rail).
2
Choose Luno OS app or custom app
Click Connect. Pick Use the Luno OS Slack App (channel bindings as @Luno OS) or Create your own Slack App (one agent ↔ one custom bot identity).
3
Luno OS path — add a channel
If the Luno OS Slack App isn’t connected yet, complete OAuth first (Add to Slack; a workspace admin may need to approve). Then Add channel to pick an existing channel, or Direct messages to make this agent the one that answers DMs to the Luno app in that workspace. Invite the app to private channels (
/invite @Luno) so they show up.4
Custom path — wizard
Follow the custom app wizard (name + handle → Slack manifest → paste Client ID / Secret / Signing Secret → install). The installed app then shows up as its own workspace (
@handle · Workspace) in External Channels: pick Everywhere the app is to answer every mention and DM of that bot with this agent, or bind single channels.5
Message the agent
Message the bound channel (Luno OS) or mention the custom bot. Review runs in Runs by filtering Type to Agent.
?rail=configure&slack=luno or slack=custom on the agent URL (or luno channels setup <slug> --custom).
Channel access and credentials
Once an agent is bound to a Slack channel (or custom app), anyone who can message it uses the agent’s tools and credentials — not their own. Only bind agents to channels whose members you trust with everything the agent can do.Speaker credentials
A personal assistant should read your inbox when you ask, and a teammate’s when they do. Opt the agent in withcredentials: "speaker":
- Tools whose credentials are
.scope("user")use the speaker’s own connection. A speaker who has not connected it gets a reply with a link to connect it, then asks again. - Project and organization credentials work as before.
- Owner tables (
defineTable({ owner: true })) show the speaker’s own rows. The conversation is then stored encrypted for that speaker, so a shared thread that already holds one speaker’s rows answers other speakers with the agent’s credentials only.
credentials: "agent", never uses anyone’s personal credentials.
Who is speaking
For every Slack or Teams message Luno looks the author up in the platform’s directory — Slack: name, display name, email, title, timezone, bot or not, workspace; Teams: name, email (or UPN), Entra object id, tenant — and, when the email belongs to an active member of your Luno organization, that member. The agent reads each message as[Ana Pérez · ana@acme.com · HR Manager] text, so in a
channel it knows who said what.
Tools get the same data as trusted context: it comes from Slack / Teams, never from what the
message says, so a tool can rely on it even when someone writes “I am the CEO”. An action reads it
from its third argument:
ctx.speaker and ctx.conversation are unset.
Slack needs the users:read.email scope for emails (the Luno app has it; reinstall older custom
apps). Profiles are cached for five minutes.
DMs vs channels
When the agent answers in a channel: by default only when mentioned, and then in threads it
already takes part in (Mentions + follow-ups). With All messages it answers every message
posted in the channel. Change it in the External Channels panel (Respond to) or with
luno channels bind|update-binding … --listen all|mentions.
Teams and All messages. Teams only sends a bot the messages that mention it, unless the app
has resource-specific consent. Once a Teams binding is set to All messages, Download app
package includes the permissions (
ChannelMessage.Read.Group, ChatMessage.Read.Chat):
upload it again as an update in the Teams admin center. Until then, people mention the bot.project:manage).
Agents that post first
notify posts a message as an agent — into a channel or to one person by email — from a workflow,
trigger or tool. It uses the agent’s bindings on that platform to pick the workspace (or tenant) and
the bot, starts a thread, and binds the thread to the agent’s session, with the posted text in its
history. Replies in that thread continue the conversation with the agent.
It returns
{ platform, channelId, threadId, sessionId } (email has no thread or session).
WhatsApp only allows replies inside its 24-hour customer-service window and is billed like any
reply; email goes out from Luno’s sending address with the agent as sender name.
Email is off until an organization admin turns it on (Governance → Organization details → Report email, or
PUT /api/organizations/:organizationId/report-email with { "reportEmailEnabled": true, "allowExternalRecipients": false }). By default it only
reaches members of your organization; with allowExternalRecipients it can reach anyone, and those
emails carry an unsubscribe link (and the List-Unsubscribe header mail apps show). Someone who
unsubscribes is never emailed by your organization’s agents again. Each organization can send at
most 20 emails a minute and 200 a day, and sending stops when the organization is out of credits.
notify throws with the reason (403 off / not a member / unsubscribed, 429 rate limit, 402
credits).
Reports
report is one document — title, optional summary (markdown), sections and actions
(buttons), optional footer — rendered as Slack Block Kit, a Teams Adaptive Card, WhatsApp text
with reply buttons, or an HTML email. Sections are text (markdown), fields (label / value),
table (columns + rows), list (items with detail, url and their own buttons) and
divider.
Buttons only carry an opaque id; Luno keeps the target and accepts the click for 14 days, in the
thread where it was posted. Slack and Teams show every button, WhatsApp up to 3 hook buttons
(its customers aren’t members of your organization, so workflow buttons are left out), and email
only links.
notify runs inside deployed Luno runs
(workflows, triggers, agent tools), not from the CLI. On Slack the app must be in the channel
(/invite @Luno); a person by email needs a Slack account in the workspace. On Teams a person is
found by email among the members of the teams and group chats the bot was added to, and a 1:1
notification continues in that person’s chat with the bot.
To show a report inside a project app (src/apps/), render it with ReportView from
@meetluno/app-client/react: workflow buttons run as the signed-in user (the workflow must be in
the app’s uses.workflows) and hook buttons resume their hook with by: <user email>.
Manage Slack from the CLI
Prefer the happy path for the Luno OS Slack App:luno auth login first. Hosted sessions are already authenticated and must use --print-url, since they cannot open a browser.)
Verify the gateway workspace registered before binding:
channels setup creates the Slack channel when needed (or binds an existing one with the same name), then binds the agent with Mentions + follow-ups (same default as the web UI).
For a custom @handle, open the agent wizard (user finishes in the browser):
Advanced primitives
Reply behavior matches the External Channels UI:--listen all answers every message in the channel (UI Respond to → All messages); --listen mentions goes back to mentions.
dm:<team-id> (Slack DMs), app:<slack-app-slug>
(a custom app everywhere it is) or tenant:<tenant-id> (Teams). Teams uses the same commands with
--platform teams --account <tenant-id>.
The agent positional takes the slug from defineAgent({ slug }) after deploy (or the platform agent id). See the CLI reference.
Connecting the Luno OS Slack App registers the gateway for your organization; you don’t need to add Slack to luno.config.ts.
Approvals in chat
When the agent calls an action or tool markedneedsApproval: true, it posts the pending call (tool name and input) in the thread with Approve and Deny buttons and waits. Approving runs the call and the agent finishes its reply in the same thread; denying tells the agent the call was refused. The card then shows who decided. When the tool has approvers, the card lists them and only those organization members (matched by the chat account’s email) can decide; anyone else gets a notice and the buttons stay. Without approvers, only a chat user whose email matches an active organization member with run access to the project can decide — being in the channel is not enough. WhatsApp never shows approval buttons (see WhatsApp). For tools marked editable, Slack adds Edit and approve (a form with the input as JSON; errors show in the form) and Teams adds an editable JSON field with Approve with edits; the same approver checks apply, and the card then says “Approved with edits by …”. The same pending calls can be decided from the web app chat or luno agents approvals. See human approval.
Other external channels
Slack, Microsoft Teams and WhatsApp (below) are available today. The channels listed here will be added over the coming weeks. You can request specific channels by contacting us at team@meetluno.ai and we will add them promptly.- Telegram
- Linear
- Discord
- Google Chat
Next steps
Run agents
Prompt the same agent directly while testing channel behavior.
Agent runs
Inspect Slack-started agent sessions in Runs.
Credentials
Connect OAuth credentials used by tools and integrations.
Build agents
Tune the agent prompt and tools for channel use.
Microsoft Teams
People can message the Luno bot 1:1 in Teams or@mention it in a channel or group chat; the
agent answers in the same thread, and each thread keeps its own session.
1
Install the app
Open an agent → External Channels → Microsoft Teams → Connect → Download app package. A
Teams admin uploads it: Teams admin center → Teams apps → Manage apps → Upload new app.
2
Link your tenant
In the same dialog (org owner or admin), Create link code, then send
link XXXX-XXXX to the
Luno bot in Teams. The code works once and expires after 15 minutes. The link then waits for
an org owner or admin to approve it in the same dialog, which shows the tenant id and the
Teams name and email of whoever sent the code (Reject it if you don’t recognize them).
Approving sends you to Microsoft: sign in as an administrator of that Microsoft 365 tenant
(Global, Privileged Role, Cloud Application or Application Administrator) and grant consent to
the Luno app; Luno verifies the consent with Microsoft before it confirms the link. Until then
the bot answers nobody in that tenant. One Luno organization answers per
Microsoft 365 tenant: a tenant another organization already confirmed is never moved — the bot
replies with the tenant id to raise with Luno support, and the attempt is recorded in both
organizations’ audit logs. To unlink a tenant, disconnect it under Integrations → Microsoft.3
Choose where each agent answers
Add the bot to the teams it should work in (team → Apps → add Luno). Back in External
Channels, open the tenant and Add channel: pick a channel the bot was added to, or
Direct messages and unassigned chats for 1:1 chats and every conversation without its own
agent. Bind other channels from other agents’ pages.
help anytime.
Alternatively, an admin can link the tenant from Integrations → Microsoft: connect with an
account holding an active Global, Privileged Role, Cloud Application, Application or Teams
Administrator role, then pick the agent under Teams bot (that sets the tenant default).
Disconnect there unlinks the tenant and removes all its channel bindings.
1
Connect a number
Open the agent, then External Channels → WhatsApp → Connect (org owners and admins). Luno
opens Meta’s WhatsApp Business setup (hosted by our WhatsApp partner, Kapso): sign in with
Facebook, pick or create the business portfolio and WhatsApp Business account, and verify the
phone number. You can keep using the WhatsApp Business app on the same number
(coexistence) or dedicate it to the API.
2
Come back to Luno
When the setup finishes you land back on the agent page and the number shows under
WhatsApp. If the setup failed, start again from Connect.
3
Choose the agent
Click Answer next to the number. One agent answers per number; choosing Answer on
another agent moves the number to it, and Stop disconnects it (customers then get a short
“not connected yet” reply).
- Text, voice notes and captions. Voice notes are transcribed and media captions are read; stickers, locations and other types get a short “I can only read text” reply.
- Read receipts and typing. The message is marked read and WhatsApp shows typing… while the agent works (for up to about 25 seconds).
- Formatting. Bold, italics, strikethrough and code from the agent’s answer use WhatsApp formatting; links stay as plain URLs; long answers are split into several messages.
- Approvals. When the agent pauses on a tool marked
needsApproval: true, the customer gets a short message saying the request is waiting for approval in Luno — never Approve / Deny buttons. Your team decides it from the web app chat,luno agents approvals, the API, or (when the tool has approvers) a Slack or Teams card; the customer then gets the answer in WhatsApp.
For platform operators
Running your own Luno OS platform? Create the shared apps once per environment with the setup guides that ship with the platform source:deploy/slack/README.md (Slack app),
deploy/teams/README.md (Teams bot) and deploy/whatsapp/README.md (WhatsApp through Kapso).