The whole thing runs on a deliberately small stack: a static front end and one edge function, with a database at the edge, plus an offline pipeline that builds the corpus when it is rebuilt.
The site is a Cloudflare Pages project in advanced mode: a single
_worker.js handles /api/* and everything else serves
static assets. The worker runs at the edge, close to you, so a tap is fast.
State lives in Cloudflare D1 (SQLite at the edge): the event corpus, the
feedback log, the chrome-bandit impressions, and the logistic weights. There is
no separate server; the worker reads and writes D1 directly. A service worker caches the app shell so it loads offline, with the
cache name stamped by the deploy's git commit so a new release refreshes it.
This sits inside Cloudflare's free tier: roughly 100k worker requests/day and millions of D1 row reads/day, with no overage billing. Exceed the free limits and requests are throttled, not charged.
Source lives in a private GitHub repository. The pipeline scripts,
the worker, the schema, and the front end are all versioned; deploys are a
single wrangler pages deploy.
Today the story text is the raw event headline from GDELT, stored as-is. A second, optional step is built but not yet switched on: an offline pass over Fireworks AI (an OpenAI-compatible inference endpoint) that filters out non-tragedies (film and book reviews leak into the disaster themes) and rewrites each kept headline into one calm factual sentence. It is designed to run offline, once when the corpus is built, never on the hot path, so a tap would still cost no model inference. Until it is enabled, none runs: the live cost of a tap is a database query, a logistic score, and an inverse-gap draw.
Pre-generating the stories and pushing the learning into a tiny logistic model plus one exploration knob keeps the request path to a single edge function and one database. No GPU at request time, no external API on the hot path, no servers to keep alive.