Who wrote it. A public key is an identity — no username, no account row.
content
What they wrote. Plain text here; sometimes JSON, sometimes encrypted.
kind
What sort of thing it is. A number. 9 means "channel message."
tags
Labelled extras. Here: which channel it belongs to.
created_at
When they say they wrote it. Their clock, not ours.
id
A hash of the five fields above. Change any of them and the id changes.
sig
A signature over that id, made with the private key.
The signature proves one thing: this key produced these exact bytes. It says nothing about whether any of it is true.
this is the Nostr event format — every Buzz feature is built on this shape
Zooming box two
Box two · the server
In Nostr, that server is called a relay. Ours does far more than relay.
What "relay" normally means
A dumb pipe with a hard drive.
Accept signed notes. Check the signature. Store them. Hand them to anyone who asks. That is the entire job of an ordinary Nostr relay, and it is why the word "relay" was chosen.
What the Buzz relay is
An application server wearing a relay's clothes.
It also enforces membership, runs moderation, executes commands, schedules automation, computes answers on the fly, and signs notes of its own.
Postgres holds the history. Redis pushes live updates. Neither is optional.
Almost everything that confuses people about Buzz comes from expecting the left-hand box and getting the right-hand one.
Model · level 1
Level 1 · who is in box one
Three kinds of author. The third one surprises people.
Box one
Someone signs a note
a personSarah, from her laptop or phone
an agentAn AI agent. It has its own keypair — it is a full participant, not a bot account owned by a human.
the relay itselfThe server signs notes as itself: system messages, membership announcements, automation output.
→
Box two
The server decides what it is
→
Box three
Something happens
Remember the third one. When a workflow posts a message on your behalf, it is the relay's signature on it, not yours.
The example we will follow
One message. We will follow it the whole way.
Sarah · #engineering
"RC is ready — @Triage can you check the changelog?"
It is a note
Box one
Sarah's client builds the JSON, fills in the channel and the mention, and signs it with her key.
We just learned this shape.
It must get in
Box two
The relay decides whether to accept it — and that decision has more steps than you would guess.
Act 1 opens this box.
Then things happen
Box three
It gets stored. People see it. An AI agent wakes up. Automation may fire. Four different things.
Act 2 opens this box.
keep this message in mind — every abstract idea in this deck arrives because this message needs it
Zooming box two
Act one · the two fields that decide everything
Before the server can decide, it reads two fields.
Term · defined once
kind
A number that says what sort of note this is. It is not a category or a folder — it is closer to a function name. It tells the server which piece of code should handle this.
9 = channel message · 7 = emoji reaction · 0 = profile 9030 = add someone to the workspace · 30620 = an automation definition
Term · defined once
tags
A list of labelled extras. Each tag is an array whose first item is the label. Three you will see all deck:
h — which channel this belongs to p — a person or agent this concerns e — another note this refers to
Buzz has names for 134 kinds. You can write 84 of them.
84
On the list
A client can send these, if it has the right permission.
31
Relay writes these
The server signs them itself. You are refused if you try.
6
Readable only
The app displays them. Nothing can create them.
5
Never stored
Typing, presence — delivered and forgotten.
4
Logging in
Used to prove who you are, not to say anything.
4
Dead
Named, never wired to anything.
gold · what this slide introducesteal · durable, survives a restartcoral · the gap or the trapviolet · written by the relay itselfperiwinkle · never storeddashed · named but not built
A list of kind numbers is a dictionary, not a menu. Two-fifths of the words in it are not yours to say.
counted from crates/buzz-core/src/kind.rs × ingest.rs — re-checked by npm run verify
Model · level 3
Act two · opening the last box
"Something happens" is really four questions.
Box one
Someone signs a note
→
Box two
The server decides what it is
→
Box three
Something happens
what does it change?Which part of the application reacts
where does it land?A table, a different table, or nowhere
who can read it?Everyone, some people, or only the author
what does it run?Nothing, automation, or an AI agent
These four are independent. Getting a yes from one tells you nothing about the others — which is exactly why the system surprises people.
Zooming box three
Act two · question two
Now we can answer the question from the first slide.
Sarah's message · kind 9
Saved, like you expected.
It goes into the events table as a row. It gets delivered to everyone subscribed to #engineering. It can be searched, replied to, reacted to, and fetched again next week.
Adding a member · kind 9030
Never saved as a message at all.
It is treated as an instruction. The relay changes the membership table, then writes a completely different, new note announcing the result — signed by itself.
The original instruction is not in the events table. Replaying every event would not rebuild your workspace roster.
Some notes are records of something that happened. Some notes are instructions to make something happen. They look identical.
Sarah changes her profile picture twice. How many notes exist?
The intuition
Two. It is an append-only log.
Both versions sit in the table, newest wins on read, and the old one is history you could go back to.
What actually happens
One. The second replaced the first.
Profiles are a "keep only the latest" kind. The relay keeps one note per author per kind, and the superseded one is usually removed from view entirely.
keep every one
Messages
Keyed by
the note's own id
Examples
messages, reactions, forum posts
keep the latest
Profiles, lists
Keyed by
author + kind
Examples
your profile, your mute list
keep the latest, per name
Documents
Keyed by
author + kind + a name you choose
Examples
an AI persona, an automation definition
keep nothing
Signals
Keyed by
—
Examples
typing, presence
Latest wins by the author's own claimed timestamp — which is why that 15-minute clock check exists.
evidence crates/buzz-db/src/store/replaceable.rs
Zooming box three
Act two · question three
One note. Five people ask for it. Five different answers.
the note
An AI agent records how many tokens a task cost — encrypted to its owner.
the owner
Yes
Because
their key is named in the note's p tag
a teammate
No
Because
their key is not named — the relay refuses the request outright
someone who knows the id
Still no
Because
asking for it by id is separately blocked — knowing it exists is not enough
someone counting
No
Because
"how many are there" leaks too — counts go through the same gate
the search index
Never indexed
Because
these notes are stored with their search text deliberately left empty
Encryption hides the contents. These gates hide the fact that the note exists at all.
Profile, membership, DMs, jobs. The identity is portable across communities; this layer is not — a protected avatar URL cannot cross the media authorization boundary.
does not propagate
Editing the persona does not mutate a running agent. A live instance consumed its definition when it started.
Runtime
The ACP harness
A process — laptop, pod, provider. Announces liveness with an ephemeral kind 20001. Holds every conversation in memory.
Deployment should be reconciliation toward one live instance per key — not "create another process."
A mention from a non-owner is dropped by the harness, silently — and the relay's records show a perfectly successful delivery.
evidence crates/buzz-acp/src/{relay,filter,queue,pool,acp}.rs · config.rs — RespondTo::OwnerOnly is the default
Act four · Concurrency
One identity. N workers. The relay cannot tell them apart — by design.
What the relay sees
One pubkey.
Authenticated. Subscribed. Publishing. Every worker in the pool presents as the same Nostr identity, so runtime topology stays private behind the key.
The cost: cross-channel ordering is not guaranteed once more than one worker is enabled.
What the harness runs
A pool, claimed per scope.
The unit of concurrency is the conversation scope — a channel by default, a thread under the thread session policy. At most one prompt is in flight per scope; later traffic queues, merges, or steers.
Three messages in one channel do not fan out to three workers. Three channels do.
01 The binary allows 1–32 subprocesses and defaults to 1.
02 Desktop-managed agents default to parallelism 10.
03 "10" is not ten model conversations — adapters differ.
Answerable. There is a real run model: pending, running, waiting_approval, completed, failed, cancelled.
Ask the relay
"Which agent turns are running?"
Not answerable today. That knowledge lives in whichever harness owns the runtime, with no TTL and no global registry. Restart it and the channel→session mapping is gone.
A mention arrives mid-turn. Four policies, and three of them cancel.
queue
Wait
Does
hold the new events; deliver after the current turn completes
Cost
latency
steer · default
Weave it in
Does
cancel, then re-prompt with the new message framed as arrived while you were working
Cost
the cancelled work
interrupt
Replace
Does
cancel and re-dispatch, framed as a supersede, for any admitted author
Cost
partial work discarded
owner-interrupt
Replace, but only for the owner
Does
everyone else queues normally
Cost
owner resolution on the hot path
None of these inject into a live turn. They cancel it and re-dispatch a merged prompt.
"Running" disagrees with itself here too: the queue says in-flight, the ACP client says prompt active, the adapter may or may not expose a live run to steer. That disagreement has an error name.
A workflow exists twice, and the two copies can drift.
Desired state
The signed definition
Kind 30620, addressable, authored as YAML and stored as canonical JSON. This is what Desktop and the CLI read. It is portable, signed, and it is the user-facing truth.
Actual state
The operational row
The relay materializes the definition into a workflows record that the scheduler and executor use. This is what actually runs.
The failure
Ghost workflows
Delete the operational row and leave the signed definition: execution thinks it is gone, the management UI still lists it, and editing the ghost can recreate it.
The shape of the fix
Controller thinking
Signed desired state reconciles to materialized operational state, as one lifecycle operation. The same pattern as agent deployment — converge, don't create.
The relay never holds the owner's private key. It signs a new event and attributes the owner in metadata.
buzz:workflow
Loop guard
Excluded from ordinary workflow triggering.
workflow-owner
Policy anchor
Lets a harness apply the owner's policy to a relay-signed message.
workflow-mention
Anti-laundering
Emitted only when that target was authored into the template — never when it arrived through substitution.
Mention resolution is deliberately paranoid: members only, exact display name, greedy-longest and non-overlapping — and if two members share the matched name, no one is woken.
Untrusted trigger text can produce a p tag. It cannot produce a buzz:workflow-mention. Authority must not be launderable.
The relay accepted the operation through this path, and the durable write committed.
not guaranteed
Every subscriber received it.
not guaranteed
The audit row is committed — the dispatch returns after a bounded enqueue.
not guaranteed
Every projection is correct.
not guaranteed
A triggered workflow ran, or finished.
not guaranteed
Any external side effect happened.
01 Redis is fan-out. Postgres is history. A disconnected client must recover from history.
02 Slow consumers get disconnected. The socket is not a durable queue.
03 For anything critical: a consumer ledger on community + source_event_id + handler_version.
evidence crates/buzz-relay/src/handlers/event.rs — dispatch_persistent_event() spawns and returns
Act six · Turning the instrument around
Now you are adding a kind. Fill in the six gates.
01
What job is this? fact · document · command · private submission · signal · projection
02
Who signs it? human · agent · service · the relay itself
03
Which scope admits it? a new arm in required_scope_for_kind — or it is rejected
04
What is its scope of meaning? community-global · channel · recipient · author
05
What validates it beyond the signature? tags, referenced objects, membership, schema
06
Where does it land? events table · a dedicated table · nowhere
07
What is its lifecycle? immutable · replaceable · addressable · deletable · expiring
08
Which reads may expose it? history · live · COUNT · ids · search · export
09
Is its effect an invariant or a best-effort hook? this is the atomicity decision
10
What makes a retry safe? event id · domain key · revision · transaction boundary
11
What happens when an upstream client does not understand it? we are a fork; upstream will meet this kind
Eleven answers before a number. The number is the last decision, not the first.
Model · level 5
Act seven · back to the picture we started with
Four places we could put something of our own.
Box one
Someone signs a note
surface 1Run a service with its own key. It signs and reads notes like any other member. No relay change at all.
→
Box two
The server decides what it is
surface 2Add one entry to the list, so a new kind is accepted and gets a permission and a read rule.
→
Box three
Something happens
surface 3Give the kind its own handler — a transaction, a private table, enforcement before anyone sees it.
surface 4Answer a question the relay computes and signs on the spot, with nothing new stored.
The same three boxes we drew in the first five minutes. Every option we have is a position on this picture.
Act seven · Our fork
Four places we can extend. They are not equally expensive.
surface 1 · cheapest
External consumer
Do
authenticate a service as its own Nostr identity, subscribe, act, publish results as existing kinds
Get
zero relay change, zero upstream conflict, independent deploy cadence
Can't
enforce anything before publication · hide data · close a fan-out gap
surface 2 · small patch
New kind on the allowlist
Do
add to kind.rs, an arm in required_scope_for_kind, and a read-gate decision
Get
durable, typed, authorized, queryable storage with real privacy rules
Cost
a patch in two hot upstream files — mandatory special review on every sync
surface 3 · real patch
New handler or side effect
Do
route the kind to the command executor, a dedicated table, or a direct mutation
Get
transactional semantics, private storage, enforcement before disclosure
Cost
highest conflict surface — and you must answer question 09 honestly
surface 4 · read path
Relay-signed projection
Do
compute and sign a response at query time, as 39005 / 39006 / 40901 already do
Get
derived views with no new storage and no new write path
Cost
must be added to is_relay_only_kind so clients cannot forge it
The boundary rule: external service when it can safely consume accepted events and publish results. Patch the relay when correctness or confidentiality must be enforced before publication, storage, or disclosure.
governance UPSTREAM.md — kinds, auth and agent authorization changes require special review on every weekly sync
Act seven · Three proposals to argue about
Three candidates, deliberately at different costs.
Proposal 01 · cheapest real win
Wire the job vocabulary
43001–43006 already exist in the registry. Clients already render them from the activity feed. Scope::JobsRead and Scope::JobsWrite already exist in the auth crate and are used by no kind at all.
The only missing piece is one arm in required_scope_for_kind plus an authorization rule for who may accept, progress, and complete a job.
Surface 2 · ~1 file · read path already done
Do we want a first-class job protocol, or is this vocabulary a dead end we should stop rendering?
Proposal 02 · closes the Act 4 gap
A durable agent-run record
Three of four meanings of "running" vanish on restart. Proposal: the harness writes an addressable, author-only run record at turn start and turn end, keyed (agent, kind, scope), carrying an expiry the way push leases already do.
Then a relay-signed projection answers "every live run in this community" without exposing which worker did the work.
Surface 2 + 4 · new kind + query-time projection
Who signs the run record — the harness or the relay? And what does a crashed harness leave behind?
Proposal 03 · the hard one
Enforced compliance hold
An enterprise hold is an invariant, not a consequence: the event must not be considered committed unless the hold is recorded. That rules out a post-store side-effect hook.
The command executor is the right shape — but its own header warns that some domain mutations run on the pool outside the event-record transaction. So this needs a transactional outbox, not a spawned task.
Surface 3 · handler + transaction boundary
Are we willing to own a write-path patch in the file upstream changes most often?
Cost is not effort. Cost is what we carry every Monday.
Proposal
Surface
Upstream sync cost
Blast radius if wrong
Must be true before we ship
01 · job vocabulary
allowlist arm
one hot file, additive
a kind nobody writes
who may accept / progress / complete
02 · agent-run record
new kind + projection
two files + read path
stale runs look live
expiry semantics and crash behaviour
03 · compliance hold
handler + transaction
write path — conflicts every sync
a hold that silently did not apply
a transactional outbox, not a spawn
Open question 01
How much of this belongs upstream?
Anything general — jobs, run observability — is cheaper contributed than carried. Anything enterprise-specific is ours forever.
A patch we carry is a patch we re-review weekly.
Open question 02
Do we add kinds, or add gates?
Several enterprise asks are authorization changes wearing a new-kind costume. Read gates are cheaper and safer than new vocabulary.
Prefer explicit schemas and authorization tests over many new numbers quickly.
Open question 03
What is our invariant list?
Question 09 on the worksheet, answered once, for the whole product: which effects must succeed before an event counts as committed?
Everything else can be a best-effort hook, and should be.
Where we started
Two events. One shape.
Buzz is not a Nostr message bus with features bolted on. It is a signed application protocol sitting on top of an authoritative state machine — and the envelope is the only thing its eight jobs have in common.
Take away 01
The kind number is the vocabulary. The handler is the machine.
Take away 02
A signature proves who supplied the data. It proves nothing about the claim.
Take away 03
Accepted is not delivered. Stored is not state.
And when you add the next one: creation, admission, transition, persistence, visibility, consequence. In that order.