Agents in shared channels.
Put an assistant into a team channel and the hardest problem is not tone, it is that two things which were always the same person have just come apart: whoever the agent is working for, and whoever wrote the text it is reading. In a one-to-one thread the instruction and the authority arrive together in the same message. In a channel of nine people, one colleague's pasted customer email is an instruction with no authority behind it, and every framework you might build on still has exactly one user field to put all of that in. Design the addressing and the authority first; the personality is the easy part and nobody quits over it.
Name what actually broke: one field, four different questions.
A single-user assistant answers four questions with one fact. The person typing is the person being served, is the person whose permissions apply, is the person accountable afterwards, and their message is the only instruction in the context. A shared channel separates all four, and most agent products ship having noticed only the first.
- Who is being served? The requester, usually — but in "@agent can you get Priya the Q3 numbers", the beneficiary and the requester differ, and so does what each is allowed to see.
- Whose permissions apply? Not the agent's, and not the admin's who installed it. See agent identity and permissions — the default in every chat platform is an app-level token, which is precisely the wrong answer.
- Who is accountable? One named human, recoverable months later, per the attribution argument in shared and multi-user agents.
- Which text in the channel is an instruction? This is the question with no platform support at all, and Steps 2 and 4 are about the two halves of it.
The cheap test for whether you have designed this: take a channel transcript where three people spoke and the agent acted, and try to answer "on whose authority" from the stored record alone. If the answer is "the workspace's", you have built an ambient-authority bot and the channel is its scope.
Make speaking a decision with a written rule, and default to silence.
An agent that answers everything resembling a question is the single most reliable way to get itself muted, and mute is where channel agents go to die. Silence is not modesty here; it is the mechanism that keeps the channel usable for its original purpose. Write the trigger policy explicitly and ship it as configuration, not as a prompt instruction.
- Explicit address is the only default. A mention, a slash command, an emoji reaction, a thread the agent already owns. Everything else is reading, not listening.
- Ambient triggers are opt-in, per channel, by the channel. "Watch for anything that looks like an incident" is a legitimate feature and an unacceptable default. Let a channel turn it on, show who turned it on, and make the agent's first ambient message in a channel say what caused it to speak.
- A reply in a thread is addressed; a message in the channel is not. Threading is the one addressing primitive every platform already has, and it doubles as your scoping boundary: the thread is the unit of work, the conversation, and the permission check.
- Rate-limit by room, not by user. The cost of a redundant message is paid by everyone present, which is the notification budget argument with a multiplier on it. Two messages into a nine-person channel is eighteen interruptions.
- Never speak on backscroll. When the agent is added to a channel, or wakes after an outage, it must not act on the history it can now read. Everything before the moment it was addressed is context at most — Step 4 argues it is less than that.
One asymmetry is worth building deliberately: it should be easier to stop the agent than to start it. Any participant can end a run the agent is doing in their channel; only the requester can redirect it. That is the channel version of interruption and steering, and it is what makes people willing to leave it in the room.
Resolve authority at the moment of the request, from the requester.
Channel platforms make the wrong thing easy: the bot holds one token, joins a channel, and can therefore read and write whatever the channel can. That design means the agent's reach is the union of everything it has ever been invited to, which nobody intended and no admin can see.
- Act as the requester, not as the app. Exchange the mention for a user-scoped token or an on-behalf-of assertion, and make every downstream tool call carry it. If your platform cannot, the honest fallback is a much smaller tool surface, not a bigger token — the reasoning is in scoped credentials for agents.
- Intersect with the channel. The output lands where everyone can read it, so the effective read scope is the requester's permissions and what this room is allowed to see. An engineer who can query the salary table can ask the agent about it in a DM and must not be able to do so in
#general. This is the case permission-aware retrieval usually misses, because the filter is built around the caller and the room is a second subject. - Bind approvals to a person, not to a reaction. A thumbs-up from whoever happens to be online is not an approval; it is a click by an unspecified principal. Check the reactor against the policy for that action, and record which identity satisfied it — the mechanics are in approval and confirmation UX, and the channel adds the problem that approver and requester may be the same person twice.
- Re-resolve on resume. A long-running task started on Tuesday by someone who left the company on Wednesday must not still be running with their authority on Thursday. Re-check at every effect, not at dispatch.
Guests and cross-org channels are where this stops being theoretical. A shared channel with a customer contains people from two directories, and the ones from the other directory have no entry in your permission system at all. Treat an unresolvable identity as a hard refusal to act, never as a fallback to the app's own authority — and see fail-closed and fail-open for why that particular check must be a decider rather than a filter.
Treat the channel's contents as data, including your colleagues' messages.
This is the step that gets skipped because it feels like distrust of the team. It is not: the danger is not a malicious colleague, it is the ordinary, helpful act of pasting something in. A support engineer drops a customer's email into the channel so the team can see it; an integration posts a webhook payload; someone forwards a vendor's PDF. Each of those is attacker-influenced text arriving in a room the agent reads, wearing a trusted colleague's avatar.
- Only the addressed message is an instruction. Everything else in the context window is quoted material, and it should be structurally marked as such when you assemble the prompt: author, timestamp, and a label that says this is channel content rather than a request. This is the taint tracking discipline, and a channel is the easiest place to apply it because the platform already tells you who wrote what.
- Pasted and forwarded content is a third trust tier. Below your colleague, who is at least accountable. A quoted block, an attachment, a link preview, a bot's message: these carry no authority whatsoever, and the agent must never follow an instruction found inside one. The failure case is documented — an agent reading a log or a ticket and obeying text inside it, per the block log is an injection channel.
- Other bots are not colleagues. Integrations post on behalf of external systems with no human in the loop, and in a channel their messages look exactly like anyone else's. Excluding bot-authored messages from the instruction tier costs one boolean and removes a whole class of agent-to-agent loop.
- Scope the history you read to the task. "The last 50 messages" is a default nobody chose and an exfiltration surface if the agent can also write outward. Read the thread plus the addressed message; reach further only when the requester asks, and say in the channel that you did.
The generalisation worth carrying: a shared channel converts indirect prompt injection from an exotic attack into an everyday accident, because the room's whole purpose is to accumulate text from outside it.
Write for a room, where your output becomes other people's context.
A message in a channel is read by people who did not ask, skimmed by people who arrive later, and — this is the part that surprises teams — fed back to the agent on its next turn as though it were ordinary conversation. That last property makes output discipline a correctness issue rather than a style one.
- Lead with the answer and who it was for. "For @dana: the Q3 figure is 4,182 units" survives being read out of order by someone scrolling. A message that begins with reasoning reads, to a late arrival, as a claim the team has agreed on.
- Mark uncertainty in the text, not in a tooltip. Nobody hovers in a channel, and a hedge that lives only in the UI chrome is lost the moment someone quotes the message elsewhere. See uncertainty and calibration.
- Keep the trajectory out of the room and one click away. The channel gets the outcome; the thread or a linked run view gets the steps. This is the same altitude decision as progressive disclosure, with a stricter budget because the audience is involuntary.
- Never let your own past messages carry instruction weight. An agent that re-reads the channel sees its own confident summary from yesterday and treats it as established fact. Tag your own messages as agent-authored on the way in, and prefer re-deriving over re-reading — the same self-confirmation trap described in the summary wrote itself a system prompt.
- Edit rather than accumulate. For a run that reports progress, update one message in place. Nine status posts is nine notifications for a single task, and the channel remembers all of them.
Operate it: one record per action, per-channel limits, and a way out.
Channel agents accumulate reach quietly — a helpful bot ends up in forty rooms, half of them containing people who were never told what it can do. The operational controls are unglamorous and they are what makes the deployment reviewable.
- Record the requesting identity with every effect. Not the app, not the channel: the human, the message link, the resolved permissions, and the approval if there was one. That tuple is what makes an incident answerable, and it is the channel instance of decision receipts.
- Budget per channel and show the meter in the channel. Spend, actions and messages, visible to the room paying for them — cost and quota UX applies, with the twist that the payer and the requester are different parties.
- Announce capability on join, and re-announce on change. The first message in a new channel should say what the agent will read, what it can do, and who it acts as. A capability change is a new announcement, because consent given to yesterday's tool list is not consent to today's.
- Make leaving clean. Removal from a channel must revoke the channel's contribution to the agent's reach immediately, cancel in-flight runs scoped to it, and stop any memory written from it being retrieved elsewhere. Memory is the one that leaks: a fact learned in
#exec-planningand recalled in#internsis the incident this whole page is trying to prevent. - Keep a per-channel memory boundary from day one. Retrofitting one after the agent has been "helpfully remembering things" across rooms for six months is a data-deletion project, not a config change.
If you ship only two things from this page, ship these. First, make the addressed message the only text in the prompt that carries instruction weight, and structurally label everything else with its author and its tier — that single change turns the channel from an injection surface into a source of context, and it costs an afternoon in your prompt assembly code. Second, resolve the requester's identity on every action and refuse when you cannot, which will immediately surface the guest accounts and cross-org channels your permission model does not cover. Both are invisible to users and both are what the security review will ask for; the trigger policy and the message formatting can be tuned afterwards, because those you can change without a migration.