Slack, Teams, and WhatsApp
Route messenger conversations to an agent: claiming a connector in code, limiting which Slack channels it answers in, how conversation identity maps per platform, and why the channel stays silent while the agent works.
Slack, Teams, and WhatsApp conversations route to an agent class natively. The connector carries the credentials; your code only names it.
Claiming a connector
Create the connector once via OAuth in the console. Then claim its alias:
# agent-config/connectors.yaml
claims:
acme_slack_bot: SupportAgent # alias → agent CLASS name
acme_teams: { agent: SupportAgent } # optional { agent, environment }
acme_ops_bot:
agent: OpsAgent
channels: [C0123456789, "#ops-alerts"] # allowlist — absent means all
dm: true # DMs are a separate switch
Deploy, and inbound messages start arriving as turns.
Notes:
- The manifest references an alias, never a secret. OAuth tokens stay in the connector.
- Claims can't be stolen. If another app holds the alias, release it there first.
- One owner per connector — but one agent can own many.
sf connectors list
sf connectors show <alias>
sf connectors reassign <alias> --owner-agent-id <ClassName>
Channel scoping
Slack only, for now. channels limits where the bot answers:
- Channel ids (
C…) are rename-stable and preferred. "#names"work on a best-effort basis.- Threads inherit their channel.
- Messages outside the list are dropped silently at ingress — no model spend, no reply, logged for debugging.
dmis separate and defaults to on.
Remove the channels lines and redeploy to go back to answering everywhere.
This is a real cost control, not just tidiness: a bot in a busy workspace without scoping will be mentioned constantly, and every mention is a turn you pay for.
Conversation identity
Each platform's natural unit becomes the conversation:
| Surface | One conversation per |
|---|---|
| Slack DM | User |
| Slack channel thread | Thread |
| Teams personal chat | User |
| Phone number |
Replies thread correctly without any work from you. Senders resolve against your team's people directory, and each turn carries who sent it as context.
Don't write threading logic into the prompt. Routing and delivery belong to the connector. An instruction like "reply in thread" or "post to channel X" points the model at plumbing that isn't in its inventory — a common leftover when migrating an older agent.
The silence is normal
This causes more confusion than anything else on this page, so be explicit with your users:
Replies are pushed asynchronously when the turn finishes. A slow turn delivers in full — minutes later if the agent is doing real work. The channel shows nothing between the mention and the reply.
That's expected, not a hang. And re-asking makes it worse: each mention queues another turn, so a user who nudges three times gets three turns' worth of work and interleaved answers.
An agent with a pinned repository additionally pays for repository materialization on the first turn of a new conversation, so its first answer in a fresh thread is slower than the rest.
One message means something different. If you get ⚠️ Could not hand your message to the agent, that's a handoff failure — auth, network, or the worker being unreachable — and retrying is reasonable. Silence means working; that error means it never started.
Practical advice
- Set expectations in the agent's first reply. For long-running work, an agent that says "looking into this, I'll follow up here" before doing the work costs one extra cheap turn and eliminates the nudging.
- Scope channels before inviting the bot widely. Easier than explaining a surprise bill.
- Use the same agent across channels. The rules live in the agent and its domain functions, so a support agent answering the website chat and Slack behaves identically in both. That's the payoff of keeping guardrails out of channel handlers.
- Test with a real message. A connector row showing the right owner is not proof that routing works. Post in the channel and confirm the turn appears:
sf api get "/apps/<appId>/agent-turns?limit=20"
Next
- Browser chat — the same agent on your website.
- Observability and costs — confirming turns ran and what they cost.
- Channels overview — choosing the right channel shape.
Frequently asked questions
How do I put an agent in Slack?
Create the Slack connector once through OAuth in the console, then claim its alias for your agent class in agent-config/connectors.yaml and deploy. Credentials never touch your source.
Can I limit the bot to certain channels?
Yes, for Slack: list channel ids under the claim. Messages outside the list are dropped at platform ingress — no model spend, no reply. Omitting the list means it answers everywhere.
Nothing happens for a minute after I mention the bot. Is it broken?
Almost certainly not. Replies are pushed when the turn finishes, and real work takes real time. The channel shows nothing in between. Don't re-ask — each mention queues another turn.
How are conversations separated?
By the platform's own natural unit: a Slack DM is per user, a Slack thread is per thread, a Teams personal chat is per user, and WhatsApp is per phone number. Replies thread correctly on their own.
Do I need to tell the agent to reply in a thread?
No, and you shouldn't. Threading and delivery are the connector's job. Prompt instructions about replying in threads tell the model to call something that doesn't exist.