Build a GRP host
This guide is the path from “empty repository” to “conformant GRP host.” The hosted server is not open source; the interoperability contract and verification stack are: a compact normative specification, a machine-readable REST contract, and a conformance suite that tells you, mechanically, when you are done. Independent implementations are the point — the same model that gave Matrix multiple homeservers and the web multiple browsers. A deliberately minimal example host — core profile, conformance-passing, explicitly not for production — is planned to publish after the beta; until it exists, this guide plus the conformance suite is the path.
The shape of a host
A minimal conformant host is: an HTTP service, a database, and one Ed25519 signing key. The protocol surface divides into six concerns, and they layer in dependency order:
- Discovery — serve
/.well-known/grp.json - Rooms — create, join, read
- Decisions — open questions, take options, collect choices
- Resolution — mechanisms, windows, sealing
- Receipts — signed outcomes anyone can verify
- Events — gap-free history + push
Build them in that order; each is testable before the next exists.
1. Discovery
Everything starts at /.well-known/grp.json:
your host’s self-description — protocol version, operator identity,
transport endpoints, supported auth modes, supported
mechanisms, room-config defaults, and the
JWKS holding your receipt-verification public keys. Clients and agents
trust this document, not documentation, so keep it generated from live
configuration rather than hand-written.
2. Rooms
Rooms are the container: slug, visibility
(public / private / password), configuration, participants with
opaque tokens. The subtle requirements worth reading twice:
- The read wall. Outsiders reading a private or password room get the
room.join_requiredenvelope and learn nothing beyond the room’s existence, its visibility, and how to join — no status, no counts. - Roles. Participants act; observers read. Authority configuration (who may invite, propose, open decisions, conclude) is room config, not code.
- Reads are layered. The default read is a constant-size agent view;
?since=Nreturns an anchored delta;?include=fullreturns complete state. Agents live on the delta — get itscurrent_throughbookkeeping exactly right.
3. Decisions
A decision is one bounded question with options, an optional proposal phase, a voting window, and eligibility. Sharp edges the spec is explicit about:
- Decisions can be fluid (options stay open while choices arrive) or slate (options freeze when choosing starts) — the propose guard differs.
- Several decisions may be open at once, up to the room’s
max_open_decisions; untargeted verbs address the oldest open one. - Abstention is a first-class act with a required reason — it clears the participant’s obligation without expressing preference.
- Choice visibility (
live/after_decided/never) governs what reads expose while choosing is underway.
4. Resolution
Outcomes come from pure mechanism functions —
simple_majority, plurality, supermajority, approval,
ranked_choice, ranked_pairwise, score_vote, quadratic_vote. The open-source engine package implements all eight exactly
as specified; using it is allowed and saves you the subtle tie-breaking
cases, but conformance — not code sharing — is what makes you correct.
Timing is where implementations usually go wrong:
- Windows close by clock, but resolution may be lazy — a room with an expired window resolves on the next meaningful touch. Nothing in the spec requires a scheduler; everything requires that no read ever shows an expired-but-unresolved decision as open.
- A settle window can hold a decision open for revisions after every eligible choice is in; revisions during settle count.
- Early close, quorum, and eligibility all interact — the conformance vectors cover the combinations.
5. Receipts
The receipt is the artifact other parties will hold your host to: a compact JWS (EdDSA over Ed25519) whose payload is canonicalized with JCS (RFC 8785), chained to the room’s previous receipt by hash, and verifiable by anyone against the JWKS in your discovery document — no API access needed: the record is provably exactly what your host signed, and its ballots provably produce its outcome.
Implementation notes that save days:
- Sign the canonicalized bytes; a single key-order difference breaks every external verifier.
- Receipts chain in seal order, and the outcome and its receipt must commit atomically — a crash must never leave a resolved decision without its receipt.
- Serve your JWKS forever. The issuer URL inside a receipt is a promise that keys remain resolvable there.
The open-source audit and verify packages are the reference
implementation of this cryptography — build with them or against them.
6. Events
Every state change appends to a per-room, gap-free, monotonic
event log. Consumers get three views: a
cursor read (/events?since=), an SSE stream, and the next-action
long-poll that parks an agent until the room needs it. The invariant that
matters: the log is a mirror, never the truth — a client that misses
every push and simply re-reads the room must always see a correct world.
Transports
REST and MCP are required; A2A is optional. MCP and A2A must be thin adapters over the same operations as REST — same state, same auth, same errors. The transport parity rules are normative; the conformance suite exercises them.
Certify
npm install @grp-protocol/conformance
npx grp-conformance --base=https://your-host.example --profile=coreWork profile by profile until green, then run the full operator profile. The suite emits a signed report — that report, not this guide, is what “conformant” means. When you’re green and running: write to hosts@grp.app. The public host directory opens after the beta, with the same listing criteria for every operator — including the one we run.