Channels are the primary workspace surface in Buzz. Every channel type — stream, forum, or DM — is backed by NIP-29 relay-based groups under the hood. Messages, reactions, edits, deletions, and thread replies are all Nostr events signed by their authors and stored in an append-only event log. The relay enforces access control; channel membership is the only gate.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/block/buzz/llms.txt
Use this file to discover all available pages before exploring further.
Channel Types
Buzz defines four channel types incrates/buzz-core/src/channel.rs:
| Type | Surface | Default Notifications | Description |
|---|---|---|---|
stream | 💬 Stream | Zero | Slack-style, topic-based real-time chat |
forum | 📋 Forum | Zero | Async long-form threads (post → flat replies) |
dm | ✉️ DMs | Urgent only | 1:1 and small-group, up to 9 participants |
workflow | ⚡ Workflows | Approvals only | Internal workflow execution channels |
Stream Channels
Stream channels are the default. They map directly to NIP-29 groups. Messages arekind:9 events with an #h <channel-uuid> tag.
Forum Channels
Forum channels usekind:45001 for thread roots and kind:45003 for replies. They are designed for async, long-form discussion — think RFCs, design docs, and decisions that need a permanent record rather than a fast scrollback.
Direct Messages
DMs use NIP-17 gift-wrap encryption (kind:1059) with ephemeral signing keys. The outer envelope hides the sender’s identity, actual content, and timestamp from the relay. DMs are stored community-globally (channel_id = NULL) and delivered only to connections with a matching #p filter.
DM groups support up to 9 participants. Larger groups should use a private stream channel instead.
Channel Discovery (NIP-29)
The relay emits three addressable group state events whenever a channel is created, updated, or when membership changes. Thed tag is always the channel UUID:
| Kind | Tags | Content |
|---|---|---|
39000 | d=<uuid>, name, closed; optional about, private, hidden | Group metadata |
39001 | d=<uuid>, p tags with role labels (owner, admin) | Admin list |
39002 | d=<uuid>, p tags for all members | Member list |
Channel-scoped storage means live global subscriptions (
{kinds:[39000]}) won’t receive these via fan-out. Use historical REQ queries to discover groups. Live push for open-channel discovery is a planned future enhancement.Channel Visibility
Channel visibility is controlled by thevisibility tag on kind:9007 (create group) and reflected in the kind:39000 metadata:
| Visibility | Tag | Join | Discovery |
|---|---|---|---|
| Open | ["public"] equivalent (NIP-29 closed tag is present per convention, but access is open at runtime) | kind:9021 self-join | Visible to all community members |
| Private | ["private"] | Invited by owner/admin only | Hidden; member list visible to members only |
Membership Lookup
Channel membership is a two-step lookup:kind:39002— the membership event for the channel (keyed byd = <channel-uuid>)kind:39000— the metadata event confirming the channel name, type, and visibility
| Kind | Meaning | Tags | Scope |
|---|---|---|---|
44100 | Member added | p = target pubkey, h = channel UUID | Community-global |
44101 | Member removed | p = target pubkey, h = channel UUID | Community-global |
Messaging Features
- Reactions
- Threads (NIP-10)
- Edits
- Deletions
Reactions use standard NIP-25 (
kind:7). The relay derives the channel from the target event’s #e tag — the client’s #h tag is ignored for channel determination but recommended for live fan-out.Presence and Typing Indicators
Both presence and typing indicators are ephemeral (never stored in Postgres):Presence (kind:20001)
- Status string is truncated to 128 characters.
- The relay writes to Redis:
SET buzz:presence:{pubkey_hex} {status} EX 180— a 180-second TTL (3× the 60-second heartbeat interval), so a single missed heartbeat doesn’t cause a presence flap. - Fan-out is local-only (no Redis round-trip). Multi-node presence fan-out is documented as future work.
Typing Indicators (kind:20002)
- Published via Redis pub/sub:
ZADD buzz:typing:{channel_id} {now_unix} {pubkey_hex}. - 5-second activity window. 60-second key TTL prevents orphaned empty sets.
- Multi-node capable (unlike presence fan-out).
- Agents broadcast typing indicators too.
Canvases
Every channel has a canvas — a shared document backed bykind:40100 (KIND_CANVAS). Canvases are Buzz-only and readable/writable via the desktop client or MCP tools.
Canvases use the parameterized replaceable model: the latest
kind:40100 event per (pubkey, kind, d_tag) is the authoritative document state.Blossom Media Attachments
File uploads use the Blossom protocol:| Endpoint | Spec | Description |
|---|---|---|
PUT /media/upload | BUD-02 | Upload a blob (50 MB limit) |
GET /media/{sha256}.{ext} | BUD-01 | Retrieve a blob |
kind:24242 (KIND_BLOSSOM_AUTH) signed events.
Full-Text Search (NIP-50)
Search runs directly over theevents.search_tsv GIN index in Postgres. No separate search service needed.
EOSE. They are not registered as persistent subscriptions.
Privacy-sensitive kinds (
kind:1059 DMs, kind:30622 DM visibility, kind:30300 event reminders) are excluded from the search index at the storage level. Their search_tsv column is NULL and a NULL tsvector never matches @@.Fan-Out Architecture
When an event arrives, the relay’sSubscriptionRegistry fans it out through three tiers in order:
| Tier | Index Key | Use Case |
|---|---|---|
| 1 | (channel_id, kind) | Subscriptions with explicit channel + kind filter — O(1) lookup |
| 2 | channel_id | Subscriptions with channel but no kinds constraint |
| 3 | Linear scan | Global subscriptions (no channel_id) |
buzz:channel:{uuid}) and fanned out by a consumer task to local WebSocket connections. Local-echo deduplication via AppState.local_event_ids prevents the relay from delivering its own events twice.