Lab · Project
An FAQ generator that reads the page without obeying it
$0.0035 per page run – and half of it is paid by the model that reads the least
The starting point: text is cheap, trust is expensive
A language model writes twenty FAQ pairs for a product page in under a minute. The problem starts after that. To get the answers right, the model needs company facts – prices, delivery times, warranty terms. To get the questions right, it needs the page text. The moment both sit in the same context window, every foreign page has a voice inside a system that knows company data and is allowed to operate tools.
In agency projects that page text comes out of client CMSs, grown across imports and acquired domains. Reviewing it line by line is not on the table. I can only build the architecture so that it does not matter.
What the workflow does
A schedule starts the run, Mondays at six by default. For every active page the workflow pulls the raw text, extracts candidate questions and key claims from it, plans the answer scope, drafts answers against the approved fact base and writes them to Google Sheets with status=review. At the end an email goes to the editorial team: drafts as a table, open fact gaps as a separate list, plus a preview of the FAQPage JSON-LD.
None of it goes live. Sign-off in the sheet is a separate, manual flow: tick the box, CMS webhook, status=published. The audience is marketing and content management, not engineering.

The architecture: three models that never see each other
The core is Simon Willison’s dual LLM pattern from April 2023. The idea: a privileged model may operate tools but never sees untrusted text. A quarantined model reads the untrusted text but holds no credentials and no data. What passes between them are symbolic variables – placeholders the privileged model forwards without ever reading their contents.
| Role | Sees | Never sees |
|---|---|---|
| Privileged planner | enums, counters, IDs | page text, extracted sentences |
| Quarantined extractor | raw page text | company data |
| Quarantined writer | approved facts, validated extraction | credentials, tools |
| Controller (code) | everything, deterministically | – |
Here the symbolic variables are two sheet columns: candidate_questions_json and key_claims_json. Free text out of quarantine sits there as inert data. The controller forwards it and renders it into the review email; the model in control never reads it. The planner works strictly on topic_class, audience and intents – all enum whitelists – plus counters and IDs. There is simply no channel through which a prepared page can reach it.
Between the models sit three code nodes, gates A through C. They check without a model, and therefore reproducibly: strict JSON schema, enum whitelists, length caps, a ban on URLs and email addresses inside answers, an injection-pattern scan, deduplication. And grounding: every answer has to cite fact IDs from the set it was handed. Cite nothing, get rejected.
That settles the privacy direction as well. The extractor receives zero company data, so a poisoned page has nothing to phish. The writer receives only facts flagged approved=TRUE, and only from the categories the planner selected.
Data model
Google Sheet
├── pages page_id, url, page_type, language, active
├── company_facts fact_id, category, fact_text, source, approved
├── extractions raw signals per run, including quarantine rejections
└── faq_drafts draft, status, fact_ids, flag, reviewer_note
Four tabs, no server, no database. As with the Share of Voice monitor, the sheet is configuration, storage and the editorial team’s working surface at once. The fact categories are a closed list of eleven entries, pricing, security and warranty among them; whatever is not on it, the planner cannot request. A draft moves from review through approved or rejected to published.
The most interesting kind of row carries the flag fact_gap. It appears when a page raises a question for which no approved fact exists in company_facts. Instead of an invented answer, the row holds a work order for the editorial team. After two runs that column reads like an audit of your own knowledge base.
What it costs to run
The table below is an estimate, not a measurement – calculated against a real product page with 691 tokens of usable text, but with a plausibly constructed fact base of twelve entries. One run crosses three models.
| Phase | Model | Input | Output | Cost |
|---|---|---|---|---|
| Extract (quarantined) | gpt-4o-mini | 936 | 296 | $0.00032 |
| Plan (privileged) | claude-sonnet-4.5 | 300 | 56 | $0.00174 |
| Write (quarantined) | gemini-2.5-flash | 804 | 465 | $0.00140 |
| One page run | $0.00346 |
Running the numbers corrected an assumption I had made. At 356 tokens the planner consumes by far the smallest volume and still carries half the cost, because Claude Sonnet 4.5 bills input at twenty times the rate of gpt-4o-mini and ten times the rate of Gemini Flash. Yet all it does is closed classification over enums and counters. A cheaper model covers that job, and the run gets roughly half as expensive.
One pass across a catalogue of 50 to 100 pages therefore costs $0.17 to $0.35. On the default weekly cadence that turns into $0.75 to $1.50 a month; a single page comes to $0.015. Cost is not an argument here, in either direction – the sheet and the review process weigh more than the API bill.
The honest limit
One residual channel remains, and I would rather state it than leave it out. The writer reads candidate questions that came out of quarantine. Across that single hop, a prepared page can still influence how an answer is phrased. It gets nowhere near tools or company data, and publishing is further out of reach still – the channel ends at gate C and at human review.
The pattern protects credentials, tools and data. It does not protect taste.
Where it trips you up
The cost driver sits with the extractor, because up to 15,000 characters of page text go in there. Planner and writer work on compact structures. So if you want to move the bill, trim the page text or swap the extractor model – not the other two.
The enum lists decide whether the output is usable. A fact category missing from the vocabulary does not exist for the planner, and the matching questions end up permanently as fact_gap rows instead of answers. Check the categories against ten real customer questions before you arm the schedule.
And the fact base is handwork. The workflow produces drafts, not truth. Put a stale delivery time into company_facts and the system will produce it in twenty variants, with approved=TRUE behind it.
Effort
Zero to production: roughly three working days for data model, gates, prompts and test runs. Importing the finished workflow takes two to four hours for credentials, the Google Sheet and a first run. The model IDs sit in plain sight in three code nodes; any OpenRouter ID works.
That leaves the question of when this is not worth building. For a handful of pages, assembling the fact base costs more time than writing the FAQ blocks by hand. It starts paying off at the point where the same price has to stay consistent across thirty pages – and from there it also replaces the update run after every change.
It started with a question: may a model read foreign page text without me handing it the company data at the same time? The answer is now an open workflow: FAQ Factory on GitHub, 30 functional nodes, CC BY 4.0. Import it, create a sheet, activate one page, check your inbox on Monday. And if you want to know which questions your customers actually put to the answer engines: write to me.