Architecture in one breath: at build time (Node) the corpus is parsed into 108 structure-aware chunks, embedded with all-MiniLM-L6-v2 (384-dim, L2-normalized) into one static JSON. At runtime the browser embeds your question with the same model via Transformers.js and scores it. No servers, no API keys, no database. The Aug upgrade added hybrid retrieval + a reranker with a measured eval.
BUILD TIME · Node.js RUNTIME · your browser
────────────────────── ────────────────────────
founding texts your question
│ │
build-corpus.js Transformers.js
→ 108 structure-aware chunks all-MiniLM-L6-v2
│ (quantized ONNX, web worker)
build-embeddings.js │
→ all-MiniLM-L6-v2, L2-norm query vector (384-dim)
│ │
index.json ───────────────────────► ┌──────────────────────────────┐
one 451 KB static file │ dense · BM25 · cross-encoder │ 3 signals
no server / no DB / no key └───────────────┬──────────────┘
│
top passages + exact citations
+ curated answer (cosine ≥ 0.60)
+ optional on-device synthesis (LaMini-77M)
Walk me through the retrieval pipeline end to end.
Build time: parse founding docs into 108 chunks along their own semantic seams → embed each with all-MiniLM-L6-v2 → L2-normalize → ship as a 451 KB JSON. Runtime: the browser loads the same quantized ONNX model, embeds the query, and scores it. The upgraded path runs three signals — dense vector similarity, BM25 keyword, and a cross-encoder reranker — then returns top passages with citations.
Why structure-aware chunking instead of fixed token windows?
Founding documents carry their own boundaries: each grievance is one accusation, each amendment one right, each section one power. Chunking on those seams means every chunk is a complete thought with a citation a human recognizes — "Amendment IV" with full text, not "chunk 47 starting mid-sentence." The transferable rule: find the document's atomic unit first — contracts have clauses, API docs have endpoints, runbooks have steps.
Why L2-normalize the embeddings?
Once every vector is unit length, cosine similarity reduces to a plain dot product — no arccos, no division at query time. Search becomes a single multiply-and-add loop.
How does search actually run, and how fast is it?
108 passages × 384 dimensions = 41,472 multiplications — under a millisecond on any device. A sorted array is the right structure when the corpus fits in memory; no vector database and no approximate-nearest-neighbor index needed. You reach for ANN/HNSW only once the corpus outgrows memory.
What is the "three-signal" retrieval, and why hybrid?
Dense vectors catch meaning, BM25 catches exact terms (names, numbers), and a ms-marco-MiniLM cross-encoder reranks the merged candidates by scoring query-passage pairs directly. Hybrid + rerank beats any single signal on recall — dense alone misses exact-term matches, keyword alone misses paraphrases.
How do you know retrieval got better — did you measure it?
Yes — there's an eval harness (eval/run-eval.js) over a labeled question set. The upgrade moved measured recall from 93.3% to 97.8%, with the multi-hop slice at 100%. I also tested a bigger embedding model (bge-base) and rejected it on the numbers — measured, not assumed. "I measured and rejected an option" is the senior signal.
How do you prevent hallucination?
In the curated path the AI only does retrieval and question-matching — the short answers and 108 passage explainers are hand-written at build time, so nothing generates text from scratch. The optional browser-side synthesis model (LaMini-Flan-T5-77M) is constrained to the retrieved passages as its only input, so it can't introduce facts that aren't there, and its output is labeled "computed."
What's the semantic router / curated-answer layer?
27 curated Q&A pairs embedded alongside the corpus. The query is scored against them; above a 0.60 cosine threshold a human-written modern-English short answer shows above the founders' passages. The threshold is empirical: paraphrases of covered questions score 0.63–0.84, off-topic queries stay under 0.20, so the gap kills false positives.
How does browser-side inference work without freezing the UI?
Transformers.js runs a quantized ONNX build of the model (22.9 MB), cached in the browser's Cache API after the first visit. The key line is env.backends.onnx.wasm.proxy = true, which moves inference into a web worker. Without it, ONNX ran on the main thread and blocked for the whole embedding pass.
Tell me about a performance problem you debugged.
Lighthouse mobile the night before launch. First, render-blocking Google Fonts (fixed with the media="print" / onload swap). That exposed a second issue: Total Blocking Time jumped to 4,500 ms because the blank screen had been hiding ONNX inference on the main thread. Moving inference to a worker dropped TBT under 10 ms. Lesson: optimizing one metric can reveal another — sequence matters.
What's the cost model at scale?
Marginal cost per query is zero, at 10 users or 10 million, because each visitor brings their own compute. The heavy bytes (22.9 MB model, ~11 MB runtime) come from third-party CDNs; the Vercel origin serves ~600 KB per cold visit, and the model caches after first load. Free tier covers ~170,000 cold visits/month.
When would you NOT use this architecture?
Private data (the whole index ships to every visitor), large corpora (the index download outgrows its welcome), or when you need generated prose of real quality (a 77M-param browser model isn't GPT-4). It's for small, public, read-heavy corpora — docs, policy texts, FAQs. Matching the tool to the problem is the actual engineering judgment.