feat: channel-wide mentions β @channel and @here
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
> π€ Filed by kray's AI agent on his behalf.
## Problem
Getting the attention of *everyone in a channel* means typing every member's name β and keeping that habit current as membership changes. The real cost isn't the typing, it's the **silent under-notification**: as [a community member described on #2728](https://github.com/block/buzz/issues/2728#issuecomment-5078984489), the moment a fourth agent joined their three-agent channel, "every saved snippet and habit is silently stale β he gets three answers and never notices the fourth was left out."
User groups (#2728) solve the *named-set* case, but someone has to create and maintain the group. Quoting the same comment: a zero-config `@channel`-style mention "is the 80% case and has no membership to keep in sync." #2332 asks for the same primitive from the agent side β "will also be good to have an 'everyone' tag or similar."
And sometimes you don't want everyone β you want *whoever is around right now*. Asking a 40-person channel "can someone restart the deploy?" at 2am shouldn't badge 35 sleeping people.
Slack users reach for `@channel` and `@here` by muscle memory. Buzz has no equivalent β today a literal `@channel` renders as plain text and notifies nobody.
## Proposal
Two reserved, zero-config mention tokens with Slack-parity semantics:
- **`@channel`** β notify every member of the channel.
- **`@here`** β notify only members who are online right now.
### Wire format: one marker tag, not roster fan-out
The message carries a single `["notify", "channel" | "here"]` marker tag (exact tag name finalized in the PR). It does **not** expand into per-member `p` tags, deliberately departing from the compose-time-expansion idiom used by team mentions (#1918), category mentions (#2531), and user groups (#2791):
- **`@here` is not expressible as a compose-time list.** "Who is online" is a delivery-time fact; a frozen send-time snapshot is stale against the presence TTL and leaks exactly who was idle to the sender.
- **Roster fan-out breaks at scale.** N `p` tags bloat the signed event, leak the full roster, hit the 50-mention cap, and are silently suppressed by NIP-PL's `suppress.p_tags_max` hellthread gate β the push spec's own example value is 20, so `@channel` push would silently vanish in any channel larger than that.
- **Agent amplification.** Every `p` tag wakes an ACP-managed agent (`event_mentions_agent`), so an expanded `@channel` in an agent-heavy channel burns one model turn per agent, involuntarily.
- **Precedent exists on both sides.** Buzz already ships a p-tag-free channel-wide attention signal β `["broadcast", "1"]` (NIP-CW) β that the notification pipeline understands today. Matrix faced this exact fork and chose the structured flag (`m.mentions: {"room": true}`, MSC3952) over per-user fan-out.
Foreign Nostr clients degrade gracefully (NIP-27 style): they render literal `@channel` text and simply don't notify. No upstream NIP defines group-wide mentions (verified against nostr-protocol/nips and the NIP-29 client ecosystem), so this will be specced as a Buzz-local NIP in `docs/nips/` (working name **NIP-CM, Channel Mentions**).
### Notification semantics
- `@channel` escalates to **mention-tier** (numeric badge, mention feed, OS notification) for every channel member.
- `@here` escalates **live-only**: clients that receive the event while online treat it as a mention; members who were offline see a normal message later β no retroactive badge (Slack parity).
- **Channel mutes suppress `@channel`/`@here`** β unlike a direct `@name` mention, which continues to pierce mutes (Slack parity; same call #2138 made).
- `@channel` messages appear in members' Home mention feed via a relay-side lookup β **one row per event**, joined against membership at query time, not one `event_mentions` row per member.
- Editing a message to add `@channel` does not re-notify.
### Composer
Autocomplete rows for `@channel` ("Notify all N members") and `@here` ("Notify N online members"), plus a confirmation prompt before sending β reusing the existing send-flow interception point (same seam as the non-member mention dialog).
### CLI and automation safety
`buzz messages send` gets an explicit `--notify channel|here` flag. A literal `@channel` typed in content **without** the flag warns and sends without the tag β agents and workflows must opt in explicitly to mass-notify, so a template variable can never accidentally ping 400 people. (Same spirit as the unresolved-mention warning in #3047.)
### Agents: humans-only, by design
`@channel` and `@here` notify **humans only**. The marker tag carries no `p` tags, so `event_mentions_agent` never fires and no managed agent burns a model turn β an involuntary N-agent turn storm per `@channel` would be a real cost explosion in agent-heavy channels. Channel-wide *agent* addressing is a different intent and already has a lane: `@agents` category mentions (#2531) and user groups (#2728) both expand to explicit `p` tags and wake agents deliberately. If demand emerges for agent-inclusive broadcast tokens, they'd be additional reserved tokens on this same marker mechanism β a follow-up, not v1.
### Permissions and abuse
- v1: any channel member may use `@channel`/`@here` (Slack's default). Relay ingest validates the tag (valid mode, allowed kinds, not in DMs). Per-channel restriction ("only admins may @channel") is a natural follow-up on the #2497 per-channel write-policy surface.
- `channel` and `here` become reserved mention tokens: resolved ahead of display-name matching (a member named "here" can't shadow or be shadowed), and reserved in the user-group handle validator from #2728.
## Prior art
- **#2138** β a full-stack `@everyone`/`@here` attempt (closed unmerged by its author, July 2026; never reviewed). It chose compose-time p-tag expansion with a 4,000-recipient cap; this proposal diverges for the reasons above but reuses several of its calls (mute-respecting semantics, relay-side tag validation, reserved-token autocomplete copy). It also surfaced a real bug this feature depends on: the hidden 1,000-row `LIMIT` in `get_members()` truncating rosters β being fixed separately.
- **#2332** β the open ask for an "everyone tag". Its agent-wake angle is intentionally *not* covered here (`@channel` is humans-only); the agent side is served by `@agents` category mentions (#2531) and user groups (#2728).
- **#2728 / #2791** β user groups: the named-set complement to this zero-config alias; the `["group", id, handle]` marker precedent.
- **#2531** β `@agents`/`@people` category mentions (open): adjacent composer work; this PR will coordinate with its `mentionCandidates.ts` helpers.
- **#2497** β announcement channels: the permission axis this feature's follow-up gate would build on.
## Out of scope / follow-ups
- Mobile compose + pill rendering (follow-up issue, mirroring #2792; mobile already surfaces the mention feed).
- Per-channel `@channel` permission policy (with #2497).
- A dedicated NIP-PL push class for channel mentions (v1 rides existing channel-wide lease subscriptions).
- `@everyone` as a community-wide token β deliberately not proposed; channel scope only.
Contributor guide
Assessment
This issue has not been assessed yet.