Welcome, and thank you for your interest in contributing! Buzz is an open-source project and we're glad you're here. This guide will help you get from zero to a merged pull request.
If you have questions that aren't answered here, open a GitHub Discussion or reach out in the community channels.
- Code of Conduct
- Setting Up the Development Environment
- Running Tests
- Code Style
- Making a Pull Request
- Architecture Overview
- Ecosystem
- How to Add a New Event Kind
- How to Add a New MCP Tool
- How to Add a New API Endpoint
- License and CLA
This project follows the Contributor Covenant v2.1. By participating you agree to uphold these standards. Please report unacceptable behavior to conduct@buzz-relay.org.
| Tool | Version | Notes |
|---|---|---|
| Rust | 1.88+ | Install via rustup |
| Node.js | 24+ | Required for desktop app commands and just ci |
| pnpm | 10+ | Required for desktop app commands and just ci |
| Flutter | 3.41+ | Required for mobile app — install via flutter.dev |
| Docker | 24+ | For Postgres, Redis, Typesense |
just |
latest | Task runner — cargo install just |
lefthook |
latest | Optional; run lefthook install for local Git hooks |
sqlx migrations |
workspace crate | just migrate applies embedded migrations from migrations/ |
This repo uses Hermit for toolchain pinning. Activate it once per shell session:
. ./bin/activate-hermitHermit pins Rust, just, Node, pnpm, and other tools to the versions in
bin/. Each tool is downloaded on first use. You can also run just bootstrap
(which just setup calls automatically) to pre-download all required tools
upfront. If you don't use Hermit, ensure your toolchain meets the minimum
versions in the table above.
# 1. Clone the repo
git clone https://github.com/block/buzz.git
cd buzz
# 2. Activate Hermit (optional but recommended)
. ./bin/activate-hermit
# 3. Bootstrap tools + infrastructure
just setup
# 4. Install Git hooks (optional, recommended)
just hooksjust setup runs just bootstrap first — it copies .env.example to .env
if it doesn't already exist, and invokes cargo, node, and pnpm to trigger
Hermit's lazy tool download (each tool is fetched once on first invocation and
cached thereafter). You can also run just bootstrap independently at any time;
it is safe to re-run.
just setup then starts Docker services (Postgres on :5432, Redis on :6379,
Typesense on :8108, Adminer on :8082, Keycloak on :8180 for local
OAuth/OIDC testing, MinIO on :9000 for media storage, and Prometheus on
:9090 for metrics) and runs all pending database migrations.
just dev # starts the relay + desktop app in one commandjust dev builds all agent tools, starts the relay (ws://localhost:3000) in
the background, and launches the Tauri desktop app. The relay process is
automatically killed when you quit the app or press Ctrl+C.
For a split-terminal workflow (relay logs visible separately from Vite output):
just relay # terminal 1 — relay on ws://localhost:3000
just desktop-dev # terminal 2 — Vite dev server only (no Tauri shell)just down # Stop Docker services, keep data
just reset # ⚠️ Wipe all data and recreate the environmentjust test-unitUnit tests are self-contained and run without Docker. They cover event parsing, filter matching, auth logic, workflow YAML parsing, and more.
just testIntegration tests spin up the relay and exercise the full stack — WebSocket
connections, NIP-42 auth, event ingestion, search indexing, and workflow
execution. just test starts Docker services automatically if they're not
already running.
End-to-end tests live in crates/buzz-test-client/tests/:
e2e_rest_api.rs— REST API testse2e_relay.rs— WebSocket relay testse2e_mcp.rs— MCP tool testse2e_nostr_interop.rs— Nostr protocol interoperability testse2e_tokens.rs— token management testse2e_media.rs— media upload/download testse2e_media_extended.rs— extended media tests (GIF, image processing)e2e_workflows.rs— workflow tests
Run them with (requires running infrastructure):
cargo test -p buzz-test-client -- --ignoredSee TESTING.md for the full multi-agent E2E testing guide.
Before opening a PR, run the full CI gate locally:
just ci
# Runs: check + unit tests + desktop build + Tauri check + mobile testsThis is the same check that runs in CI. PRs that fail just ci will not be
merged.
We use rustfmt with default settings. Format your code before committing:
cargo fmt --allTo check without modifying:
cargo fmt --all -- --checkWe use clippy with warnings-as-errors:
cargo clippy --all-targets --all-features -- -D warningsFix all clippy warnings before submitting a PR. If you believe a warning is
a false positive, add a targeted #[allow(...)] with a comment explaining
why.
All crates enforce #![deny(unsafe_code)]. Do not add unsafe blocks. If you
believe unsafe is genuinely necessary, open an issue first to discuss the
approach.
- Use
thiserrorfor library error types. - Use
anyhowfor binary / application-level error propagation. - Do not use
unwrap()orexpect()in production code paths. Use?or explicit error handling.unwrap()is acceptable in tests.
Use the tracing crate for all instrumentation. Prefer structured fields
over string interpolation:
// Good
tracing::info!(channel_id = %id, event_kind = kind, "Event ingested");
// Avoid
tracing::info!("Event ingested: channel={id} kind={kind}");Follow Conventional Commits:
feat(mcp): add get_feed_actions tool
fix(auth): reject expired NIP-42 challenges
docs(agents): document workflow MCP tools
refactor(db): extract channel queries into channel.rs
test(workflow): add approval gate integration test
The type prefix (feat, fix, docs, refactor, test, chore) is
required. The scope (in parentheses) is optional but encouraged.
- Check open issues and PRs to avoid duplicate work.
- For significant changes, open an issue first to discuss the approach.
- For small fixes (typos, doc improvements, obvious bugs), go ahead and open a PR directly.
-
Focused — one logical change per PR. If you're fixing a bug and refactoring a module, split them into two PRs.
-
Tested — new behavior has tests. Bug fixes include a regression test. If a test is impractical, explain why in the PR description.
-
Documented — public APIs, new event kinds, new MCP tools, and new config variables are documented. Update
README.md,AGENTS.md, orVISION.mdas appropriate. -
CI passing —
just cipasses locally before you push. -
Clear description — the PR description explains:
- What problem this solves (or what feature it adds)
- How it was implemented (key decisions, trade-offs)
- How to test it manually (if applicable)
- Any follow-up work deferred to a future PR
- [ ] `just ci` passes (fmt + clippy + unit tests + mobile)
- [ ] Integration tests pass (`just test`)
- [ ] New public APIs / tools / endpoints are documented
- [ ] No new `unwrap()` in production code paths
- [ ] No new `unsafe` blocks
- A maintainer will review your PR within a few business days.
- Address review comments by pushing new commits (don't force-push during review; it makes it hard to see what changed).
- Once approved, a maintainer will squash-merge your PR.
See ARCHITECTURE.md for the full system design and AGENTS.md for the complete crate map. The key design principles:
The relay is the single source of truth. All state flows through the
event store. Crates communicate through the database and Redis pub/sub — not
through direct function calls across crate boundaries (with the exception
of buzz-core types, which are shared everywhere).
Event kinds are the only switch. Every action in the system — a message, a reaction, a workflow step, a canvas update — is a Nostr event with a kind integer. Adding a new feature means defining a new kind. No breaking changes to existing clients.
Buzz is developed across multiple repositories. This repo (block/buzz)
is the open-source home for all application code — the relay, desktop app,
mobile app, CLI, and agent harness. Internal repositories handle
enterprise-signed builds and infrastructure deployment.
See AGENTS.md § Ecosystem for the full repo table and dependency diagram.
External contributors: Fork block/buzz, open a PR, and CI runs
automatically. No special access is required.
Block team members: See the internal sprout-releases CONTRIBUTING.md for team access setup, onboarding, and the full repo inventory. See RELEASING.md for the release process.
-
Define the kind constant in
buzz-core/src/kind.rs:/// My new event kind — description of what it represents. pub const KIND_MY_FEATURE: u32 = 4XXXX;
Pick a kind number in the appropriate sub-range defined in
kind.rs. Check theALL_KINDSarray for collisions. Each sub-range is documented with comments in the file. -
Define the payload type in the appropriate module in
buzz-core/src/(e.g., alongsideevent.rs) if the content field is structured JSON:#[derive(Debug, Serialize, Deserialize)] pub struct MyFeaturePayload { pub field_one: String, pub field_two: Option<u64>, }
-
Register the kind's required scope in
crates/buzz-relay/src/handlers/ingest.rsinsiderequired_scope_for_kind(). This controls which auth scope a caller needs to submit the event:KIND_MY_FEATURE => Ok(Scope::MessagesWrite),
-
Handle post-storage side effects by adding a match arm in
crates/buzz-relay/src/handlers/side_effects.rsinsidehandle_side_effects():KIND_MY_FEATURE => handle_my_feature(event, state).await?,
handle_side_effects()runs after the event is stored — use it for notifications, cache invalidation, or derived data. If the new kind also needs a REST surface (e.g., a query endpoint for clients), add a handler incrates/buzz-relay/src/api/and register it incrates/buzz-relay/src/router.rs. -
Persist to the database — if the event needs to be queryable, add a handler in
buzz-db/src/(e.g.,buzz-db/src/my_feature.rs) with the appropriateINSERTandSELECTqueries. -
Index for search (if applicable) — add the kind to the Typesense indexing logic in
buzz-search/src/index.rs. -
Audit — the audit log captures all events automatically; no changes needed unless you need custom audit metadata.
-
Write tests — add a unit test for payload serialization in
buzz-coreand an integration test inbuzz-test-clientthat sends the new event kind and verifies the expected behavior. -
Document —
kind.rsis the authoritative registry of all kind numbers. UpdateREADME.mdif it's a user-facing feature.
REST endpoints live in crates/buzz-relay/src/api/ — each resource has
its own submodule (e.g., channels.rs, messages.rs, tokens.rs). Routes
are registered in crates/buzz-relay/src/router.rs.
-
Define the handler function:
pub async fn get_my_resource( State(state): State<Arc<AppState>>, headers: HeaderMap, Path(channel_id_str): Path<String>, ) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> { let channel_id = uuid::Uuid::parse_str(&channel_id_str) .map_err(|_| api_error(StatusCode::BAD_REQUEST, "invalid channel_id"))?; let ctx = extract_auth_context(&headers, &state).await?; buzz_auth::require_scope(&ctx.scopes, buzz_auth::Scope::ChannelsRead) .map_err(scope_error)?; let pubkey_bytes = ctx.pubkey_bytes.clone(); check_token_channel_access(&ctx, &channel_id)?; check_channel_access(&state, channel_id, &pubkey_bytes).await?; // Fetch data let data = state.db.get_my_resource(channel_id).await .map_err(|e| internal_error(&e.to_string()))?; Ok(Json(serde_json::json!(data))) }
-
Register the route in
crates/buzz-relay/src/router.rs:.route("/api/channels/{channel_id}/my-resource", get(get_my_resource))
-
Add the database query in
buzz-db/src/— follow the existing patterns inchannel.rs,event.rs, etc. -
Handle errors — use the
api_error()andinternal_error()helpers inbuzz-relay/src/api/mod.rs. Return(StatusCode, Json<Value>)tuples. -
Write tests — add an integration test using the
buzz-test-clientharness incrates/buzz-test-client/tests/e2e_rest_api.rs. -
Document — if the endpoint is part of the public API surface, add it to the API reference section of
README.mdor a dedicatedAPI.md.
Buzz is licensed under the Apache License, Version 2.0. See LICENSE for the full text.
By submitting a pull request, you agree that your contribution is licensed under the Apache 2.0 license and that you have the right to submit it.
If your employer has rights to intellectual property you create, you may need their sign-off. When in doubt, check with your legal team.
Thank you for contributing to Buzz. Every bug report, documentation fix, and code contribution makes the project better for everyone. 🐝