A daily AI content operation with no servers, no database and no vendor lock-in, running on $0–20 a month. Published so it can be argued with. The first two minutes are for executives; everything after is for the people who have to build it.
Solid outline = deterministic. Accent outline = the gate that can stop a deploy.
Nine RSS feeds — labs, tech press, wires. A dead feed is skipped, not fatal.
agents/config.pyEach item scored against commerce, leadership and technology keyword lenses, then drafted by Claude in house voice.
research_agent.pySite rebuilt from data files. 31 structural assertions run. Any failure aborts the commit entirely.
build-news.js53 pages from a CDN. One serverless function answers questions with citations.
api/ask.js01
Every piece of content is a JSON file in git. No Postgres, no CMS, no admin panel. The consequences are the point: every content change is a reviewable diff, the history is git log, rollback is git revert, and there is nothing to operate at 3am.
This holds to roughly tens of thousands of records. It is not advice for your transaction system — it is advice for your content and knowledge surfaces, where teams routinely over-build.
02
31 structural assertions run after every build and exit non-zero on failure. The daily job then commits nothing and the live site stays on its last good version.
An autonomous pipeline is only safe if it can refuse to ship. Most AI content pipelines have generation and no veto, which is why they eventually publish something requiring an apology.
03
Ask answers from the archive with citations when retrieval finds something, and from general model knowledge — explicitly labelled, uncited, visually distinct — when it does not. Off-topic questions are declined without a model call at all.
Note what that buys: the AI is strictly an enhancement. Remove it and the product still works. That is the inverse of how most AI features get built.
| Layer | Provider | Cost |
|---|---|---|
| Hosting, CDN, function | Vercel | Free tier |
| Daily automation | GitHub Actions | Free tier |
| Content generation | Anthropic API | Cents per day |
| Ask synthesis | Ollama Cloud | Free, or $20 flat |
| Database | — | None |
| Retrieval | BM25, precomputed | $0 |
That last row is the one people miss. Retrieval is lexical and computed at build time, so search costs nothing and cannot drift, rate-limit or bill you. Only the final synthesis step touches a model.
One GitHub Actions job, cron: "0 23 * * *" UTC. A single job owns index.html so the firehose and newsroom writers can never race.
SENSE 9 RSS feeds
CURATE research_agent.py score vs LENS_KEYWORDS
{commerce, leadership, technology}
→ Claude → 10 ticker + 9 cards
→ agents/output/research.json
update_agent.py → regex-replace <section id="firehose">
inside index.html
→ firehose-section-<mmmdd>.html
ingest-news.py RSS → dedupe by normalised title
→ Claude → prepend data/news.json
ingest-explainers.py demand backlog → data/explainers.json
PUBLISH build-news.js → 22 story pages + feed.xml
→ refresh #firehose from snapshot
→ PRESERVE index.html (v2 sentinel)
build-explainers.js → explain/*
generate-sitemap.js → sitemap.xml (46 URLs)
validate-build.js → 31 assertions · HARD GATE
git commit + push → Vercel deploysIf the gate fails, the commit never happens. A bad ingest costs a day of freshness, never a broken site.
POST /api/ask
1. BM25 over data/rag-index.json $0, no network
+ title-match boost (1.6× / 2.2×)
+ coverage gate (≥ 0.5 of query terms)
2. grounded → Ollama, temperature 0, strict grounding
→ validate every citation against the retrieved
set; drop any the model invented
3. no hit, off-topic → decline. No model call. $0.
4. no hit, on-topic → GENERAL answer from model knowledge,
labelled "not from the archive",
zero citations, dashed border in the UI
ASK_ALLOW_GENERAL=false turns this offEmbeddings would retrieve slightly better and cost an API key, a vendor dependency, a rate limit and a rebuild on every content change. BM25 over 1109 documents is computed in Node at build time, ships as a 201 KB gzipped file, and runs in both the function and the browser from the same code path.
A
At 66 documents a BM25 floor of 3.0 rejected nonsense. At 1109 it did not — "my cat is sick" scored 7.51, higher than "Who is Suman Guha?" at 6.52. The fix is term coverage: what fraction of the query's content words actually appear in the top hit. Junk covers about 0.33, real queries 0.50 or more, and coverage is independent of corpus size.
B
A short entry that merely mentioned a subject outranked a longer entry whose title was the actual question. For an FAQ corpus that is wrong, so title containment is boosted explicitly.
Both are the same lesson: relevance thresholds tuned on a small corpus fail silently as it grows. If you run RAG in production, test your refusal path against corpus growth, not just your happy path.
An architecture document that only lists strengths is marketing.
build-rag-index.js is not a step in the workflow. New stories land in data/news.json every night, but the Ask index only rebuilds when someone runs the script. So Ask goes progressively stale against the feed. Fix is one step before the gate.
Items are scored against commerce and leadership keywords, then the top nine are taken regardless of whether anything scored. On a quiet day the slots fill with whatever tech is loudest — which is how a rocket launch and an astrology-app acquisition reached a page written for boards.
Target met, but every generated entry maps to one of 27 sources by archetype. Defensible, coarse — ~30 domain-specific verified sources would fix it.
20 requests per minute per IP, best effort. Adequate now; replace before any volume.
Four new pages use the design system directly; legacy pages are reskinned by remapping 27 CSS variables rather than being rewritten. Deliberate — the memos carry 128 bespoke classes of hand-built visual explainers a blanket rewrite would have destroyed. But it is two systems, not one.
Most of this does not apply directly at enterprise scale, and pretending otherwise would be dishonest. What does transfer:
git revert beat an admin panel with no audit trail.01
The full document, including the repository map, lives in the repo as ARCHITECTURE.md. If you would build any of this differently, that is the conversation worth having.