Buzz is a Rust monorepo (Cargo workspace) built around a single architectural principle: the relay is the single source of truth. All reads and all writes flow throughDocumentation 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.
buzz-relay over WebSocket. There is no peer-to-peer event exchange, no gossip, and no replication — just clients connecting to one relay, and the relay enforcing auth, verifying Schnorr signatures, persisting events, fanning out to subscribers, indexing for search, and triggering automation.
System Overview
req.community = resolve_host(connection.host) is established before any AUTH, EVENT, REQ, REST, media, git, search, workflow, or pub/sub handling. Unknown hosts fail closed — they never fall through to a default tenant.
Crate Dependency Hierarchy
The workspace is organized as a layered set of focused crates. Cross-subsystem coordination happens only throughbuzz-relay — service crates are deliberately isolated from each other.
buzz-core is the foundation — zero I/O, no tokio, no sqlx, no axum. Every other crate builds on it. buzz-relay is the only crate that imports and orchestrates all subsystems.
The Protocol
Buzz uses Nostr NIP-01 on the wire. Every action is a JSON event:kind integer is the only dispatch switch. The relay routes, stores, and fans out events based on kind. New feature = new kind number = zero breaking changes to existing clients.
buzz-core defines every kind as a pub const u32 and exports the full registry as ALL_KINDS: &[u32] (127 kinds at time of writing). The file crates/buzz-core/src/kind.rs is the source of truth. KIND_AUTH (22242) is never stored — it is rejected at the DB layer and never appears in audit logs.NIP-01 Wire Messages
| Direction | Message | Purpose |
|---|---|---|
| Client → Relay | ["EVENT", <event>] | Submit a signed event |
| Client → Relay | ["REQ", <sub_id>, <filter>, ...] | Subscribe to events |
| Client → Relay | ["CLOSE", <sub_id>] | Cancel a subscription |
| Client → Relay | ["AUTH", <event>] | Authenticate (NIP-42) |
| Relay → Client | ["EVENT", <sub_id>, <event>] | Deliver a matching event |
| Relay → Client | ["EOSE", <sub_id>] | End of stored events |
| Relay → Client | ["OK", <event_id>, true/false, ""] | Event acceptance result |
| Relay → Client | ["CLOSED", <sub_id>, "reason"] | Subscription closed by relay |
| Relay → Client | ["NOTICE", "message"] | Informational message |
| Relay → Client | ["AUTH", <challenge>] | Authentication challenge |
Nostr Kind Ranges
| Range | Meaning |
|---|---|
| 0–9,999 | Standard Nostr kinds (NIP-01 through NIP-XX) |
| 10,000–19,999 | Replaceable events (NIP-16) |
| 20,000–29,999 | Ephemeral events — not stored, not audited |
| 30,000–39,999 | Parameterized replaceable events |
| 40,000–49,999 | Buzz custom kinds |
Selected Custom Kinds
| Kind | Constant | Description |
|---|---|---|
| 7 | KIND_REACTION | Emoji reaction (standard NIP-25) |
| 9 | KIND_STREAM_MESSAGE | Chat message in a Stream channel (NIP-29) |
| 20,001 | KIND_PRESENCE_UPDATE | Ephemeral presence heartbeat (not stored) |
| 40,002 | KIND_STREAM_MESSAGE_V2 | Stream message v2 format |
| 40,003 | KIND_STREAM_MESSAGE_EDIT | Edit of a stream message |
| 43,001 | KIND_JOB_REQUEST | Agent job request |
| 45,001 | KIND_FORUM_POST | Forum thread root |
| 45,003 | KIND_FORUM_COMMENT | Forum thread reply |
| 46,001–46,012 | KIND_WORKFLOW_* | Workflow execution events |
Event Pipeline
When the relay receives["EVENT", <event>], the handler runs this pipeline in order:
["OK", <id>, true, ""] after the full pipeline, not just after the DB insert.
Ephemeral events (kinds 20,000–29,999) bypass Postgres storage, audit, and search. Presence events (kind 20,001) use local-only fan-out; typing indicators publish through Redis pub/sub for cross-node delivery without a DB write.
Connection Lifecycle
Every WebSocket connection follows this sequence:Community Binding
resolve_host(connection.host) establishes TenantContext before any handler can observe tenant data. An unknown or unmapped host is rejected immediately.Semaphore Acquire
state.conn_semaphore.try_acquire_owned() — if the relay is at connection capacity, the connection is rejected before any data is read. The permit is held for the entire connection lifetime.NIP-42 Challenge
The relay immediately sends
["AUTH", "<challenge>"] with a random challenge string. The connection is registered in ConnectionManager.Authentication
The client must respond with
["AUTH", <signed-event>]. On success, ConnectionState.auth_state transitions from Pending → Authenticated(AuthContext). Unauthenticated EVENT/REQ messages are rejected.Active Loops
Three concurrent tasks run for the connection lifetime: a
recv_loop (reads and dispatches frames), a send_loop (drains the mpsc channel and writes frames), and a heartbeat_loop (WebSocket ping every 30s; 3 missed pongs → disconnect).Subscription Fan-Out
TheSubscriptionRegistry uses a three-tier DashMap index for efficient event delivery:
| Tier | Index Key | Use Case |
|---|---|---|
| 1 | (channel_id, kind) | Subscriptions with an explicit channel + kind filter — O(1) lookup |
| 2 | channel_id | Subscriptions with a channel but no kinds constraint |
| 3 | Linear scan | Global subscriptions (no channel_id) — fallback |
channel_id receive events from private channels — regardless of filter match.
Storage Layers
Postgres 17
Primary event store. Monthly range-partitioned
events table with a search_tsv GIN index for full-text search. Also stores channels, channel members, workflows, workflow runs, approval gates, and the hash-chain audit log.Redis 7
Pub/sub fan-out for cross-node event delivery, presence tracking (
SET EX 180), and typing indicators (ZADD sorted sets with 5-second activity windows).Key Postgres Tables
| Table | Purpose |
|---|---|
events | All stored Nostr events; monthly range-partitioned on created_at; keyed by community_id in multi-tenant mode |
channels | Channel records — type (Stream, Forum, Dm, Workflow), visibility, canvas, topic |
channel_members | Membership with roles (Owner, Admin, Member, Guest, Bot); soft-delete via removed_at |
workflows | YAML workflow definitions stored as canonical JSON |
workflow_runs | Execution records with trigger context and trace; statuses: Pending, Running, WaitingApproval, Completed, Failed, Cancelled |
audit_log | Hash-chain tamper-evident audit entries; per-community chain in multi-tenant mode |
Redis Key Patterns
| Pattern | Type | TTL | Purpose |
|---|---|---|---|
buzz:channel:{uuid} | Pub/Sub | — | Event fan-out channel |
buzz:presence:{pubkey_hex} | String | 180s | Online/away status |
buzz:typing:{channel_uuid} | Sorted Set | 60s | Active typers (5-second window) |
Full-Text Search
Search runs over theevents.search_tsv generated tsvector column — no separate search service. The column is populated on insert via to_tsvector('simple', content) and backed by a GIN index (idx_events_search_tsv). Privacy-sensitive kinds yield a NULL tsvector and are storage-level unsearchable. Every query carries community_id so results are fenced to a single tenant.
Workflow Engine
Workflows are YAML-as-code channel-scoped automation definitions. The engine supports four trigger types (message_posted, reaction_added, schedule, webhook) and seven action types:
evalexpr with a 100ms timeout to prevent adversarial expressions from blocking. Workflow concurrency is capped at 100 simultaneous runs via Arc<Semaphore>.
Security Model
| Concern | Mechanism |
|---|---|
| Every event | Schnorr signature + SHA-256 ID verified in buzz-core before storage |
| WebSocket auth | NIP-42 Schnorr challenge/response; ±60s timestamp tolerance |
| HTTP auth | NIP-98 Schnorr-signed kind:27235 with URL and method tags |
| Channel access | Membership-gated at REQ registration — no race window for private channel leaks |
| Audit integrity | SHA-256 hash chain; pg_advisory_lock single-writer guarantee; panic-safe lock release |
| SSRF protection | is_private_ip() in buzz-core covers IPv4 private ranges, link-local, loopback, and IPv4-mapped IPv6; applied to all outbound webhook calls |
| Auth events | KIND_AUTH (22242) is never stored in Postgres and never logged in the audit chain |
Explore Further
Introduction
What Buzz is, the seven surfaces, and what works today.
Quickstart
Clone, build, and run the relay and desktop app locally.