Open architecture

How recodeai is built.

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.

The shape Three ideas worth stealing What it costs The daily run How Ask works Known gaps What transfers

The shape

Four stages, once a day, unattended
recodeai architecture Four sequential stages: Sense pulls nine RSS feeds; Curate scores items against a commerce and leadership lens and drafts them with Claude; Publish regenerates the site and passes a 31-assertion gate before committing; Serve delivers static pages from a CDN with one serverless function for the Ask box. DAILY · 04:30 IST · GITHUB ACTIONS RUNTIME · EVERY VISIT 01 SENSE 9 RSS feeds Labs, press, wires agents/config.py Dead feed → skipped 02 CURATE Lens + model Score, then draft research_agent.py No output → no change 03 PUBLISH Build + gate 31 assertions validate-build.js Fail → commit NOTHING 04 SERVE CDN + 1 function 53 static pages api/ask.js Degrades, never errors THE DATABASE IS A FOLDER OF JSON IN GIT data/news.json22 stories data/faq.json51 entries data/sources.json27 verified links data/rag-index.jsonBM25 · 1109 docs · $0 Browser fetches JSON Feed + FAQ render client-side. No framework. POST /api/ask BM25 (free) → below threshold? refuse → else Ollama, temp 0 → verify every citation

Solid outline = deterministic. Accent outline = the gate that can stop a deploy.

01 SENSE

Pull

Nine RSS feeds — labs, tech press, wires. A dead feed is skipped, not fatal.

agents/config.py
02 CURATE

Score, then write

Each item scored against commerce, leadership and technology keyword lenses, then drafted by Claude in house voice.

research_agent.py
ingest-news.py
03 PUBLISH

Regenerate + gate

Site rebuilt from data files. 31 structural assertions run. Any failure aborts the commit entirely.

build-news.js
validate-build.js
04 SERVE

Static + one function

53 pages from a CDN. One serverless function answers questions with citations.

api/ask.js

Three ideas worth stealing

The transferable part

01

Data files are the database

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

A hard gate, not a dashboard

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

Two answer modes, never blurred

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.

What it costs

Per month
LayerProviderCost
Hosting, CDN, functionVercelFree tier
Daily automationGitHub ActionsFree tier
Content generationAnthropic APICents per day
Ask synthesisOllama CloudFree, or $20 flat
DatabaseNone
RetrievalBM25, 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.

The daily run

For architects and devs

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 deploys

If the gate fails, the commit never happens. A bad ingest costs a day of freshness, never a broken site.

How Ask works

And why not embeddings
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 off

Embeddings 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.

Two failures we hit, both instructive

A

Score thresholds do not survive corpus growth

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

Length normalisation beats exact title matches

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.

Known gaps

An honest list

An architecture document that only lists strengths is marketing.

The retrieval index is not rebuilt daily

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.

Lens scoring has no minimum threshold

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.

The knowledge base is 1043 entries, target met

Target met, but every generated entry maps to one of 27 sources by archetype. Defensible, coarse — ~30 domain-specific verified sources would fix it.

Rate limiting is per-instance and in-memory

20 requests per minute per IP, best effort. Adequate now; replace before any volume.

Two design systems coexist

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.

What transfers to an enterprise

Five rules

Most of this does not apply directly at enterprise scale, and pretending otherwise would be dishonest. What does transfer:

  1. Put the gate in the pipeline, not on a dashboard. Autonomous generation without an automated veto eventually ships something you have to apologise for.
  2. Make the AI removable. If the feature breaks when the model is unavailable, you built a dependency, not a capability.
  3. Precompute what you can. Retrieval that costs nothing per query cannot surprise you on the invoice or the latency budget.
  4. Version content like code. Reviewable diffs and git revert beat an admin panel with no audit trail.
  5. Test the refusal path. Everyone tests whether the system answers. Almost nobody tests whether it declines when it should — and that is the failure that damages trust.

01

Argue with it

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.

Tell me where I'm wrong