harvester.blue#
A Product Hunt for the at-proto ecosystem — but instead of hunting the next launch, we harvest what's actually growing on the network.
harvester.blue is a directory and ranking site for at-proto apps. Apps don't earn their place by marketing alone; they earn it by being used, being voted for, and being built well.
Nomination#
Apps enter the directory by being nominated by a harvester (a logged-in user). The nominator declares the measurable aspects of the app — most importantly:
- The record schemas (NSIDs) the app owns — what the firehose pipeline should count as evidence of use.
- The app's canonical URLs / domains — what counts as a mention.
Nominator-declared metadata is the input to all the automatic measurement that follows.
Verified nominations#
Anyone can nominate any app. But if the nominator is verified via keytrace as authorized on behalf of the app's domain, their nomination is treated as canonical — it's the version of the listing the site stands behind. Unverified nominations remain visible (and useful for surfacing apps whose makers aren't on harvester.blue yet), but a verified nomination overrides them.
Recommendation to app builders: link your identity#
To make your app's listing canonical and to score higher across the board, line up your identity end-to-end:
- Set up a DID for your service.
- Host the app under a domain you control.
- Publish your lexicons under that same name.
When your domain, DID, and NSID authority all match, everything links: verification is straightforward, citizenship badges line up (service records, correctly-published lexicons), and the metrics counted against the listing are unambiguously yours. The more these are linked, the higher the listing counts.
The three metrics#
Each app shows three metrics side-by-side. They are deliberately not collapsed into a single "harvester score" — each tells a different story, and the user picks the sort order based on the question they're asking.
1. Engagement — are people using it?#
Count of distinct DIDs creating, or updating records containing $type fields that match your lexicons.
2. Buzz — are people talking about it?#
Count of distinct DIDs whose creates/updates contain URLs pointing at the app's domains.
3. Votes — do humans think it's cool?#
Logged-in users (via Atproto OAuth) vote for apps they like. Votes are stored as records in the voter's own repo under a harvester.blue lexicon — portable, verifiable, and expensive to fake without real DIDs behind them.
Citizenship badges#
Separately from the three metrics, apps display citizenship badges issued as cryptographic attestations via badge.blue. Badges reflect "built right" on at-proto:
- Lexicons correctly published.
- Service records added to the DID.
- Minimal OAuth scope — not
transition:generic. - Composes with other at-proto services — uses lexicons or services from projects like Tangled (code), and others in the broader at-proto ecosystem.
- Brings decentralizing infra. The principle is that good citizens contribute infrastructure that makes the network more distributed, rather than running entirely on existing shared infra. Self-hosting a PDS is the canonical example, but running a feed generator, labeler, relay, or appview also counts.
Badges are displayed per app. Because they're attestations on badge.blue, they're verifiable independently of harvester.blue itself.
Why metrics + badges, not one score#
Engagement alone rewards popular-but-poorly-built apps. Votes alone reward marketing. Citizenship alone rewards purism without traction. Showing the three metrics side-by-side, with citizenship as an orthogonal layer of attested badges, lets a viewer ask the question that fits them — who is being used, who is being talked about, who is loved, who is doing it right — without the site picking the answer for them.
Architecture#
The system splits cleanly into two tiers with different scaling stories:
- Go VM (singleton). Consumes the Jetstream firehose, writes nominations and votes straight to a local SQLite file, and aggregates engagement/buzz metrics in an in-memory HyperLogLog that periodically flushes to the same DB. Exposes read-only XRPC over Fly's private network. Cannot be horizontally scaled — only one consumer at a time, otherwise events double-count into the HLL.
- Frontend VMs (stateless, autoscaled). TanStack Start handles UI/SSR and proxies both metric XRPC calls and PDS operations so the browser only talks to one origin. Truly stateless: OAuth sessions and PKCE state live in Upstash Redis (with native TTLs), so frontends can autoscale freely and roll on every deploy without logging anyone out.
Diagram source lives in
architecture.mmd. To regenerate the SVG after editing:mmdc -i architecture.mmd -o architecture.svg --backgroundColor white
Status#
Early. The current code (main.go) is a Go scratch that subscribes to Jetstream and extracts unique URLs from records — the seed of the activity-measurement pipeline.
Open questions#
These are unresolved and tracked here so they don't get lost:
- Mention counting: any URL on the app's domain, or only canonical URLs? Apps with user-generated subpaths will otherwise dominate.
- Measurement window: trailing 7d, 30d, all-time? Probably surface both — "growing" vs "established."
- Vote budget: 1 per product ever (PH-style), 1 per day, or a small per-season budget (e.g. 5/week)?
- "Composes with other services": what counts — links out, reads from, writes to, reuses lexicons? And which services qualify — Tangled, Frontpage, Smoke Signal, Whitewind, Linkat, Bookhive, others?
- "Decentralizing infra" badge: how is this verified? Self-hosted PDS is detectable from the DID doc; feed generators and labelers are discoverable via service records; what about appviews or relays?