Markdown source for agents: https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/docs/design/design-synthesis.md

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.md is the "what is installed" layer. docs/kind-cheatsheet.md is 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:

  1. an event schema (kind + tags + content shape), and
  2. 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:

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:

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:

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

Common smells

4. Picking a kind — concrete recipe

The instinct "check before you allocate" is the right one. The procedure:

  1. Read the canonical kind table in nostr-protocol/nips/README.md. This is the registry. (See vendored copy in docs/sources/nips-readme-kinds.md for offline reference.)

  2. 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 browser On this machine NAK knows about 91 NIPs.

  3. 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.lol This 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.

  4. 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.

  5. 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:

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?

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:

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/: