How it's built
Every morning this site publishes a briefing that no human read first. An agent collects the day's stories, writes them up, opens a pull request, and merges it. I'm not in the loop.
That's a slightly alarming sentence to write, so most of the engineering here isn't about generating text — it's about deciding what an unattended agent is allowed to put on a public site. This page is the honest version of how that works.
The daily loop
The pipeline runs in four stages: collect leads, write the briefing, gate it, publish it. Each stage assumes the previous one might be wrong.
1. Collect
A dependency-free Node script pulls 84 feeds — vendor blogs, GitHub release feeds for the projects worth watching, targeted arXiv API queries, practitioner blogs, trade press, and Google News query feeds for sources that publish no feed of their own. It groups everything into the nine areas the briefing covers and prints a Markdown digest of the last 24–48 hours.
RSS is doing real work here, not nostalgia: plenty of sites that return 403 to an
automated page fetch will happily serve the same content as plain XML. The digest also
marks any lead whose URL already appeared in a published briefing as [seen],
and prints a per-area lead count — so an area that's starving shows up as a number
instead of just quietly going missing.
What comes out is a lead list, never citable copy. Headlines rot, aggregator summaries are wrong often enough to matter, and a Google News redirect isn't a source. The agent verifies each lead and cites the real article.
2. Write
The agent works from a committed prompt that fixes the topic areas, the research window, and a house style — bullet lengths, a two-bold-span budget, a ban on stock connectors like "it matters because," and a balance rule so no single vendor takes more than about a third of a day's bullets. It deduplicates against the previous days' briefings by canonical URL first, vendor and substance second. If nothing genuinely new is left, it skips the day rather than padding one out.
3. Gate
This is the part I'd actually defend in a code review. The prompt rules above share a context window with untrusted web content, so they can't be the only defense — a prompt is a preference, not a control. The real guardrails are deterministic and live outside the model, in a validator that runs both as a required status check and again inside the merge workflow. It rejects a briefing that has:
- raw HTML, script tags, or event handlers in the body — stored XSS,
- token- or secret-shaped strings anywhere in the file — credential exfiltration by way of a committed file,
- more than 40 KB or 60 links — a blast-radius bound,
- any link that isn't https to one of 133 allow-listed domains (95 primary and official sources, 38 reputable press),
- section headings outside the known set, or house-style violations it can count — summary and bullet word caps, bold-span density, retired connectors.
The allow-list is the load-bearing one, and it's deliberately a boring flat file: adding a source is a one-line pull request I review. Cite anything else and the briefing simply doesn't publish. User-content platforms, social posts, and AI-generated content farms are excluded on purpose — the failure mode I care about is a laundered claim arriving through a plausible-looking URL.
The list is not frozen, though, and this is the part I'd point at if I had to defend one decision. A weekly routine can propose entries — add a source, drop one that's gone dead — as data, never as logic. What it cannot do is land the change: that diff touches a script, the path check refuses it, and the pull request comes to me. Proposing a rule and authorising one are separate permissions, which is what lets the rules keep evolving without the agent being the one who ratifies them.
Two more layers sit behind it. The site strips raw HTML at render time, so even a file that somehow reached the main branch can't emit active content. And the auto-merge workflow re-reads the diff: if a labelled pull request touches anything other than a briefing file — a prompt, a layout, a workflow, any code — it removes its own merge label, comments, and stops. The agent can publish news. It cannot change the rules it publishes under.
4. Publish
The briefing lands as a pull request against the main branch. A second pass adds plain-language context, then applies the label that lets the merge proceed — that ordering is deliberate, so the merge waits for the enrichment instead of racing it. The merge squashes, deletes the branch, and dispatches the deploy.
The parts that surprised me
Dedup has to be a publishing property, not a memory one. The agent dedups against briefings already merged, which means yesterday's pull request has to be merged before today's run — otherwise the same story is "new" twice. Auto-merge started as a convenience and turned out to be a correctness requirement.
Generated prose drifts, and it drifts slowly. Any single briefing looks fine; it's across a couple of weeks that you notice the same three sentence shapes and the same vendor leading every day. So there's a weekly routine that reads the week's output, re-derives the biggest stories independently to catch what the feeds missed entirely, checks whether one vendor is over-represented, and proposes changes to the search queries, the style guidance, and the source lists. It's capped at a handful of changes a week, it may not touch the logic that judges a briefing, and — as above — it merges nothing.
The tedious checks are the ones that pay. Nothing about the validator is clever. It's a few hundred lines of string matching, and it's the only reason I'm willing to leave the whole thing unattended.
Stack
Astro with Tailwind, statically generated and deployed to GitHub Pages. Content lives in Markdown content collections with Zod-validated frontmatter — one file per day, named by date. The scripts are plain Node with no dependencies, which keeps the gate easy to audit and impossible to break with a bad lockfile update. Claude Code does the writing, driven by prompts committed alongside the content they produce, and the whole thing runs on GitHub Actions.
What it doesn't do
It doesn't fact-check. The gate constrains where a claim may come from and what shape it may take, not whether it's true — a trusted source that's wrong produces a briefing that's wrong. It doesn't rank stories by importance beyond the prompt's judgement, and it has no idea which of them you'll care about. Every item carries a link to the original, and that link is the part to trust.
The daily briefings are the output. The about page covers what I curate and what I don't.