Skip to content
FLAVIO COPES
flaviocopes.com
2026

Buzz: a workspace where humans and AI agents work together

By

Buzz is an open source workspace where people and AI agents share channels, projects, workflows, search, and one signed event history.

~~~

Buzz is a workspace where people and AI agents work together.

Think Slack, GitHub, an automation tool, and a team of coding agents in one place.

But Buzz is not a collection of integrations glued around a chat app. Everything goes through the same relay. Messages, reactions, workflow steps, code patches, approvals, and agent actions become signed events in one log.

The project is built by Block, open source on GitHub, and licensed under Apache 2.0.

It is also a developer preview.

That last detail matters. There is a real desktop app, a relay, channels, search, workflows, Git support, and agent tooling. But the larger vision is still being built.

Let’s see how it works.

What problem does Buzz solve?

AI agents can write code, inspect a repository, run tests, and open pull requests.

But most agents still work outside the place where the team talks.

You ask an agent to fix a bug in one window. The relevant discussion is in Slack. The issue is in GitHub. The CI result is in another dashboard. The design decision is buried in a document.

The agent needs access to all those systems.

Then it needs enough context to understand how they relate.

Buzz starts from a different idea: put people, agents, conversations, code, and workflows in the same workspace.

An agent joins a channel like a person. It gets its own identity. It can read the conversation, respond to a mention, search previous work, post a patch, update a shared document, or react to a message.

The channel becomes the place where the work happens.

This is more interesting than adding an AI bot to chat. A bot usually waits for a command, calls an API, and posts a result.

In Buzz, an agent is a workspace member.

The relay is the workspace

The central part of Buzz is the relay.

A relay receives events, verifies them, stores them, and sends them to connected clients.

The desktop app connects to the relay. The CLI connects to the relay. Agents connect to the relay. Workflows run through the relay.

The architecture looks like this:

people ───────┐
agents ───────┼──> Buzz relay ──> Postgres
CLI scripts ──┘          │       Redis
                         └─────> object storage

The relay is the source of truth.

Postgres stores events and powers full-text search. Redis handles things like pub/sub, presence, and typing indicators. Media files go to S3-compatible object storage such as MinIO.

You can run this infrastructure yourself.

Buzz also supports hosted communities. In both cases, the URL selects the community you are entering. Channels, members, agents, DMs, repositories, and search stay inside that community.

This gives Buzz a clear boundary:

one community = one workspace = one URL

A hosting provider may run many communities on shared infrastructure. But each community should still feel like its own isolated workspace.

A closer look at the architecture

The simple diagram explains the idea. The actual relay has more moving parts. Buzz documents them in its architecture guide.

Buzz is a Rust monorepo. The backend is split into focused crates, but it runs as a modular application instead of a fleet of small services.

The important crates include:

The relay imports those crates and calls them directly.

The lower-level crates do not call each other. Search does not reach into the workflow engine. The workflow engine does not publish directly to Redis. Cross-system coordination returns to the relay.

This is a modular monolith.

I think that is the right choice for this stage of the project. It keeps deployments understandable and avoids a network call between every step. The crate boundaries still make the code easier to test and audit.

The tradeoff is clear too. The relay becomes a very important process. Authentication, persistence, live delivery, search, workflows, media, Git, and agent activity all meet there.

What happens when a client connects

A desktop client or agent connects over WebSocket.

The relay first resolves the community from the request host. This happens before the connection can authenticate or read data.

Then the connection goes through these steps:

  1. acquire a connection permit
  2. receive a random NIP-42 authentication challenge
  3. sign the challenge with its private key
  4. enter the receive, send, and heartbeat loops

The receive loop parses incoming messages. The send loop writes outgoing frames. The heartbeat loop checks that the client is still alive.

A cancellation token connects their lifecycles. When one part fails, the relay can stop the other tasks and clean up the connection’s subscriptions.

The relay also uses bounded buffers.

If a client stops reading and fills its outgoing buffer repeatedly, Buzz disconnects it. One slow connection should not keep consuming memory forever.

This is a small detail, but it shows that backpressure is part of the design.

The persistent event write path

Let’s follow a normal channel message.

The WebSocket sends an EVENT frame containing the signed Nostr event. The relay then runs a sequence of checks before storing it.

The current path looks like this:

authenticate and check scope

match signer to authenticated identity

verify event hash and Schnorr signature

validate timestamp, size, kind, and tags

resolve channel and check access

insert into Postgres

enqueue audit entry

acknowledge the write

publish, fan out, and trigger workflows

Signature verification is CPU work, so the relay moves it to Tokio’s blocking thread pool. It does not block an async runtime worker while checking the signature and event hash.

The relay also checks the event timestamp and content size. It rejects unknown kinds rather than accepting data it does not understand.

Scopes depend on the event kind. A message needs message-write access. A profile update needs user-write access. A repository announcement needs repository-write access.

Channel-scoped tokens cannot publish global events. This closes an easy way to escape a token’s intended channel boundary.

After validation, Postgres stores the event.

Normal inserts are idempotent. The event ID is deterministic, and a repeated insert becomes a duplicate instead of a second event.

Replaceable Nostr events use a different path. Buzz atomically replaces the previous event while rejecting stale writes.

What an acknowledgement means

Buzz separates durable acceptance from live delivery.

The relay stores the event and waits until its audit entry enters a bounded queue. It can then return an OK response to the client.

Redis publishing, WebSocket fan-out, and workflow triggering continue in a spawned task.

So an OK response means the event was accepted durably. It does not mean every connected client already received it or every workflow finished.

That is an important contract.

It keeps message writes fast when another client is slow. It also means the system is eventually consistent after the Postgres commit.

If Redis publishing fails, the durable event still exists. Local subscribers can still receive the direct fan-out. Clients reconnecting later can retrieve the stored event through the historical query path.

The weakness is that another relay node may miss the immediate live notification. Redis pub/sub is delivery infrastructure, not the source of truth.

Audit backpressure

Audit logging is asynchronous, but the queue is bounded.

The relay waits while placing the audit entry into that queue. If the audit worker cannot keep up and the queue fills, event ingestion slows down too.

This is deliberate.

An unbounded queue would hide the problem until the process consumed all available memory. Silently dropping audit entries would make the audit log unreliable.

The database write performed by the audit worker can still fail after the event is accepted. The current worker logs that failure and does not retry it.

So the architecture protects against queue overload, but it does not make the event row and audit row one atomic transaction.

That is a reasonable preview-stage tradeoff. It is also something I would monitor closely in a serious deployment.

Persistent and ephemeral events

Not every event belongs in history.

Chat messages, workflow activity, canvas changes, and Git events are persistent. Buzz stores them in Postgres and can return them in later queries.

Presence and typing indicators are ephemeral.

Ephemeral events use the Nostr kind range from 20000 to 29999. The relay verifies their signatures and checks access, but does not insert them into Postgres, index them for search, or add them to the audit log.

They flow through Redis and the live WebSocket fan-out only.

Presence uses a Redis value with a 90-second expiry. Typing uses a sorted set with a short activity window and a longer key expiry for cleanup.

This is a good separation.

There is no reason to fill permanent storage with years of “Flavio is typing” events.

How subscriptions work

Nostr clients subscribe with REQ messages and filters.

A filter can ask for a channel, one or more event kinds, an author, an event ID, or tagged recipients.

Buzz does not scan every active subscription for every channel message. The in-memory subscription registry maintains several indexes:

A stream message can jump directly to subscriptions for its community, channel, and kind.

The registry keeps global and channel subscriptions separate in both directions. A global subscription does not receive channel-scoped events. A channel subscription does not receive global infrastructure events.

Before registering a channel subscription, the relay checks access. Before delivering a live event, it checks access again.

The second check protects against stale subscriptions. A user might have joined a channel, subscribed, and then been removed while the connection stayed open.

This costs an additional authorization step, but it keeps the live path from trusting old state.

When a client first subscribes, Buzz sends matching history from Postgres. It then sends an EOSE marker, meaning “end of stored events.” New events arrive through the live fan-out after that.

Historical results are capped per filter. This prevents one subscription from turning into an unbounded database export.

Why Postgres does so much work

Postgres is the durable center of Buzz.

It stores events, channels, memberships, profiles, workflows, workflow runs, approvals, and the audit chain.

The large event table is partitioned by month. That makes retention and maintenance more manageable as the log grows.

Search also runs in Postgres.

Each searchable event gets a generated tsvector column during the insert. A GIN index powers full-text queries.

There is no separate search cluster and no second copy of the searchable data to synchronize.

This is simpler than sending every message to Elasticsearch or Typesense. A committed event is searchable through the same database row.

Privacy-sensitive event kinds get a NULL search vector. They are excluded at the storage layer instead of being filtered only after a search matches them.

The search crate returns candidate hits. The relay still checks community and channel access before returning them to the caller.

What Redis does

Redis handles state that is fast, shared, and disposable:

When several relay processes run, an event accepted by one process publishes to Redis. Other processes receive it and deliver it to their local WebSocket clients.

The accepting relay also delivers the event locally. To prevent duplicate delivery when its own Redis message comes back, it keeps a short-lived local event-ID cache.

This lets Buzz scale WebSocket connections across relay processes without turning Redis into permanent storage.

But Redis pub/sub does not replay missed messages. Postgres remains the recovery path.

Multi-community isolation

Multi-tenancy is not implemented as a client-supplied filter.

The relay creates a TenantContext from the hostname before handling authentication, events, queries, media, Git, workflows, or pub/sub.

That context carries the server-resolved community ID through the lower-level crates.

Postgres queries include the community ID. Redis keys and topics include it. Subscription indexes include it. Audit chains are community-specific.

An unknown host fails closed.

This is stronger than trusting an event tag that says which tenant it belongs to. A client controls its event tags. It does not control the community that the server derived from the connection host.

The repository also includes formal multi-tenant specifications and conformance tracing around the ingest path. A guard records when a code path exits without producing the expected modeled action.

That does not prove the whole application is bug-free. It shows the project treats cross-community data leaks as an architectural problem, not a controller-level check added later.

The agent process is outside the relay

The coding agent does not run inside buzz-relay.

The separate buzz-acp process connects to the relay over WebSocket. It starts an ACP-compatible agent as a child process and talks to it over JSON-RPC on standard input and output.

Buzz relay
    │ WebSocket

buzz-acp
    │ ACP over stdio

coding agent

The harness can manage a pool of agent processes. It allows one active prompt per channel and queues later mentions behind it.

That per-channel rule prevents two overlapping turns from racing with the same conversation context.

If an agent process crashes, the harness can start it again.

The harness itself does not persist state. Durable conversation history belongs to the relay. An operator should not treat an in-memory agent queue as a durable job system.

This separation also limits the relay’s responsibilities. The relay coordinates identity, access, events, and history. The agent harness deals with model processes, prompt queues, and crashes.

What the architecture gets right

Several choices stand out to me:

The design has a strong center. Every path returns to the relay and the signed event log.

Where the architecture is under pressure

The same center can become a bottleneck.

The ingest path already handles many special kinds: messages, reactions, deletions, profiles, moderation commands, Git events, workflows, agent metrics, and more.

As the product grows, this pipeline risks becoming difficult to reason about. The crate boundaries help, but buzz-relay still owns a lot of coordination logic.

The durable write and later side effects are not one transaction. That keeps latency down, but live delivery, audit persistence, and workflow execution can fail independently after the event commit.

Rate limiting is another important gap. The auth crate defines a rate-limiter interface and several planned tiers, but the current architecture documentation says no production rate limiter is wired in.

That matters for a public relay, especially when agents can generate traffic much faster than people.

Workflow support is also incomplete. Approval resumption is not wired end to end. send_dm and set_channel_topic exist in the workflow schema but currently return NotImplemented during execution.

And SQL queries use runtime sqlx::query() calls. The project does not keep a SQLx offline cache, so the compiler cannot validate those queries against the schema.

None of these gaps invalidate the architecture.

They explain why Buzz still calls itself a developer preview.

Why Buzz uses Nostr

Buzz uses the Nostr protocol for its events.

Nostr is often associated with social networks, but the basic protocol is much smaller than that.

An event contains a few fields:

{
  "id": "event hash",
  "pubkey": "author public key",
  "kind": 9,
  "tags": [],
  "content": "Hello from the release channel",
  "sig": "event signature"
}

The author signs the event with a private key.

The relay verifies the signature before accepting it.

The kind number tells the relay what the event represents. One kind can be a chat message. Another can be a reaction. Other kinds represent workflow activity, forum posts, presence, or agent jobs.

This gives every action a similar shape.

A person posting a message and an agent posting a test result both produce signed events. The relay can store, search, deliver, and audit them using the same basic model.

Buzz adds its own kinds for workspace features while keeping the Nostr wire format.

And no, this is not a blockchain.

There is no coin and no mining. Signed events are useful on their own. They tell you who produced an action and let the relay reject altered data.

People and agents use the same identity model

Every Buzz identity has a public key and a private key.

The public key identifies the member. The private key signs actions.

This applies to people and agents.

An agent does not need to borrow a human account or use one shared bot token. It can have its own keypair, profile, channel memberships, and activity history.

That makes permissions easier to understand.

If a code review agent only belongs to two project channels, it can only see those channels. If you remove it from a private channel, it loses access.

You can inspect what the agent posted because its events carry its identity.

This is a better model than giving one integration a powerful API token and hoping every internal process uses it correctly.

But private keys come with responsibility.

Never give a Buzz private key to another user, an agent you do not trust, or a support person. If you lose the key without a backup, recovery can be difficult.

What you can do in Buzz today

Buzz is broad. The repository contains a lot more than a chat interface.

The current project includes:

The desktop app has Stream channels for fast conversation, Forum channels for longer discussions, DMs, search, agent management, workflows, profiles, and settings.

The project also has voice huddles, with audio relayed through Buzz. Recording is not finished.

Mobile clients are being wired up. Workflow approval gates have infrastructure in place, but the workflow executor does not yet suspend and resume a run correctly.

This is why the developer preview label is important.

You can experiment with Buzz now. I would not assume every feature in the vision documents is production-ready.

A channel is more than chat

A Buzz channel can hold the conversation, files, a canvas, workflow activity, and agent work.

Suppose a team is preparing a release.

People discuss the remaining tasks in #release. A release agent searches merged work and drafts the notes. A workflow posts build status. Someone reviews the draft and asks for a change. The agent edits it.

All those actions stay together.

Six months later, someone can search the workspace and find:

This is the part of Buzz I find most useful.

AI agents need context, but teams already create the context while working. The problem is that we spread it across too many tools.

Buzz tries to keep the context next to the work.

Shared canvases

Each channel can have a canvas.

A canvas is a shared document connected to the channel. People can edit it from the desktop app. Agents can read and update it through tools.

You might use a canvas for:

The CLI exposes the same surface:

buzz canvas get --channel CHANNEL_ID

You can update it too:

buzz canvas set \
  --channel CHANNEL_ID \
  --content "# Release checklist"

This is a small feature with a useful consequence.

The agent does not need a separate document integration. It already has access to the document through the workspace.

Workflows are YAML files

Buzz includes a workflow engine.

Workflows can start from a message, a reaction, a schedule, or a webhook.

Here is a small example:

name: "Release request"

trigger:
  on: message_posted
  filter: "str_contains(trigger_text, 'ship it')"

steps:
  - id: announce
    action: send_message
    text: "Release requested by {{trigger.author}}"

This workflow watches messages in its channel.

When a message contains ship it, the workflow posts a response with the author’s public key.

The workflow schema defines actions for channel messages, DMs, channel topics, reactions, webhooks, delays, and approvals.

Not all of them work yet. send_dm and set_channel_topic currently return NotImplemented during execution.

Every step produces traceable workflow events.

Be careful with approval steps too. The schema, API, and interface exist, but the executor does not yet persist and resume an approval correctly. The project documentation calls this unfinished.

Git is part of the workspace

Buzz does not treat code as a link to another system.

The relay supports Git repository announcements, patches, status events, and Git hosting. It exposes standard Git smart HTTP endpoints, so normal Git clients can clone and push.

The larger vision is to turn a branch into a room.

Imagine creating a feature branch and getting a matching channel. The patch, CI results, review comments, and merge decision all land there.

When the branch merges, the channel becomes the permanent explanation for the code.

That complete flow is the direction of the project. Some Git pieces already work, but I would treat the seamless “branch as room” experience as a product vision that is still being assembled.

This distinction is easy to miss when reading a large and fast-moving repository.

How agents connect to Buzz

Buzz has two important agent-facing layers: a CLI and an ACP harness.

The buzz-cli command is designed for machines first. It prints JSON to standard output, structured errors to standard error, and meaningful exit codes.

This matters because an agent should not have to scrape a human-oriented interface.

The CLI can:

The ACP harness connects an ACP-compatible coding agent to the Buzz relay.

ACP means Agent Client Protocol. It gives a client a standard way to drive a coding agent over JSON-RPC.

Buzz can bridge channel mentions to agents such as Goose, Codex, and Claude Code. It queues events by channel and sends prompts to the agent. The agent then uses MCP tools to act inside Buzz.

The flow looks like this:

Buzz channel
    |
    | @mention
    v
buzz-acp
    |
    | ACP
    v
coding agent
    |
    | MCP tools
    v
Buzz channel

ACP drives the agent. MCP gives the agent tools.

The relay keeps the shared history.

Try the desktop app

The easiest way to see Buzz is to get the packaged desktop app from buzz.xyz.

Builds are available for macOS, Linux, and Windows.

By default, the app connects to:

ws://localhost:3000

You need a relay at that address, or you need to point the app at a relay someone shared with you.

You can set the relay before opening the app:

export BUZZ_RELAY_URL='wss://buzz.yourproject.com'

The app also lets you switch relays from the interface.

A relay stores and serves one community’s channels, messages, members, agents, and rules. Relays do not automatically share private workspace content with each other.

Run Buzz locally from source

To run the complete development version, you need Docker and Hermit.

Hermit downloads the pinned development tools for the project.

Alternatively, you can install the required versions yourself. At the time of writing, the repository asks for Rust 1.88 or newer, Node.js 24 or newer, pnpm 10 or newer, and just.

Clone the repository:

git clone https://github.com/block/buzz.git
cd buzz

Activate the Hermit environment:

. ./bin/activate-hermit

Run the setup:

just setup

This creates .env from the example when needed, downloads tools, starts the Docker services, and runs the database migrations.

Build Buzz:

just build

Then start the relay and desktop app:

just dev

The relay starts on port 3000 and the desktop app opens.

If you want separate logs, run the relay in one terminal:

just relay

Then start the desktop app in another:

just desktop-dev

Be careful with this command:

just reset

It wipes the local Buzz data and recreates the development environment.

If the desktop app and your development relay share the default Docker containers, that includes the data used by the app.

Use the Buzz CLI

You can install the CLI from the cloned repository:

cargo install --path crates/buzz-cli

Install the admin tool too. We’ll use it to create identities:

cargo install --path crates/buzz-admin

The CLI expects a relay URL and a private key:

export BUZZ_RELAY_URL='http://localhost:3000'
export BUZZ_PRIVATE_KEY='nsec...'

Do not paste a real private key into a script you plan to commit.

For a local test, generate a keypair with the admin tool:

buzz-admin generate-key

Keep the secret key private.

Now list the channels:

buzz channels list

Create one:

buzz channels create \
  --name 'release' \
  --type stream \
  --visibility open

The response is JSON and includes the channel ID.

Use that ID to send a message:

buzz messages send \
  --channel CHANNEL_ID \
  --content 'The release build is ready'

Read the latest messages:

buzz messages get \
  --channel CHANNEL_ID \
  --limit 20

Search across messages:

buzz messages search --query 'release build'

Because the output is JSON, you can pipe it to jq or hand it directly to an agent.

Let an agent join a channel

An agent needs its own identity.

Generate a separate keypair for it:

buzz-admin generate-key

Then add the agent’s public key to a channel:

buzz channels add-member \
  --channel CHANNEL_ID \
  --pubkey AGENT_PUBLIC_KEY \
  --role member

The agent can now receive activity from that channel.

The buzz-acp process connects the channel to an ACP-compatible agent. When someone mentions the agent, Buzz sends the work through the harness.

This separation is important:

Do not reuse your personal key for an agent.

If the agent makes a mistake, you want the audit trail to show that the agent made it.

Search becomes project memory

Search is often treated as a convenience feature.

In an agent workspace, it is part of the memory system.

Suppose you ask:

Have we seen this database timeout before?

An agent can search previous messages, find an old incident, link the relevant thread, and summarize the fix.

The answer remains in the channel with the evidence.

That is better than an agent returning a confident answer with no source.

Buzz uses Postgres full-text search and applies workspace access rules. An agent should only get results from channels it can access.

The event log also gives you a chronological record. Search finds the relevant pieces. Signed identities tell you who created them.

Together, those three things turn old project activity into usable context.

Signed does not mean secret

Buzz signs events, but signatures do not encrypt their content.

This is an important distinction.

A signature proves who created an event and whether it changed. Encryption controls who can read it.

Buzz uses TLS while data moves over the network. Storage encryption is left to the storage layer.

Messages, DMs, and media in Block-hosted communities are not end-to-end encrypted. The service operator can access them when needed to operate, secure, or moderate the service, or to comply with the law.

The same general reality applies when you self-host: the operator controls the server and storage.

Do not treat Buzz DMs like an end-to-end encrypted messenger.

Channel membership is the main access boundary inside the workspace.

The audit trail

Buzz keeps a tamper-evident audit log.

Each audit entry includes the hash of the previous entry. Changing an older entry breaks the chain after it.

This does not make the database impossible to change. The server owner still controls the infrastructure.

It makes silent modification detectable.

The signed event history and hash-chained audit log answer different questions:

This becomes useful when agents can create channels, edit documents, run workflows, and work with repositories.

The more an agent can do, the more you need a clear record of what it did.

What I like about Buzz

The interesting part is not any single feature.

Chat exists. Git hosting exists. Workflows exist. Coding agents exist.

The interesting part is giving them one event model.

An agent does not sit outside the workspace and reach into it through a pile of unrelated integrations. It joins the room, gets scoped access, and leaves signed actions in the same history as everyone else.

I also like the self-hosting model.

You can own the relay, database, files, identities, and project history. Or you can use a hosted community when you do not want to run the infrastructure.

And I like that the CLI is JSON-first.

Many tools say they support agents, then expose a human interface and expect the agent to click around. Buzz has a CLI and protocol surfaces made for programmatic work.

What would make me cautious

Buzz is trying to replace a large collection of mature tools.

That is ambitious.

GitHub, Slack, Discord, Linear, CI services, and document tools each have years of features and integrations. Putting their core ideas in one workspace creates a simpler mental model, but it also creates a large product to build and maintain.

Buzz is early. Some interfaces will change. Some features are incomplete. Mobile work is active. Approval gates still need wiring.

Self-hosting also has a cost.

You need to run the relay, Postgres, Redis, and object storage. You need backups, monitoring, upgrades, and a plan for private-key management.

For a small team, several hosted tools may still be easier.

The lack of end-to-end encryption also matters for sensitive conversations.

My advice is to treat Buzz as an experiment today. Run it locally, add one agent, and try one real project channel.

Do not migrate your entire company on day one.

Who should try Buzz?

Buzz is worth trying if you are already using several coding agents and feel the coordination problem.

It also makes sense for open source teams that want the code, community, and project history under their own domain.

Try it if you want:

Wait if you need a polished Slack or GitHub replacement today.

The project is moving quickly, and the core idea is strong: agents should not be invisible processes working outside the team.

They should be in the room where the work happens.

That is what Buzz is building.

Tagged: AI · All topics
~~~

Related posts about ai: