Cell is the SI guide in the corner of every page. It runs on Cloudflare’s Free plan and has never sent me a bill. It meters every question before it spends anything, does its heavy work in a Durable Object, and since October 2026 answers with Gemma 4 26B, which was charged about a quarter of the neurons Llama 3.3 70B was in the same 15-question eval.
Engineering
How Cell Runs on Cloudflare's Free Plan
How I added an SI guide to my site without a paid plan: a budget that fails closed, a Durable Object for the heavy work, and an edge Worker near 1ms of CPU.
On this page 27 sections
Llama stays behind it as a fallback. This is how each piece works, what changed from the first version, and what it took to move to the cheaper model.
What Cell is
Cell is the small NAND flash cell in the corner of every page. You ask it an SSD question and it answers from this site’s own writing, cites the pages it used, and says so when it falls back on general knowledge. The rest of the site is static files, so Cell is the only part that needs a server.
I gave myself one constraint: it has to run on the Cloudflare Free plan and never send me a bill. That constraint shaped everything else. Free gives you three limits that matter here.
- Each Worker request gets 10ms of CPU.
- The whole account gets 10,000 Workers AI neurons per UTC day. After that, every SI call fails. Free is never billed, so the risk is not cost. The risk is that the chat dies at 9 a.m. and stays dead.
- The account can serve 100,000 Worker requests a day.
What Cell is
Three Free plan limits set the shape of Cell
The shape of it
Each question travels through three pieces:
- The page sends the conversation and its own path to
eyesonflash.com/api/assistant/chat. The Worker is routed on the site’s own domain, so the Content Security Policy keepsconnect-src 'self'and there is no CORS to configure. - The edge Worker does the cheap checks: origin, configuration, rate limit, body size and validation.
- A Durable Object named Cell does the expensive work. It searches the corpus, charges the budget, calls the model and streams the reply back through the Worker.
The shape of it
A question passes three pieces before it reaches the model
That split exists because of the 10ms limit. Searching the whole site and streaming a model reply take far more than that. On one live question, Cell’s Durable Object used 604ms of CPU, so a Free Worker would be cut off. A Durable Object request gets 30 seconds of CPU by default. The edge Worker measures about 1ms per question, well inside its allowance.
One global Durable Object has a second benefit. Every question in the world passes through the same instance, so the daily counter is exact, and the search index stays warm between questions.
Where the answers come from
Each build generates /assistant-corpus.json from the guides, articles and project pages. Each chunk links to a real heading anchor. Cell fetches that file, indexes it with MiniSearch and searches on the latest question plus the one before it, so a follow-up such as “and how does that age?” keeps its topic. Results from the page the reader is on get a boost.
Where the answers come from
Every answer is built from this site’s own pages
My prompt tells the model to cite sources as [n], to use the site’s own wording when a source defines a term, to flag general knowledge as such, and never to invent my opinions or numbers. The client renders citations as links, and it only accepts same-site paths.
Counting neurons before spending them
Workers AI bills in neurons, and the daily allowance is small, so the Worker meters itself. Before every model call, Cell estimates the worst-case cost and charges it against a daily budget kept in the Durable Object’s storage. If the charge doesn’t fit, the model is never called and the visitor sees “resting until tomorrow”.
Each estimate is an upper bound built from the real prompt:
- Input tokens are the prompt length divided by 3 characters per token, plus a fixed allowance for each message’s chat-template framing. English text averages nearer 4 characters per token, so this over-counts on purpose.
- Output is charged as the full reply limit, whether or not the model uses it. That limit was always 450 tokens until a Clef screen started sizing it per question.
- Rates come from Cloudflare’s pricing page, per million tokens. Gemma 4 26B costs 9,091 neurons for input and 27,273 for output. Llama 3.3 70B, the fallback, costs 26,668 and 204,805.
A prompt of about 1,500 tokens on Gemma works out to roughly 14 neurons in and 12 out, so about 26 in total. In the eval I ran on October 26, 2026, Cell charged 31 to 43 neurons for each answered question, Clef screen included. Llama was charged 122 to 161 for the same questions that day. My budget is 8,500 of the 10,000, leaving 1,500 for the site’s article-maintenance Worker, so it now covers about 200 to 270 questions a day.
Counting neurons before spending them
A 1,500-token question: about 26 neurons on Gemma, 132 on Llama
Charged before the call: the whole prompt plus the full reply limit, at each model’s rate
Three details keep the meter honest:
The charge is one read-then-write on storage
A Durable Object holds its input gate shut across storage-only awaits, so two questions cannot both read the same balance and overspend.
The counter is one key per day
Each new UTC day’s first charge deletes everything else, so storage stays a single number.
Everything fails closed
Cell returns a 503 instead of making an unmetered call when the AI binding or the budget is missing, when the budget is not a number, or when the model has no rates in neurons.js. Setting the budget to "0" switches the chat off. Switching models can’t bypass the meter, because a new model has to have its rates added first.
There is also a backstop. If Workers AI reports that the daily allocation is used up anyway, Cell treats it as the resting state rather than an error. And once an edge isolate learns the budget is spent, it turns visitors away for the rest of the day without waking the Durable Object.
Keeping strangers from spending it
Any visitor-facing endpoint that spends a shared budget invites abuse, so the edge Worker is strict:
Keeping strangers from spending it
Five guards stand between a stranger and the neuron budget
Origin check
Only requests from the site’s own origins pass. It stops casual cross-site use and nothing more, so I never rely on it as authentication. There is no workers.dev or preview hostname to attack instead.
Per-IP rate limit
Each IP gets 6 questions a minute. IPv6 addresses are keyed by their /64, because a single visitor often controls a whole /64 and would otherwise rotate through addresses.
A Cloudflare rate-limiting rule
A Cloudflare rate-limiting rule (“Cell chat and MCP flood limit”) blocks more than 6 requests in 10 seconds from one address on the chat path before they count as Worker requests. Free allows one such rule, so since November 2026 the same rule also covers the site’s MCP server.
Bounded input
Cell reads the body as a stream and abandons it past 32KB. Questions stop at 500 characters and conversations at 8 turns and 6,000 characters. Only the page path is accepted from the browser’s page context. Page titles come from the corpus, so the client cannot write text into the system prompt.
Old assistant messages come back from the browser, so they can be forged. My prompt is followed by a reminder of the rules, and every chunk of source text has its angle brackets neutralized so it cannot close the <sources> block. A leak guard watches the reply as it streams. If the model starts reproducing distinctive phrases from its own prompt, the stream is cut and the client replaces what it showed with a short in-character refusal.
Privacy
Message content is never logged, only turn counts, source counts, the neuron estimate and timings. The conversation lives in the visitor’s sessionStorage, and tip preferences live in localStorage. The questions themselves do go to the Worker and to Workers AI to produce a reply.
Privacy
Logs keep counts and timings, never the words you type
Testing a chatbot
Two kinds of tests cover it. The Worker’s validation, budget, neuron math, streaming and hardening have Node tests that run with the rest of the site’s suite, using fakes for the AI binding and the Durable Object. Those catch arithmetic and plumbing mistakes without spending a neuron, and since the model change they replay real Workers AI streams I recorded.
My eval script, npm run eval:assistant, does the rest. It sends 19 questions to a live endpoint: five the site can answer, five general SSD questions, three off-topic requests, two jailbreak attempts and four questions readers asked that Cell got wrong. One jailbreak comes with a forged history. It flags missing citations and leaked prompt text, a reader question that does not cite the page that answers it, a known fact stated wrong, and sign-off lines. It spaces the requests to respect the rate limit. When it had 15 questions, one run on each model on October 26 charged 388 neurons on Gemma and 1,569 on Llama, and on Llama one more question got past the screen to the model. My run of all 19 on Gemma on November 5 charged 562. The flags are only a heuristic, so I read the answers myself after any change to the prompt, the model or the corpus.
Testing a chatbot
One eval run charged 388 neurons on Gemma and 1,569 on Llama
Our measurement: npm run eval:assistant, 15 questions, October 26, 2026, Clef screen included
What changed from the first version
The version described above grew out of a first one. Two things changed once I checked it against the Free plan’s numbers.
What changed from the first version
The first cap let Cell spend 13 times the free neurons
A daily cap of 1,000 questions at about 130 neurons each on Llama 3.3 70B
It counted questions, not neurons
My first budget was a daily question cap, defaulting to 1,000. That looks like a limit, but a question cost about 130 neurons on Llama, so 1,000 of them is about 130,000 neurons against an allowance of 10,000. The cap could never have protected the account, because the allowance would have run out long before the 1,000th question, and every SI call after that would have failed. The fix was to stop counting questions and count the thing Cloudflare actually meters: neurons, estimated from the real prompt before each call.
It did all the work in the edge Worker
Validation, corpus search, the budget check and the streamed reply all ran in one Worker. That’s fine on a paid plan, but indexing the corpus and streaming a reply take far more CPU than the ten milliseconds a Free request gets. Moving that work into a Durable Object took the edge Worker down to about 1ms per question.
That move had a side effect. My first design used one Durable Object for the budget and another for search. Once the work lived in a single object, both were redundant, so they merged into Cell. Migrations are append-only, so the config still records all of it. It shows two classes created, then both deleted and Cell created in their place. It also let me drop the CORS and preflight handling, since a same-origin route never needs it.
Retrieval took some tuning too, and the code comments record why:
- Very short terms were matching everything. The “s” left over from “what’s” prefix-matched everything, so terms under two characters are ignored and prefix matching needs three.
- Fuzzy matching turned “hire” into “wire”. It now applies only to longer terms, and only loosely.
- Numbers are kept at any length, or “part 8” cannot be told from “part 9”.
- Two neighboring chunks can each run past a thousand characters, so the limit on merged text had to fit both. Otherwise a section’s definition got dropped just when it was needed.
- Readers type names with a space. “storage NEXT” searched for “storage” and “next” separately, and Part 8, “Where Memory Goes Next”, outranked the Storage-Next page. A hyphenated name such as Storage-Next or XL-FLASH is now also indexed as one joined word, and two neighboring words in a question are joined the same way, so the page that carries the name comes first.
One testing change was structural. Cell is a plain class with a fetch method rather than extending Cloudflare’s base class, so the tests can import it under Node without the Workers runtime. That is why the tests can cover the budget and the streaming path with fakes and no deployed Worker.
Moving to a cheaper model
For its first month, Cell answered with Llama 3.3 70B. Gemma 4 26B looked like a way out. Cloudflare prices its input at about a third of Llama’s and its output at about an eighth. I tried GLM 4.7 Flash beside it. In my first eval with them, every question that reached either model came back empty, with no error.
Moving to a cheaper model
With thinking on, the whole 300-token reply went to reasoning
Our measurement: raw Workers AI streams for one test question, October 26, 2026
Reasoning models think first
Both are reasoning models. I recorded their raw streams, and with a 300-token reply limit each spent all 300 tokens on its reasoning field and stopped at the limit before writing a word of answer. Cell’s reader already dropped that field, which is right, because thinking must never reach the visitor or the leak guard. The call was what had to change. For those two models only, Cell now sets the chat template’s switch that turns thinking off, and the same eval passed 15 of 15 on Gemma.
A fallback that pays its own way
Cloudflare could stop honoring that switch, so Llama 3.3 70B now stands behind Gemma. If a Gemma reply ends with no answer text, Cell charges the budget for one Llama call and asks the same prompt again. Past the budget, or if that call fails, the visitor gets the empty reply as before. A visitor who closes the tab while Gemma is still thinking costs no fallback call. In the eval it never fired, because every Gemma reply carried an answer.
A cheaper model that got facts wrong
In November 2026 I tried Qwen3 30B. On Cloudflare’s pricing page its input costs 4,625 neurons per million tokens, about half Gemma’s 9,091, and its output costs 30,475, a little more than Gemma’s 27,273. In my runs of the 19-question eval on November 5, it was charged 425 neurons, Clef screen included, against 562 for Gemma, and it passed every automatic check. Reading the answers changed the verdict. It said PCIe Gen5 runs at 16 GT/s per lane, citing my PCIe generations page, which gives 16 GT/s for Gen4. PCI-SIG gives 32 GT/s for Gen5. It also said QLC is “faster to read in some cases” than TLC, where the QLC page it cited says QLC’s longest array read takes about twice as long, and it said TRIM erases blocks. Gemma got all three right, so Gemma stays, and the PCIe question now fails the eval unless the answer says 32 GT/s.
The same reading caught something in Gemma. Its one allowed pun per reply had become a filler sentence such as “It is important to keep your WAF low so you don’t lose your charge too quickly”, and it would either cite a source for the pun, as it did there, or tag it as general knowledge, so a reader saw a citation or a flag on a line with no fact in it. Cell has stopped making puns, and its character now stays in its greeting and its plain wording.
What I’d tell someone copying this
What I’d tell someone copying this
Every model call is charged before it runs, the fallback too
Budget the reply, not just the prompt
On Llama 3.3 70B, output tokens cost about 8 times as much as input tokens, so max_tokens is the lever that matters most. On Gemma 4 26B output costs about 3 times input, and a full prompt costs about as much as the reserved reply, so trimming the sources counts as much as the reply limit.
Meter before the call, and fail closed
A counter you check after the fact only reports what was spent. Charge first, and refuse when the charge doesn’t fit.
Move the heavy work to where the CPU limit is generous
Durable Objects fit well if you can live with one global instance.
Keep every miss as a test
When a reader gets a bad answer, the question goes into the eval with the page it should have cited. The next model, prompt or corpus change has to answer it correctly before it ships.
Test a cheaper model on the real stream
Changing the rate in the config takes one line, but the model behind it may not stream the way the old one did. Record its raw output before you trust the eval, and keep the model you trust behind it. Then read every answer, because a wrong number with a citation passes any check that only looks for citations.
Cell will sometimes tell a visitor it is resting until tomorrow, and that is what the budget is for. On the Free plan, the choice is between a guide that stops itself at a budget I set and one that runs into Cloudflare’s wall at some unknown moment. Because every call is charged before it runs, I can try a cheaper model on real traffic with its worst case already capped, and leave the chat running without watching it.
Sources
Primary is the original paper, standard or datasheet. Secondary is a review, a vendor summary or a news report. Measured is my own test data.
Show all 5 sources, 4 primary
- PrimaryCloudflare Workers AI pricing, read 2026-10-26 (publisher page printed to PDF)Cloudflare Docs
- PrimaryCloudflare Workers: LimitsCloudflare Docs
- PrimaryLimitsCloudflare Docs
- PrimaryRate limiting rulesCloudflare Docs
- MeasuredThe author’s own measurements
















































































































