I replaced the server with a prompt: a self-running AI news digest for $0
· Mick Brzeziński
- AI agents
- Claude Code
- build in public
I built a daily AI news digest, then deleted the server it ran on. The cron daemon is now a prompt, the database is a git branch, and the editorial bills to a subscription I already pay for — $0 on the metered API account.
The whole thing runs as a scheduled Claude Code routine: an ephemeral cloud container wakes once a day, runs the pipeline unattended, deploys to Cloudflare Pages, and commits its history to a GitHub branch so nothing is lost when the container evaporates.
From the outside it’s one clean static page a day — a themed headline, a short overview, 4–8 top stories (each with a technical summary, a “why it matters” line, and verbatim source links), up to 10 quick links, RSS, and an archive.

The core idea: the LLM is the editor, not the reporter
The single most important decision: Claude never reports, it only edits. It works exclusively with articles the pipeline already fetched, and it copies URLs verbatim — it cannot invent a story, a fact, or a link. Every story cites at least one real, fetched article. Feeds are the foundation (structured, free, they never hallucinate).
Cheap deterministic code does the mechanics — fetch, dedup, score, select — and the expensive model does the judgment: which stories matter, how to phrase them, what the take is. That split keeps cost flat and makes the $0 rail viable.
The pipeline, stage by stage
A modular pipeline; each stage is independent and replaceable, exposed both individually
(gonio fetch, gonio digest, gonio build) and as one gonio run.
Fetch reads a source registry, not a scraper. Sources live in config/sources.yaml
— adding one is a YAML edit, never code. ~28 today, weighted by editorial trust:
vendor/lab blogs highest (OpenAI, Anthropic, Google AI, DeepMind, Hugging Face), then
big-tech infra, technical newsletters (SemiAnalysis, Simon Willison, Latent Space,
Interconnects), tech press, and community signal (Hacker News, r/LocalLLaMA). A new
fetcher type is one file with a @register("type") decorator. Requests go through a
shared fetchclient that tries a cheap headers-only GET first and escalates to a
residential proxy on a 403/429; a broken feed logs and is skipped, never fatal. Dedup is
a two-layer pass in SQLite.
Cluster is a wide pass with Haiku: lexical dedup can’t tell that “OpenAI launches
GPT-5” and “GPT-5 is here” are the same event, so Haiku groups the day’s articles by
concrete event (same release, same paper; when unsure, keep separate). Two details worth
stealing: a _repair step re-checks the model’s grouping in code — never trust its
bookkeeping — and any error fails open to one-article-per-cluster, so the pipeline never
dies because triage hiccuped.
Rank is deliberately boring arithmetic, no ML:
score = source_weight + recency_decay + keyword_boosts
Source weight from the YAML, linear recency decay over 48h, and boosts for high-signal technical terms. The clever bit: it scores whole event clusters, not individual articles (with a small multi-source corroboration bonus, capped at 5 links per event), so each prompt slot is a distinct story and one hot event can’t swallow the day.
Enrich happens after ranking, only for the handful of articles that made the prompt:
the pipeline fetches the full article body (trafilatura), because feed summaries are
often one line and empty for Hacker News. A dozen requests a day, not one per feed item —
and it fails open: a dead link just falls back to the feed summary.
Digest is the one expensive call. Opus gets the selected clusters and a tight
system prompt for the voice — technical, concrete, skeptical of marketing, dry, never
clickbait — plus hard rules: work only with the provided articles, copy URLs exactly,
4–8 stories, drop low-signal items. It returns a Digest Pydantic model, a structured
output, so it can’t return something unrenderable. A thin-day guard skips the call
entirely if the 24h window has fewer than 10 articles: a quiet day is a free no-op, not a
failure.
Build renders Jinja templates to static HTML, RSS, and a sitemap — and also emits
agent-readable projections: /llms.txt, /latest.md, /md/<day>.md, /api/*.json.
The digest is already structured, so HTML is just one projection; handing agents markdown
and JSON costs nothing and pays off as more readers are machines.
The serverless trinity: GitHub + Routines + Cloudflare Pages
The pipeline used to run on a VPS with Docker Compose. It now runs on three free surfaces, none of them a server you operate:
- Routines — the cron daemon is now a prompt. A scheduled Claude Code routine is an
ephemeral container that runs daily (~06:40 UTC, after EU/US feeds update) with your
repo cloned in. The “program” is a prompt: the operational runbook lives in
ROUTINE.mdand gets pasted in. The orchestration logic is natural language, version-controlled, executed by an agent that can reason about failures rather than just exit non-zero. - Cloudflare Pages — serving. Deployed via Direct Upload (wrangler), not CF’s Git integration — token-driven, no dashboard step. Free tier, global CDN, HTTPS, custom domain one CNAME away.
- GitHub — durable memory. The container is ephemeral; when the routine finishes, the
SQLite DB is thrown away. So history lives on a
gh-pagesbranch used as a private store (read from git, not HTTP): one digest JSON per day, the raw article corpus with full bodies, and an audit log. Each run rehydrates it at startup and re-snapshots it at the end as one force-pushed commit.

Two ordering rules carry the design. Persist before you publish: history is committed first, deployed second, so a failed deploy can’t lose data. And keep the private corpus off the public CDN: full article bodies stay on the private branch and never reach the site dir, so the CDN serves the digest and links while the raw text stays private.
The $0 question: subscription vs API
There are two billing rails behind one interface. The default is subscription; the
paid API rail is opt-in, so a stock run cannot accidentally spend API tokens.
| Rail | How | Billed to |
|---|---|---|
| api | anthropic.Anthropic().messages.parse | Per token, your ANTHROPIC_API_KEY |
| subscription | claude_agent_sdk.query with structured outputs | Your Claude plan’s usage — $0 on the metered API account |
You can’t shortcut this by using a Pro/Max token as an API key: a subscription token is
inference-only, the Messages API rejects it, and as of February 2026 Anthropic prohibits
using OAuth from Free/Pro/Max plans in third-party integrations outside Claude Code and
Claude.ai. What is sanctioned: the routine already runs on your subscription (Claude
Code on the web is Anthropic’s first-party harness, not the third-party kind the ban
targeted), and Agent SDK usage draws from your normal subscription limits, not the metered
API. So the generation runs through the Agent SDK with
output_format={"type":"json_schema"}, which keeps schema validation — and I verified its
output matches the API rail’s in quality.
A moving target (verified June 2026). Anthropic announced that Agent SDK /
claude -pusage would move to a separate non-rollover monthly credit ($20 Pro / $100 Max-5x / $200 Max-20x). The change was paused before it shipped; per Anthropic’s docs it still draws from your subscription’s usage limits. Either way the conclusion holds: generation is $0 on the metered API account, and even at API rates a daily ~$0.12-equivalent run sits far inside the smallest ($20) credit. Re-check before quoting exact numbers.
List prices: Opus 4.8 $5/$25, Sonnet 4.6 $3/$15, Haiku 4.5 $1/$5 per 1M tokens. Two calls a day (Haiku triage + Opus digest), with the real cost logged every run:
| Path | Cost / run | ~ / month |
|---|---|---|
| API key, Opus (with full-text enrichment) | ~$0.12–0.14 | ~$4 |
| API key, Sonnet digest | ~$0.08 | ~$2.5 |
| Subscription rail (default) | $0 API | $0 |
The subscription rail isn’t free of everything — it’s ~2× slower than the API rail and
leans on the Claude Code harness (~40k cached tokens/run, plan-side). On a subscription
error the default rail fails rather than falling back, and the paid fallback only fires
under explicit auto with a key present.
Designing for “no human is online”
Running unattended shapes everything. Never block on questions. Never deploy a partial or empty site — abort only on a real catastrophe like a zero-article run. On any unrecoverable failure the agent diagnoses which stage broke and opens a GitHub issue with the relevant log tail instead of dying silently.
The unglamorous part is encoding expected soft-failures as facts. The residential
proxy is blocked in the routine environment, so two feeds reliably 403 — the prompt says
up front that those two are expected and don’t count, while any other failure is worth a
look. That environment knowledge lives in version control next to the code. And because
the container vanishes, observability is committed too: one JSON line per run in
_data/runs.jsonl (article counts, failed sources, token usage, cost_usd) — a greppable
history with no external logging service.
What this unlocks
The same structured digest becomes many things almost for free. A read-only MCP server
exposes the news DB to other agents — the latest digest, any past day, and full-text
search across the entire fetched corpus, not just the published picks. Because the raw
corpus is persisted, any past day can be re-digested with new logic or a different
model: digests are a derived cache, the corpus is the source of truth. The obvious next
channel — a real email newsletter — is one new module in publish/; auto-posting the top
story to X or LinkedIn is another adapter, on the same $0 rail. Once you have a clean
structured artifact and a serverless publish pipeline, a new channel is a small additive
change, not a new system.
Caveats
- Feeds-only coverage (for now). RSS + Hacker News. A story that only breaks off the source list, or on a site with no feed, isn’t seen. A web-search fetcher is on the roadmap as an additional input.
- The subscription rail is bounded. It draws down your normal subscription usage, and there’s a daily cap on routine runs — fine for one digest a day, not an unlimited compute faucet. Routines + Agent-SDK billing are research-preview and in flux; pin the behaviour you rely on.
- No semantic dedup, single-writer SQLite. Clustering is one Haiku pass over titles —
cheap, but not embeddings; you’d want vector dedup at higher volume. And
db.pyis the only storage-aware module, so it’s swappable, but today it’s a deliberate single-box constraint. - Quality ceiling = model + inputs. The editor only sees what the pre-ranker selected and what enrichment could fetch; a bad selection or a blocked body caps the day.
The real lesson: software without servers
The digest is the product; the reusable part is the operating model — a stateful, scheduled application with no servers, billed to a flat subscription instead of metered API. Swap the domain logic and the same near-zero-cost, self-healing pipeline runs almost any “generate something useful every day and publish it” idea.
Fact-check (verified June 2026): Claude API pricing; the subscription-OAuth ban; the paused June 15 Agent SDK billing change and that Agent SDK usage still draws from subscription limits. Routines and subscription-billing for the Agent SDK are a research-preview, actively-changing area — re-verify before republishing.