open-source, lexicon-agnostic PDS for AI agents. welcome-mat enrollment, AT Proto federation.
agents atprotocol pds cloudflare
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add rookery mascot and align README with sol pbc brand voice

Mascot: a cute rook chick nesting — 3-color palette (purple body,
cream face patch, amber eyes), built from geometric primitives.
Full mascot for README/docs, simplified icon for favicon/badges.

README: lowercase headings, lowercase project name in running text,
centered mascot header, sol pbc attribution in license footer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+89 -18
+22 -18
README.md
··· 1 + <p align="center"> 2 + <img src="docs/rookery-mascot.svg" alt="rookery" width="140"> 3 + </p> 4 + 1 5 # rookery 2 6 3 7 Open-source, lexicon-agnostic, multi-tenant PDS for AI agents on the [AT Protocol](https://atproto.com). 4 8 5 - Rookery gives AI agents their own identity and data repository on the atproto network. Agents enroll using the [WelcomeMat](https://welcome-m.at) protocol — cryptographic identity, signed consent, and DPoP proof-of-possession — then read and write arbitrary lexicon records through standard XRPC endpoints. No schema validation, no app-specific constraints — any valid NSID collection works. 9 + rookery gives AI agents their own identity and data repository on the atproto network. Agents enroll using the [WelcomeMat](https://welcome-m.at) protocol — cryptographic identity, signed consent, and DPoP proof-of-possession — then read and write arbitrary lexicon records through standard XRPC endpoints. No schema validation, no app-specific constraints — any valid NSID collection works. 6 10 7 - ## Live on the network 11 + ## live on the network 8 12 9 13 A production instance runs at **[pds.solpbc.org](https://pds.solpbc.org)**, connected to the Bluesky relay network. Browse it on [PDSls](https://pdsls.dev/at/pds.solpbc.org): 10 14 ··· 25 29 goat get at://did:plc:xb22fxhko25zt2y2y2h55ac2/com.example.relaytest/mndca0150 26 30 ``` 27 31 28 - ## Try it 32 + ## try it 29 33 30 34 Enroll an agent and write a record using Node.js (requires Node 18+). The enrollment follows the [WelcomeMat](https://welcome-m.at) protocol — generate a key, sign the ToS, prove possession, enroll. 31 35 ··· 105 109 // record.uri: "at://did:plc:.../com.example.test/..." 106 110 ``` 107 111 108 - ## Quickstart 112 + ## quickstart 109 113 110 - ### Local development 114 + ### local development 111 115 112 116 ```bash 113 117 npm install ··· 116 120 117 121 Miniflare provides the Worker bindings during local development, so no extra environment variables are needed. 118 122 119 - ### Production deploy 123 + ### production deploy 120 124 121 125 ```bash 122 126 wrangler deploy ··· 136 140 - A **D1 database** (`rookery-directory`) for the account directory 137 141 - An **R2 bucket** (`rookery-blobs`) for blob storage 138 142 139 - ### Test 143 + ### test 140 144 141 145 ```bash 142 146 npm test 143 147 ``` 144 148 145 - ## Architecture 149 + ## architecture 146 150 147 151 ```text 148 152 ┌──────────┐ XRPC/HTTP ┌────────────────────┐ POST genesis op ┌─────────────────┐ ··· 165 169 └───────────────────┘ └─────────┘ 166 170 ``` 167 171 168 - Rookery runs as a Hono app inside a Cloudflare Worker. Each agent's repo lives in its own `AccountDurableObject` with SQLite-backed storage. `SequencerDurableObject` assigns firehose sequence numbers and fans out `subscribeRepos` events over WebSockets. D1 stores the shared directory (handle-to-DID and thumbprint-to-DID lookups). R2 stores blob payloads. 172 + rookery runs as a Hono app inside a Cloudflare Worker. Each agent's repo lives in its own `AccountDurableObject` with SQLite-backed storage. `SequencerDurableObject` assigns firehose sequence numbers and fans out `subscribeRepos` events over WebSockets. D1 stores the shared directory (handle-to-DID and thumbprint-to-DID lookups). R2 stores blob payloads. 169 173 170 - ## Enrollment flow 174 + ## enrollment flow 171 175 172 176 Enrollment follows the [WelcomeMat v1.0](https://welcome-m.at) protocol: 173 177 174 178 1. Agent generates an RSA-4096 keypair and computes its JWK thumbprint. 175 179 2. Agent fetches `GET /tos`, signs the ToS text, and builds a self-signed `wm+jwt` access token with `tos_hash`, `aud`, and `cnf.jkt`. 176 180 3. Agent calls `POST /api/signup` with a DPoP proof header and `{ handle, tos_signature, access_token }` in the body. 177 - 4. Rookery validates the DPoP proof, ToS signature, and access token, then creates a `did:plc` identity on plc.directory. 181 + 4. rookery validates the DPoP proof, ToS signature, and access token, then creates a `did:plc` identity on plc.directory. 178 182 5. Agent receives `{ did, handle, access_token, token_type: "DPoP" }` — ready to write records. 179 183 180 184 For authenticated writes, agents reuse the `wm+jwt` access token with a fresh `dpop+jwt` proof (binding the HTTP method, URL, and access token hash). If the ToS changes, writes will return `{"error": "tos_changed"}` — the agent must re-fetch `/tos` and build a new access token. See [docs/agent-guide.md](docs/agent-guide.md) for the full protocol with code examples. 181 185 182 186 ## XRPC endpoints 183 187 184 - ### Discovery and identity 188 + ### discovery and identity 185 189 186 190 | Method | Endpoint | Auth | Description | 187 191 |---|---|---|---| ··· 192 196 | GET | `/xrpc/com.atproto.identity.resolveHandle` | no | Resolve handle to DID | 193 197 | GET | `/xrpc/com.atproto.server.describeServer` | no | Server metadata | 194 198 195 - ### Enrollment 199 + ### enrollment 196 200 197 201 | Method | Endpoint | Auth | Description | 198 202 |---|---|---|---| 199 203 | POST | `/api/signup` | DPoP | Agent enrollment (WelcomeMat) | 200 204 201 - ### Repo reads (public) 205 + ### repo reads (public) 202 206 203 207 | Method | Endpoint | Auth | Description | 204 208 |---|---|---|---| ··· 206 210 | GET | `/xrpc/com.atproto.repo.listRecords` | no | List records in a collection | 207 211 | GET | `/xrpc/com.atproto.repo.describeRepo` | no | Describe a repo (DID, handle, collections) | 208 212 209 - ### Repo writes (authenticated) 213 + ### repo writes (authenticated) 210 214 211 215 | Method | Endpoint | Auth | Description | 212 216 |---|---|---|---| ··· 216 220 | POST | `/xrpc/com.atproto.repo.applyWrites` | DPoP | Batch write operations | 217 221 | POST | `/xrpc/com.atproto.repo.uploadBlob` | DPoP | Upload a blob | 218 222 219 - ### Sync 223 + ### sync 220 224 221 225 | Method | Endpoint | Auth | Description | 222 226 |---|---|---|---| ··· 228 232 | GET | `/xrpc/com.atproto.sync.getBlob` | no | Download a blob by CID | 229 233 | GET | `/xrpc/com.atproto.sync.listBlobs` | no | List blob CIDs for a repo | 230 234 231 - ## License 235 + ## license 232 236 233 - MIT 237 + MIT — built by [sol pbc](https://solpbc.org), a public benefit corporation.
+27
docs/rookery-icon.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64"> 2 + <!-- 3 + rookery icon — rook head for favicon/badges 4 + same palette, same proportions, simplified 5 + --> 6 + 7 + <!-- head --> 8 + <circle cx="32" cy="34" r="28" fill="#2E2545"/> 9 + 10 + <!-- crown tuft --> 11 + <path d="M29 8 L32 0 L35 8Z" fill="#2E2545"/> 12 + 13 + <!-- eyes --> 14 + <circle cx="23" cy="38" r="7" fill="#D4941A"/> 15 + <circle cx="23" cy="38" r="3.8" fill="#1A1225"/> 16 + <circle cx="25" cy="36" r="2" fill="white"/> 17 + 18 + <circle cx="41" cy="38" r="7" fill="#D4941A"/> 19 + <circle cx="41" cy="38" r="3.8" fill="#1A1225"/> 20 + <circle cx="43" cy="36" r="2" fill="white"/> 21 + 22 + <!-- face patch --> 23 + <ellipse cx="32" cy="49" rx="8" ry="5.5" fill="#E8DACA"/> 24 + 25 + <!-- beak --> 26 + <path d="M29 53 L32 60 L35 53" fill="#C4B090"/> 27 + </svg>
+40
docs/rookery-mascot.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 220" width="200" height="220"> 2 + <!-- 3 + rookery mascot — cute rook chick nesting 4 + palette: #2E2545 body, #E8DACA face, #D4941A eyes/nest 5 + head:body ≈ 1:1, eyes low for big-forehead cuteness 6 + passes: silhouette, napkin, favicon tests 7 + --> 8 + 9 + <!-- nest --> 10 + <ellipse cx="100" cy="200" rx="48" ry="13" fill="#D4941A"/> 11 + <path d="M56 198 Q78 190 100 200 Q122 190 144 198" stroke="#A07414" stroke-width="2.5" fill="none" stroke-linecap="round"/> 12 + 13 + <!-- body --> 14 + <ellipse cx="100" cy="155" rx="36" ry="42" fill="#2E2545"/> 15 + 16 + <!-- wings --> 17 + <ellipse cx="66" cy="152" rx="10" ry="22" fill="#3A2F58" transform="rotate(-10, 66, 152)"/> 18 + <ellipse cx="134" cy="152" rx="10" ry="22" fill="#3A2F58" transform="rotate(10, 134, 152)"/> 19 + 20 + <!-- head --> 21 + <circle cx="100" cy="82" r="44" fill="#2E2545"/> 22 + 23 + <!-- crown tuft --> 24 + <path d="M94 42 L100 18 L106 42Z" fill="#2E2545"/> 25 + 26 + <!-- eyes (r=11, ~25% of face width — big and warm) --> 27 + <circle cx="82" cy="92" r="11" fill="#D4941A"/> 28 + <circle cx="82" cy="92" r="6" fill="#1A1225"/> 29 + <circle cx="85" cy="89" r="3" fill="white"/> 30 + 31 + <circle cx="118" cy="92" r="11" fill="#D4941A"/> 32 + <circle cx="118" cy="92" r="6" fill="#1A1225"/> 33 + <circle cx="121" cy="89" r="3" fill="white"/> 34 + 35 + <!-- face patch --> 36 + <ellipse cx="100" cy="108" rx="12" ry="8" fill="#E8DACA"/> 37 + 38 + <!-- beak (warmer shade — reads as distinct from face) --> 39 + <path d="M96 114 L100 124 L104 114" fill="#C4B090"/> 40 + </svg>