The relay is a Cloudflare Worker under relay/. It accepts measurement rows
from installs over NDJSON at POST /pool/v1/rows, folds each install's rows
into per-install running totals ("sheets") in KV, and once an hour publishes a
signed, versioned JSON index that installs read. It serves, all under the
/pool/ prefix:
POST /pool/v1/rows— submit rows. Requires theX-Codeaf-Install: <32 lowercase hex>header; the body is at most 256 KiB and at most 200 lines.GET /pool/index.json— the published index document.GET /pool/index.json.sig— its detached Ed25519 signature.GET /pool/healthz— a liveness probe.
Every other path answers 404 with an empty body. The document is signed with
Ed25519: the Worker holds the 32-byte private seed as the secret
POOL_SIGNING_KEY (standard base64) and the 32-byte public key as the plain var
POOL_PUBLIC_KEY (standard base64). Installs read
https://codeaf.agentfield.ai/pool by default; CODEAF_MODEL_POOL_RELAY_URL
points an install elsewhere and CODEAF_MODEL_POOL_PUBLIC_KEY sets the trusted
key.
-
Have a Cloudflare account with Workers & Pages enabled.
-
npm i -g wrangler && wrangler login. -
cd relay && wrangler kv namespace create POOL, then paste the returned id into the[[kv_namespaces]] idinwrangler.toml. -
Generate the signing key once, off the repository, and set the secret:
openssl genpkey -algorithm ed25519 -out pool-ed25519.pem- raw 32-byte seed, standard base64:
openssl pkey -in pool-ed25519.pem -outform DER | tail -c 32 | base64 - raw 32-byte public key, standard base64:
openssl pkey -in pool-ed25519.pem -pubout -outform DER | tail -c 32 | base64 wrangler secret put POOL_SIGNING_KEYreads the seed from stdin, so pipe it in:openssl pkey -in pool-ed25519.pem -outform DER | tail -c 32 | base64 | wrangler secret put POOL_SIGNING_KEY- put the public key into
[vars] POOL_PUBLIC_KEYinwrangler.toml. Never write the seed anywhere in the repository.
-
wrangler deploy. -
DNS and the route: create a proxied DNS record for
codeaf.agentfield.ai(AAAA100::or A192.0.2.1, proxied) so the route can attach. The route inwrangler.tomlbindscodeaf.agentfield.ai/pool/*to the Worker. Installs readhttps://codeaf.agentfield.ai/poolby default, andCODEAF_MODEL_POOL_RELAY_URLpoints an install elsewhere. -
First publication:
wrangler triggers deployand wait for the next:17, or, underwrangler dev --test-scheduled, drive it by hand withcurl -X POST "http://localhost:8787/__scheduled". Publishing does not depend on the cron: the first read past PUBLISH_EVERY republishes in the background while serving what is stored, so the cron is a floor rather than a requirement, and step 7a below is only for a store you want filled before anyone reads. 7a. If the store is still empty forty minutes after the first:17(a fresh Worker's cron can miss its first tick), trigger the scheduled handler once by hand:wrangler dev --test-scheduledbound to the remote namespace, thencurl "http://localhost:8787/__scheduled?cron=17+*+*+*+*"; the next:17takes over from there. The same hand trigger is how to publish at once after loading rows (relay/tools/prime.py, or any large batch): a read republishes only when the stored document is older than PUBLISH_EVERY, so fresh rows behind a young document wait up to an hour otherwise. -
Verify from a laptop:
curl -sI https://codeaf.agentfield.ai/pool/index.json(expect 200 and an ETag), thencodeaf pool verify, thencodeaf pool status. -
Know what the store holds — one running total per install, cell and day; a per-install per-day quota counter; the nonces it has folded for that install and day (
seen/<install>/<day>, one week's TTL); and the last index document and its signature — and how to wipe it:wrangler kv key listto see the keys andwrangler kv key delete <key>to drop one.What a batch costs the meter: one KV read and one KV write for each distinct (day, cell) it folds into, one read per day the batch carries (that day's seen set), and one read and one write per day it has fresh rows on (that day's seen set and its quota counter) — neither scaling with the row count, so a 200-row batch over ten cells on one day is 12 writes where one write per row was 200. The relay is deployed on a plan whose KV is metered, not capped, so the free tier's daily ceilings (1,000 KV writes and 100,000 KV reads) do not apply; the operator keeps the plan's name and price out of this file. The day's seen set is at most
ROWS_PER_INSTALL_PER_DAYnonces of 32 hex characters plus a separator — 16,500 bytes at the default 500, against KV's 25 MiB value limit — so a plan that raises the quota past what one value holds is refused rather than allowed to drop nonces. -
Rotating the signing key: generate a new seed, add the new public key to the binary's trusted key list, then redeploy with the new secret.
-
Keep the seed index the binary carries faithful to the relay. Before a release, run
go run ./internal/pool/index/cmd/seedgen -checkfrom the repository root: it fetches the signed index, verifies it, and diffs it againstinternal/pool/index/seed.jsonanddocs/design/model-pool/data/seed-cells.csv, exiting non-zero when either has drifted. Review the diff, then re-run without-checkto write both, and let the regenerated files ride the release's pull request and change note. The command checks signatures under the key the build carries; pass-key <base64>(repeatable) for a relay of your own, and-url/-mirrorto point it elsewhere. -
Refuse a fixture vendor at the door: set
ALLOWED_VENDORSin[vars]to a comma-separated list of the vendors the relay accepts on a row'smodelandjudge(for exampleALLOWED_VENDORS = "z-ai,anthropic"). Unset or blank, every vendor passes as it always did. Set it, and a row whose model or judge vendor is not named is refused, with a message naming the field, before anything is stored. Nothing inrelay/names the vendors the pool serves, so read them off the smallest source of truth there is: the published index itself, whose cells'modelvendors andjudgesvendors are exactly the vendors the pool has served. -
Purge fixture-vendor rows already stored.
relay/tools/purge.jslists thesheet/keys whose model or judge vendor is named and deletes them, drivingwrangler kv key listandwrangler kv key deleteagainst thePOOLbinding. Dry-run it first, then delete, then trigger a publish (step 7a) so the index drops the purged judge:cd relay node tools/purge.js --vendor crew --vendor other --dry-run # prints the keys node tools/purge.js --vendor crew --vendor other # deletes them, prints the countThe purge reads only the
sheet/prefix, so everyseen/and quota key is left alone, and the published judge list is derived from the stored keys — deleting them is what removes a judge from the next publish.
wrangler.toml carries the dashboard's observability settings (invocation
logs and traces, persisted, sampled at 100%) so a redeploy keeps them. The
Worker writes no log line of its own; what the platform persists is its
invocation record: route, method, status, timing and request metadata, which
includes the caller's network address and the X-Codeaf-Install header for
the platform's retention period. That record is the one place a row can be
tied to an address. A relay that should keep none sets persist = false or
head_sampling_rate = 0 in both blocks.
Judge severity and the primed rows. The index's role_quality cells come
out of a per-judge severity fit (relay/src/sheet.js): every judge's scores
are shifted by a fitted β before they are pooled, so a judge who scores high
or low on everything is taken out of the published means. The priming script
(relay/tools/prime.py) posts every seat as exactly 0 or 100 under one judge
id, so in that fit the primed judge's rows carry only the two ends of the
rubric. For a judge whose rows are all primed, with n_prime rows on cells
the other judges hold n_other rows of at weighted mean c, the stationary
fit puts its severity at β = n_other · (x̄ − c) / (n_prime + n_other), where
x̄ is the primed rows' own mean (100 × the pass rate), and moves the centre
of every cell it shares by n_prime / (n_prime + n_other) of the gap toward
x̄. As a worked number: n_prime = 385 rows, all failures (x̄ = 0),
against n_other = 600 rows at c = 88 gives
β = 600 · (0 − 88) / 985 ≈ −53.6 — and the fit holds every severity inside
±10 (a judge further off than a tenth of the rubric's width is a different
rubric, not a severity), with each adjusted score clamped back into
[0, 100], so a cell that judge scores alone shifts by at most 10 points
either way. An operator weighing a purge can read the size of the pull from
the same formula with their own n_prime and n_other.
Generate your own keypair with the same openssl lines as step 4, then
wrangler deploy to your own account with your own route or the workers.dev
address, wrangler secret put POOL_SIGNING_KEY to set the seed, and your own
POOL_PUBLIC_KEY in your own wrangler.toml. Point installs at it with
CODEAF_MODEL_POOL_RELAY_URL and CODEAF_MODEL_POOL_PUBLIC_KEY (the trusted
key).
The mirror workflow is optional; it takes the same two values as its workflow
env. relay/ holds no secret and no value that is not documented in
wrangler.toml or this runbook — the quota, the min installs and the cron — so
a copy of the directory is a complete relay.
- Actions: enable Actions on the repository if it is disabled.
- Workflow permissions: under Settings → Actions → General → Workflow permissions, set "Read and write", which the mirror needs to push the data branch.
- Run it once by hand: the workflow can be dispatched only once its file
is on the default branch, so the first run waits for the branch that carries
it to merge; until then the mirror address answers 404, which the client
treats as one more unreachable mirror and reads its cache or the seed.
Then dispatch the mirror workflow with
workflow_dispatch. - Confirm it worked: a commit appears on the
model-poolbranch.