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.

The Spikefrost Team30 Jul 20263 min read

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.
  • dm is 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
WhatsApp 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

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.