block / block/buzz

feat: channel-wide mentions β€” @channel and @here

Open
#3,146 2 comments 4 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.