Slack AI Agent Subscriptions Without a Control Plane
Slack AI agent subscriptions turn every channel message into a worker. The runbook: channel allowlist, spawn cap, event dedupe, human gate on merge, revoke path
Go deeper. Build your own.
At 9:40 on a Tuesday morning someone pastes a stack trace into #bugs. Under a subscription, that message stops being a message. It is a work order, it carries your card, and nobody typed a prompt. Slack AI agent subscriptions are standing instructions: an agent watches a channel, a thread, a PR, or a clock, wakes when something happens, and acts. Cursor shipped them for cloud agents on Aug 19, 2026 and wired them into its Projects coordinator on Sep 10; OpenAI’s Agents API, in public beta since Sep 10, lists Events as one of its four primitives. The pattern is now a feature, and features get switched on before anyone writes the policy.
This is the policy: a file with four gates in front of every subscription (a channel allowlist, a spawn rate cap, dedupe by event signature, and a human on merge and deploy), a revoke procedure that works at 2 a.m., and the anti-pattern written as a scenario so you recognize it in your workspace before the invoice does.
The requirement changed because the agent acts. A chatbot in a channel answers the message it was shown; the worst case is a wrong paragraph. An agent subscribed to the channel spawns work for every message it sees; the worst case is that the channel’s message rate becomes your spend rate. Nothing in the channel knows that. Something in between has to.
What shipped on Aug 19 and Sep 10, and what did not ship with it
Cursor’s Aug 19, 2026 changelog entry, “Cloud Agents and Cursor Harness Improvements,” introduced subscriptions in one sentence: “Cursor can now monitor your PRs, watch a Slack thread, or run scheduled tasks,” “available for cloud agents only, for now” (Cursor changelog). Cloud agents “automatically subscribe to PRs they create,” “fixing CI and addressing bot comments,” and the Slack example is a standing order: “@cursor check back in an hour and keep going until that feedback is in.” Subagents each get “an isolated copy of the project with clean context in its own cloud environment.”
Screenshot: Cursor changelog, “Cloud Agents and Cursor Harness Improvements” (Aug 19, 2026), captured Sep 13, 2026.
On Sep 10 the same idea moved up a level. The “Cursor Projects” changelog entry says to “Tell the coordinator agent to watch a Slack channel, run on a schedule, or follow all your PRs,” then adds the sentence this article exists for: “Connect Slack and point it at a bug-report channel, and it starts delegating each time a bug comes in.” The companion post, “Introducing Projects,” says the coordinator “delegates tasks to thousands of subagents,” runs them “as many in parallel as the work needs,” and “can watch a Slack channel, run on a schedule, or follow all your PRs, fixing CI and acting when they open or merge” (Introducing Projects). Projects is a beta, “rolling out to all users starting today.”
Screenshot: Cursor changelog, “Cursor Projects” (Sep 10, 2026), captured Sep 13, 2026.
What the Sep 10 entry does not contain is any pricing, limit, or permission language. A changelog describes a capability; limits are the operator’s job. The same shape appears at OpenAI, whose Agents API entered public beta on Sep 10 with Events as one of four primitives and bills “model usage at the selected model’s API rates” plus “standard container rates” for hosted sandboxes (Introducing the Agents API; Agents API overview). An event wakes an agent; the agent runs on a metered box.
The news stops here.
Picture #bugs with a subscription and no cap
Picture a #bugs channel in a 40-person engineering org. On a quiet day it gets 30 messages: 20 from people, 10 from a monitoring bot that mirrors alerts. Threads run three to six replies. Someone reports the same login bug twice a week because search is hard.
Now point a coordinator at that channel. Every message is a signal; the coordinator delegates; a worker spawns in its own cloud environment, clones the repo, reads the trace, and starts a fix. Thirty messages is thirty workers before lunch, and the ten alert mirrors were never bugs. The duplicate login report becomes two PRs that conflict. A thread reply that says “still happening on staging” is another signal, so a six-reply thread has spawned six investigations of one bug.
Then an incident starts. The monitoring bot posts every five minutes for two hours: 24 messages, 24 workers, all reading the same failing endpoint, none aware of the others, each opening a PR. An engineer types “check back in an hour and keep going” in three threads, which schedules three more re-entries an hour until someone says stop. By evening nothing has merged, the PR list has 50 agent-owned entries, and the bill is the message rate multiplied by the per-worker cost of a clone, a context load, and a run. Nobody prompted any of it.
That is the anti-pattern in full: a subscription with no gate turns a channel into a spawn loop with your budget as the only backstop. The fan-out metering piece covers what one coordinator costs when it runs wide; this is the same bill arriving through a side door, and the operating bill is what you pay for runs you never asked for.
Illustrative model, not a measurement: one worker per message plus 40% thread re-triggers, against allowlist, six spawns an hour, and a 24-hour dedupe window. At 300 messages an hour: 420 workers versus 6.
Four gates for Slack AI agent subscriptions: allowlist, rate limit, dedupe, human gate
Put four checks between the channel and the spawn, cheapest first. The first two are integer comparisons; the third is a hash lookup; the fourth is a person. Anything a gate rejects is dropped and logged, never queued. A queue converts a burst into a bill that arrives when nobody is watching.
Cheap checks first, the human last, and a revoke path fed by counters the gates already keep.
Gate 1: allowlist channels and triggers, never the workspace
Name channels exactly. No wildcards, no “every channel the app can see,” no #bugs*. Limit the Slack app’s channel access to the channels in the file, and confirm the current installation and channel settings in Slack Help. Add a denylist anyway for channels that must never carry a subscription: #general, #random, #incidents, anything with customers in it.
Then narrow the trigger. “Every message” is the wrong trigger for a bug channel; a human reaction (:agent:) or an explicit mention is the right one, because it costs a person one click to say this one is real. Ignore bot-authored messages, edits, and joins. Under this rule the scenario’s monitoring bot never spawns a worker, and the duplicate report spawns only if a human tags it twice.
Gate 2: a spawn rate cap tuned to the humans, not the agent
Measure the channel’s human message rate for a week before you set anything. Then set the cap at the number of proposals your reviewers will actually read that day, spread across working hours. An illustrative starting range is four to eight spawns an hour with a burst of two; the policy file below uses six. The cap is per subscription, and a scheduled check-in re-enters at this gate and counts. If a loop needs more than the cap, the loop is wrong.
Use a token bucket, not a sliding window, so two real bugs at 9:40 both get served and the 24 alert mirrors do not. When the bucket is empty, drop and log. And set the cap in spawns, not tokens; tokens are a lagging number you learn about after the worker has already cloned the repo.
Gate 3: dedupe on an event signature, inside a window
A signature is a hash of the parts of an event that identify the work, not the parts that identify the delivery.
| Signature input | Why it is in | Why the alternative is wrong |
|---|---|---|
| Channel ID | Same text in two channels is two decisions | Channel names change; IDs do not |
thread_ts (thread root), else normalized text |
A thread is one problem, however many replies | Per-reply signatures spawn a worker per “still broken” |
| Attachment hashes | A new trace on an old thread may be new information | Raw file names collide |
Not event_id, event_time, or user |
Those identify the delivery, not the work | Retries and edits would look new |
Keep signatures for 24 hours. A hit means already working on it, so the gate drops the event and posts a one-line pointer to the existing worker in the thread. Separately, keep Slack’s event_id for a shorter window so a repeated delivery cannot create repeated work. Check the current event-delivery behavior and app settings in Slack Help, then test the duplicate path yourself before enabling the subscription.
Gate 4: a human between the worker and anything that ships
The worker proposes. It opens a PR, posts a summary in the thread, and stops. Merge, deploy, config changes, and anything touching a secret path require a person, through the mechanism you already have: branch protection with required reviews, required status checks, and deployment environments with required reviewers (GitHub Docs). The subscription never holds a token that can merge. If CI on that PR goes red and the agent may fix it, that loop needs its own guards on top of the agentic CI/CD basics.
The proposal queue is where fatigue lives. Six an hour is a number reviewers can hold; sixty is a number they rubber-stamp. Keep the approval queue honest by sizing the cap to the reviewers, which is why gate 2 comes before gate 4.
The policy file, one subscription at a time
Illustrative shape, not a vendor schema. Keep it in the repo next to the CI config and review changes like code; this is the code that decides when you spend money.
# agent-subscriptions.yaml (illustrative shape; adapt to the harness you run)
version: 1
defaults:
mode: propose # propose | act-with-gate ; never plain "act"
spawn_rate:
per_hour: 6
burst: 2
on_exhausted: drop # never queue
dedupe:
signature: [channel_id, thread_ts_or_text_hash, attachment_hashes]
window_hours: 24
delivery_id_window_minutes: 30
budget:
per_event_usd: 2.00
per_day_usd: 60.00
human_gate:
required_for: [merge, deploy, config_change, secret_path]
approvers: [team-platform]
proposal_ttl_hours: 48 # unreviewed proposals expire
auto_pause:
cap_hit_hours_in_a_row: 3
dedupe_hit_rate_over: 0.50
daily_budget_fraction_over: 0.80
notify: '#agent-ops'
subscriptions:
- id: bugs-triage
source: slack
channel: '#bugs' # exact name; the ID is resolved at load
trigger:
type: reaction
emoji: ':agent:'
ignore: [bot_messages, edits, joins]
action: triage-and-propose-pr
owner: jkim
expires: 2026-10-13 # 30 days, then re-approve
denylist:
channels: ['#general', '#random', '#incidents', '#customers']
Three lines carry the weight. on_exhausted: drop keeps a burst from becoming a delayed bill. expires keeps a subscription from outliving the person who understood it. mode: propose as the default means a new subscription ships nothing until someone deliberately widens it.
The revoke procedure, written for 2 a.m.
Auto-pause fires on the thresholds under auto_pause, driven by counters gates 2 and 3 already keep. The manual path is for when you notice first.
- Pause the source. Set the subscription’s
enabled: false, or remove the app from the channel. Removal is the hard stop; it works even when the policy loader is what broke. - Stop in-flight workers. Cancel every cloud agent tagged with the subscription ID; local workers get a signal and a 30-second grace. A half-finished branch is cheaper than a finished wrong one.
- Freeze the output. Convert the subscription’s open PRs to draft and label them
agent-paused. Nothing merges from a paused subscription, good ones included, until a human sorts the list. - Snapshot the counters. Events received, drops per gate, spawns, spend per event, and the last 50 signatures. This is the evidence for the next step and the postmortem.
- Find the noise. The usual suspects: a bot that started posting to the channel, a channel merge or rename, a reaction storm on one thread, or a scheduled check-in nobody cancelled.
- Re-enable narrower. Halve the cap, shorten
expiresto seven days, and require a second owner’s sign-off. Widen again only after a week of clean counters. - Write it down in the thread and in the policy file’s changelog. The next person to point a coordinator at
#bugsshould find the story before they find the button.
I have never once regretted a kill switch, and I have never seen one built after the incident that needed it. Build it the same day as the subscription.
What breaks in Slack AI agent subscriptions, and the signal that tells you
| Failure mode | The signal | First response |
|---|---|---|
| Bot echo loop: the worker’s status post triggers a bot that triggers the worker | Bot-authored events climb; spawns exceed human messages | Confirm ignore: bot_messages; pause if spawns keep rising |
| Thread storm: one bug thread, 40 replies | Dedupe hit rate on one thread_ts passes 50% |
Working as designed; check the pointer post is not itself a trigger |
Channel drift: #bugs merged with #support |
Cap hit three hours in a row | Auto-pause; re-scope the allowlist before re-enabling |
| Delivery retries: slow acknowledgement, Slack redelivers | Duplicate event_id within minutes |
Fix the ack path; the delivery-ID window absorbs it meanwhile |
| Check-in pileup: “check back in an hour” in six threads | Scheduled re-entries outnumber fresh events | Cancel the loops; cap scheduled re-entries per thread at one |
| Budget tail: one event burns $20 | Spend beyond per_event_usd |
Kill the worker; the task needed a human scope, not a retry |
Every row is visible from the counters the gates keep. If you cannot see spawns per hour per subscription on one screen, you have a record of what already happened, and Nightfall’s line about MCP applies word for word: “visibility without control is just a dashboard” (Nightfall, Aug 3, 2026). Cost anomaly alerts are the escalation path for the counter nobody watched.
Subscriptions are operating-layer work, not a better prompt
None of the four gates can be written into the coordinator’s instructions. A prompt that says “only act on real bugs” is a preference, and the meter does not read preferences. The allowlist, the cap, the signature store, and the required review live outside the agent, in the layer that decides whether a run starts and who signs before it lands. That is the operating layer the fleet thesis keeps returning to, and why a command center is built around counters and kill switches rather than a smarter system prompt. When your org grows an agent gateway, the policy file moves into it. Until then it lives in your repo, and it still runs on Tuesday.
FAQ: Slack AI agent subscriptions
How do I stop an AI agent from acting on every Slack message?
Subscribe the agent to named channels only, and make the trigger a human reaction or mention instead of every message. Ignore bot posts and edits. Put a per-hour spawn cap in front of the agent, drop events over the cap, and dedupe on thread signature so one bug is one worker.
What should a Slack agent subscription policy include?
An exact channel allowlist and denylist, a trigger definition, a spawn rate with a burst and a drop rule, a dedupe signature and window, per-event and per-day budgets, a human gate for merge and deploy, an owner, an expiry date, and auto-pause thresholds with a channel to notify.
Sources
- Cursor changelog: “Cloud Agents and Cursor Harness Improvements” (Aug 19, 2026) and “Cursor Projects” (Sep 10, 2026)
- Cursor, “Introducing Projects” (Sep 10, 2026)
- OpenAI, “Introducing the Agents API” (Sep 10, 2026)
- OpenAI, Agents API overview
- Slack Help Center (apps in channels)
- GitHub Docs (branch protection, environments)
- Nightfall, “MCP access control” (Aug 3, 2026)
- Cursor (@cursor_ai), Projects launch post on X (Sep 10, 2026)
