Nostr Agent Onboarding · start here · index · source:
nostr-dev/docs/design-synthesis.md· snapshot 2026-10-10
Designing solutions on Nostr — synthesis
Codification of the design principles for Nostr applications. Synthesized from Hodlbod's "Building Nostr" book (Coracle), fiatjaf's writing on fiatjaf.com, NIP-01, the NIPs kind registry, the OpenSats client report, the Nostr Rising podcast series, and the Gossip / Outbox literature.
This is the first document any Claude session in this workspace should read before designing or building anything Nostr-shaped. It is the "why" layer.
CLAUDE.mdis the "what is installed" layer.docs/kind-cheatsheet.mdis the "what to do when picking a kind" layer.
1. The single load-bearing idea
Nostr's whole shape falls out of one move: signatures decouple authentication from storage. The signed event is the unit of truth. Relays are interchangeable mirrors. The address space is keypairs. Everything else — outbox routing, replaceable events, NIP-05, GRASP — is a downstream consequence.
When you design for Nostr, you are not designing a system. You are designing:
- an event schema (kind + tags + content shape), and
- a routing strategy (which relays this kind goes to / comes from).
The runtime is whatever relays happen to be around.
This is also the trap. People keep trying to reintroduce centralization — a
canonical relay, a global index, a "main" feed — because eventual-consistency-
across-anonymous-mirrors is genuinely uncomfortable. Resist that. Lean into
"many hackish attempts" (fiatjaf's phrase, see sources/fiatjaf-vision.md) as
a feature, not a bug.
Worked example: same idea, applied to packet routing (FIPS)
The cleanest demonstration that this load-bearing idea generalizes outside
Nostr's own event/relay surface is FIPS (Free Internetworking Peering
System; sources/fips.md). FIPS is a decentralized mesh routing protocol
whose nodes are identified by the same secp256k1 keypairs as Nostr nodes.
Peer discovery, endpoint advertisement, and NAT-traversal signaling all
happen over Nostr relays (kind 37195 addressable adverts; kind 21059
NIP-59-wrapped offers/answers). Where Nostr decouples authentication from
event storage, FIPS decouples it from packet routing — but the underlying
move is identical: signed thing + interchangeable mirrors. If you're
designing anything that wants "Nostr keypair as identity" but isn't itself a
Nostr application, FIPS is the most fully-developed reference architecture
available, and its 13 layered design specs in repos/fips/docs/design/ are
worth reading on their own as a protocol-design exercise.
2. Routing: relay-feed vs outbox vs gossip
Three positions on the same axis, increasing sophistication. Pick deliberately; don't drift.
Relay-feed (fixed list)
The simplest model. The client connects to a curated set (typically 3–10 relays), publishes there, reads there. Easy to reason about; predictable.
Costs: - Centralization — popular relays carry everything; the long tail dies. - Brittle to censorship — if the relays in your fixed set drop you, you're gone, and your followers can't find you. - Doesn't scale — you either over-fetch (connect to thousands of relays defensively) or under-fetch (miss the author's actual data).
Right choice when: the relay set is intentional and bounded. A community relay, a private/team relay, a topical feed, a paid index. Not the right choice for a general-purpose social client.
Outbox (NIP-65, kind 10002)
Each user advertises their relays via a kind 10002 event. The two flags:
write≡ OUTBOX — where they publish.read≡ INBOX — where DMs/mentions should be sent.
Routing rules that fall out of this:
| Action | Where to write | Where to read |
|---|---|---|
| Read someone's posts | — | their OUTBOX |
| Send a DM / mention | recipient's INBOX + your own INBOX (for record) | — |
| Publish your own post | your OUTBOX + tagged users' INBOX | — |
| Publish a profile / follow list / relay list | your OUTBOX | — |
The honest framing from the literature: outbox is necessary but not sufficient. NIP-65 only solves "who-publishes-where for kind:1-style social." Communities, DMs, topical feeds, search/indexing relays — none of those are solved by NIP-65.
Gossip (Mike Dilger's superset)
Outbox plus every other discoverable relay hint:
nprofileURIs that include relay hints,- NIP-05 metadata that names relays,
- relay hints in
e/p/atags on events that mention people, - observed presence of authors in replies on relays you already speak to.
The Gossip client (mikedilger/gossip) is the canonical reference. NDK and Coracle both implement gossip-flavored outbox. If you build a serious client, you want gossip-flavored outbox. If you build a tool or pipeline (a bot, a specialty relay, a single-purpose client), a fixed list is often fine — be explicit about why.
Per-kind routing — the real design surface
Once you accept "no single relay set fits all jobs," the design question becomes per-event routing:
- Public broadcast (kind 1 notes, reposts, reactions) → outbox + tagged inboxes.
- DMs (NIP-17 / NIP-44 / NIP-59) → inbox-only of recipient.
- Community / group content (NIP-29, NIP-72) → community relay only.
- Topical / curated feeds → topic relay (paid, vetted, or invite-only).
- Search / indexing (kind 30000-range listings) → indexer relay
(
purplepag.es,user.kindpag.es,search.nos.today;relay.nostr.bandwas unreachable as of 2026-09). - GRASP / nostr-git events (kind 30617/30618/1617/1621/1622) → repo's GRASP relay first, fall back to user's outbox.
A client that does all five well is hard. A client that does one of them brilliantly is the Nostr way.
3. Event kinds — taxonomy and decision rule
NIP-01 defines four classes by kind number. The relay's storage contract is different for each class. This is the key design choice; it is also where most people make the wrong call.
| Class | Range | Relay contract | Use for |
|---|---|---|---|
| Regular | 1–2, 4–44, 1000–9999, 40000+ | Store everything; full history. | Notes, reactions, deletions, reposts. Default. |
| Replaceable | 0, 3, 10000–19999 | Latest per (pubkey, kind). |
Single-current-value-per-user state: profile (0), follow list (3), relay list (10002). |
| Ephemeral | 20000–29999 | Not stored. Live broadcast only. | Live signaling: typing, in-flight signing (NIP-46), presence. |
| Addressable | 30000–39999 | Latest per (pubkey, kind, d-tag). |
Multi-keyed state: long-form by slug (30023), git repos by name (30617), lists. |
Default to regular
This is the consistent advice across Hodlbod, fiatjaf, and the Coracle book.
Regular events have history, are auditable, and degrade gracefully. The other three classes are optimizations that throw information away — only reach for them when you can articulate why throwing it away is the right call.
When to reach for each non-regular class
-
Replaceable — there is exactly one current value per user, and history is noise, not signal. Your relay list, not "all the relay lists you've ever had." Your profile picture, not a timeline of profile pictures.
-
Addressable — same as replaceable, but you have multiple distinct items keyed by an identifier you control: one article per slug, one repo per name, one list per d-tag. Replaceable + cardinality.
-
Ephemeral — persistence would actively be wrong. A "user X is typing" event that lives forever is bizarre. A NIP-46 in-flight signing request that hangs around for months is dangerous. Ephemerals are the right call when the event has only an instantaneous meaning.
-
Regular — everything else. Including: things you might be tempted to make replaceable for "efficiency." If you want history, accountability, threading, or "who said what when" — use regular.
Common smells
- A "settings" event that's regular and accumulates → should be replaceable.
- A "current location" event that's regular → should probably be ephemeral.
- A "blog post" that's regular and edited → should be addressable.
- A "comment" that's addressable so it can be edited → almost certainly wrong; comments are accountable, use regular and tombstone with a NIP-09 deletion if needed.
- A "draft" that's regular → should be addressable (one current draft per user-defined identifier).
4. Picking a kind — concrete recipe
The instinct "check before you allocate" is the right one. The procedure:
-
Read the canonical kind table in
nostr-protocol/nips/README.md. This is the registry. (See vendored copy indocs/sources/nips-readme-kinds.mdfor offline reference.) -
Use NAK locally — it ships with NIP descriptions:
nak nip # list all NIPs nak knows nak nip 34 # show NIP-34 (git stuff) nak nip open 34 # open NIP-34 in the browserOn this machine NAK knows about 91 NIPs. -
Probe the live network to see whether a kind is in use even if undocumented:
nak req -k 39999 -l 1 wss://relay.damus.io wss://nos.lolThis is the most underrated step. Plenty of clients ship private kinds that aren't in the registry but show up everywhere. We confirmed kind 39999 is occupied in the wild even though no NIP claims it. -
Reuse before you invent. If your data fits an existing schema, use the existing kind. Inventing a new kind for "long-form content" because you don't like NIP-23 fragments the ecosystem and no one will read your events. This is the single biggest mistake new builders make.
-
For new public kinds — open a PR against
nostr-protocol/nips. The community process is informal but real. For a private/in-house kind that only your client cares about, pick a high number in an unallocated band, document it in your repo, and accept that other clients will ignore it.
A practical 30-second cheatsheet lives in docs/kind-cheatsheet.md.
5. Architectural philosophy — distilled
From Nostr Design's "Guiding Principles," fiatjaf's posts, Hodlbod's book, and the OpenSats report:
- User sovereignty above all. Control of keys, choice of relays, no lock-in.
- Signatures decouple data from storage. Same event valid anywhere; relays are interchangeable mirrors, not authorities.
- Build small things that do one thing well. Modular clients > monolithic platforms. ("A client that does one thing really well.")
- Embrace the messiness. There is no global state. Discovery is "many hackish attempts." Eventual consistency is the norm.
- Don't censor; expose filtering. Give users tools, don't decide for them.
- Per-relay context matters. User-specific (outbox), public-feed (broadcast), topical/community (curated), DMs (inbox-only). One relay set doesn't fit all jobs.
6. What this means for building solutions on Nostr
The design surface is four decisions, taken in this order:
1. What's the event schema?
A kind, a tag set, a content shape. This is the only schema you get; tags are your data model. Reuse existing kinds and tag conventions wherever you can. Document any new ones in your repo.
2. Which class of event?
Default regular. Justify any other choice in writing — what are you optimizing, what are you discarding?
3. What's the routing strategy per kind?
- Outbox for broadcast,
- inbox for delivery,
- topic relay for community,
- index relay for search.
Don't conflate.
4. What's the failure model?
Events arrive duplicated. Events don't arrive at all. Events arrive late, out of order, or from a relay that's lying. Build for that:
- idempotent ingest,
- dedup by event id,
- treat relay responses as "best effort,"
- never assume "I queried 3 relays and got nothing, therefore it doesn't exist."
The recurring theme: don't build platforms, build pieces. A client that does one thing well, against an event schema that other clients can also speak. Censorship-resistance, identity portability, and the entire point of the protocol fall out of that. Try to build a "Nostr Twitter" and you'll fight the protocol; build a Nostr-native long-form reader, a code-collaboration tool, or a feed algorithm, and the protocol carries you.
7. Local sources
Vendored where licensing allows; summaries + links otherwise. See
docs/sources/:
nip-01.md— NIP-01 verbatim (MIT, the canonical event-kind classification)nips-readme-kinds.md— the NIPs README kind registry tablefiatjaf-vision.md— summary + link to fiatjaf's "A vision for content discovery"outbox-model.md— summary + links to Nostrify, Why Nostr, gossip docshodlbod-book.md— pointer to building-nostr (cloned intorepos/for offline read)nostr-rising.md— episode list + summaries of the Nostr Rising seriesnostr-design-principles.md— Nostr Design "Guiding Principles" summaryopensats-advancements.md— OpenSats "Advancements in Nostr Clients" summaryvideos.md— pointers to fiatjaf / Hodlbod / Pablo / Dilger talks on YouTube