Nomad addresses every Drive by a 32-byte public key, and a Drive announces on the swarm under its
discovery key = hash(publicKey) — a “prove you know the key” gate, not a directory. That is
great for privacy but means a Drive you were never handed the key to is undiscoverable by design.
Discovery is the deliberate, opt-in escape hatch: a Drive can choose to appear on a public noticeboard so search engines can find it. Nothing is listed unless its author asks for it.
See ADR-0014 (the announce topic) and ADR-0016 (this vocabulary) for the full rationale.
The three manifest fields
All three live in a Drive’s /index.json and work on any Drive type (a blog, an app, a profile —
not just feeds). They are managed for you by the Site Info panel (“List this drive publicly for
search”), or you can set them directly with nomad.fs.configure.
{
"title": "Permaculture Weekly",
"description": "Notes on raised beds and composting",
"indexable": true,
"topics": ["gardening", "permaculture"],
"keywords": ["raised beds", "compost", "no-dig"]
}
indexable — the listing switch
A boolean. This, and only this, opts the Drive into discovery. When true, the Drive announces
itself on the well-known index topic so crawlers can find it. Absent or false means it is never
listed — exactly as unfindable as a Drive with no discovery vocabulary at all.
- Declaring Topics or Keywords does not list a Drive; only
indexable: truedoes. - A listed Drive with no Topics is still searchable by its title, description, and Keywords.
- Never set on private, Vault, or Root drives — the Site Info UI only offers it for writable, non-system drives.
- Un-listing (
indexable: false) removes the Drive from indexes, but its bytes still replicate to anyone holding the key. This is a listing layer, never deletion.
topics vs keywords — a deliberate split
These look similar but do different jobs. The one-line distinction: Topics scope the search; Keywords match within it.
| Topics | Keywords | |
|---|---|---|
| Question they answer | “What category is this?” | “What words should surface this?” |
| Role in search | Exact-match facet — a shelf you browse or scope by | Search text — matched alongside title & description |
| Compared as | Identities (equality across the network) | Text (case-insensitively matched) |
| Normalization | Lowercase hyphenated slug, any script (raised-beds) |
Free text, spaces allowed, wording preserved |
| Limit | 5 per Drive | 12 per Drive |
Why the split matters:
- Topics are canonicalized because there is no registry. Independent indexers must agree that two
Drives declaring
gardeningsit on the same shelf, without a central authority foldingGardening,gardening, andRaised Bedstogether. So Topics are slugified (Raised Beds→raised-beds) and you get at most five — they describe what the Drive is, not everything it mentions. - Keywords are just search text. They widen the net of queries that hit your Drive (
permaculture,no-dig) but place it on no shelf. They are matched, never compared, so they keep your wording and aren’t slugified. The 12-cap is an anti-spam limit enforced by the indexer — stuffing more buys nothing.
The tell for which field to use: if you want your Drive to appear in a browsable directory of like things, that’s a Topic. If you want it to turn up for a phrase someone might type, that’s a Keyword.
Values you write are normalized on save (topics slugified & de-duped, capped at 5; keywords trimmed & de-duped, capped at 12), and a well-behaved indexer re-enforces those caps on its side.
How search finds a Drive
Search is a plain aggregation layer beside the protocol — no central registry owns the namespace, and any number of indexers can run.
- A listed Drive announces on a single well-known topic derived from a public constant
(
hash('walled.garden/index/v1')) — the same 32 bytes for everyone, computable without holding any Drive key. - A crawler joins that topic and, over a small
nomad/listinghandshake, receives each listed Drive’s key plus its manifest-only fields (title, description, topics, keywords). - The crawler builds an inverted index and serves a search API. The unit of search is the Drive,
described by its own manifest — v1 does not crawl post bodies, so a good
title/descriptionis what makes your Drive findable. - The built-in
nomad://searchapp queries a configurable index endpoint, so no indexer is privileged — you can point it at any crawler you trust.
Two consequences worth knowing as an author:
- Your metadata is both your visibility and the spam defense. A lazy description means a nearly invisible Drive, no matter how good the content is.
- Follows are never crawled. Who follows a Drive is stored only on the follower’s side and never announced (ADR-0013). Discovery advertises the Drive, never its audience.
Running an indexer
The reference crawler is a standalone, ownerless peer at app/tools/crawler.mjs in the Nomad repo:
node app/tools/crawler.mjs --port 8787
# GET /search?q=<terms>&topic=<slug> · /topics · /stats
It holds no privileged position — it only reads what listed Drives volunteer. Run your own and point
nomad://search at it.