> **Nostr Agent Onboarding** · [start here](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/start.md) · [index](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/llms.txt) · 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`](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/docs/design/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`](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/docs/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`](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/docs/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:

- `nprofile` URIs that include relay hints,
- NIP-05 metadata that names relays,
- relay hints in `e`/`p`/`a` tags 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.band` was 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:

1. **Read the canonical kind table** in
   [`nostr-protocol/nips/README.md`](https://github.com/nostr-protocol/nips/blob/master/README.md).
   This is the registry. (See vendored copy in [`docs/sources/nips-readme-kinds.md`](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/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`](https://github.com/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`](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/docs/design/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 table
- `fiatjaf-vision.md` — summary + link to fiatjaf's "A vision for content discovery"
- `outbox-model.md` — summary + links to Nostrify, Why Nostr, gossip docs
- `hodlbod-book.md` — pointer to building-nostr (cloned into `repos/` for offline read)
- `nostr-rising.md` — episode list + summaries of the Nostr Rising series
- `nostr-design-principles.md` — Nostr Design "Guiding Principles" summary
- `opensats-advancements.md` — OpenSats "Advancements in Nostr Clients" summary
- `videos.md` — pointers to fiatjaf / Hodlbod / Pablo / Dilger talks on YouTube
