Put a 26,600-word manuscript through the deployed inference service and the response comes back carrying this, among other fields:
```json "textClassifier": { "modelId": "vanguard-ai-text-detector", "aggregation": "mean-over-windows", "windowsScored": 4, "windowTokens": 512, "tokensTotal": 29260, "tokensScored": 2048 } ```
Two thousand and forty-eight tokens out of twenty-nine thousand two hundred and sixty. Seven percent. The verdict at the top of that report was computed from seven percent of the document, and the other ninety-three percent was accepted, stored, and never shown to the model.
That specific measurement is recorded in `docs/PRICING-BASIS.md`. The block above is the coverage subset of what the service actually returns; the full field also carries the raw score, the threshold it was compared against, any normalisations applied before scoring, and the individual per-window readings.
This post is about why that cap exists, what the four windows do instead of reading the first page, and where you can and cannot currently see the number.
Why there is a cap at all
The model underneath is ModernBERT-large, fine-tuned for AI-text classification. A token, for what follows, is roughly a word-piece — the manuscript above came out at 29,260 tokens for 26,600 words, about 1.1 tokens per word in that one document. ModernBERT accepts up to 8,192 tokens in a single pass, so the architecture is not what stops us at 2,048.
Time is. Measured on an M-series CPU at batch size one, a single forward pass costs 65 ms at 128 tokens, 135 ms at 256, 309 ms at 512, and 852 ms at 1,024. Those numbers are in the source, and the box that serves production is a shared vCPU, which is slower than the machine they were taken on. Cost tracks the same curve: at metered rates a 400-word detection costs roughly $0.0004 in compute.
So the engine fixes `WINDOW_TOKENS = 512` and `MAX_WINDOWS = 4`. Four passes of 512 content tokens, and no more, whatever arrives.
The effect on latency is visible in the measured curve, taken against the deployed service on 27 August 2026:
| Input | Latency | Marginal compute | | --- | --- | --- | | 150 words | 6.1 s | ~$0.0002 | | 400 words | 13.6 s | ~$0.0004 | | 1,000 words | 34.5 s | ~$0.001 | | 2,000 words | 58.0 s | ~$0.004 | | 26,600 words | 64.1 s | ~$0.004 |
Look at the last two rows. A document thirteen times longer takes six seconds longer, because it is the same four forward passes. The curve flattens exactly where the window cap bites. That is the point of the cap: there is no request that can run away, and no length of input that turns one detection into an unbounded bill.
It is also the honest cost of it. Six seconds for a short passage and a minute for a long one is not a good latency story, and no plan we sell can promise a response time until that changes.
Four windows, spread out
The obvious way to spend four windows is to take the first 2,048 tokens and stop. It is one line of code and it is a correctness bug that never announces itself: a long document would be judged entirely on its opening page.
The case the windowing is built around runs the other way. A piece opens in a human voice and closes with a generated summary, or a chapter is human until the conclusion someone asked a model to write. The source calls that the ordinary case — an assumption about how people actually mix writing, not a measurement of our own traffic, of which there is currently none. Front-loaded windows would never see any of it.
So `_windows()` spreads the windows evenly across the whole document instead. The first window starts at token zero, the last one ends at the final token, and the rest are spaced between them. Running that function over the manuscript above — 29,260 tokens, four windows — gives these spans:
| Window | Tokens | Position in document | | --- | --- | --- | | 1 | 0 – 512 | opening | | 2 | 9,583 – 10,095 | ~33% in | | 3 | 19,165 – 19,677 | ~65% in | | 4 | 28,748 – 29,260 | final page |
Sampling does not make coverage complete, and the gaps here are enormous — 9,000 tokens of unread text between the first two probes. But it makes coverage *unbiased with respect to position*, which is the property that matters when the thing you are looking for tends to sit at the end.
Coverage also improves fast as documents get shorter, because the four windows stay the same size while the gaps between them shrink. A 4,000-token piece gets 51% read. A 2,200-token piece — roughly a 2,000-word essay at the token ratio above — gets 93%, in four windows with about fifty tokens skipped between each. At or below 2,048 tokens, somewhere near 1,860 words by that same ratio, the windows tile the document and every token is scored. Most single essays fall inside that. Manuscripts and long reports do not.
One more detail, because it changes how you read `tokensScored`: overlapping windows are counted once. The number is distinct tokens seen, not passes made.
What averaging costs
The four window scores are averaged. Not maximised.
Maximising would be the more sensitive choice and it is the wrong one here. Score twenty windows of ordinary human prose and one of them will eventually come out high; take the maximum and the chance of a false accusation grows with document length, which is an appalling property for a tool people use on students and job applicants. The mean does not have it.
What the mean costs is dilution. A 300-word AI passage dropped into a 10,000-word human document will, at best, land inside one window and be averaged against three windows of human writing. The engine will very likely not flag it. That is a real miss, and it is the side of the trade we chose deliberately: someone who suspects a specific paragraph can seek other evidence, while someone falsely accused often has no recourse at all.
The measured shape of that choice, as of 27 August 2026: the text lane flagged 67% of AI passages (12 of 18, all from a single generator — with a sample that small and that narrow, this is the number to trust least of anything here) at a false-accusation rate of 0.2% (1 of 496 documents written before 2022, so before generative writing was common). The firing threshold behind those figures is declared policy, not a calibration study on our own corpus, and until such a study exists no accuracy claim of ours should be relied on.
Under a hundred words, nothing is scored
Below roughly a hundred words, detectors of this family flag genuine human writing. There is not enough signal, and what comes back is noise wearing the costume of a measurement.
So the engine declares a floor. `MIN_WORDS = 100`, checked before any model or file path is even resolved, and the result is a status called `too_short` that carries no score at all. Not an error. Not a low score. No number, because there is nowhere honest to put one nobody measured. That path takes about half a millisecond and returns a sentence saying how many words you submitted and why nothing was judged.
The floor is a judgement call from the literature rather than something we measured, and it replaced a guard that used to sit at ten words — which meant the engine would happily score a tweet, at total confidence.
Worth being precise about what a non-flag means at any length, short or long. A low score is not a finding of human authorship. Paraphrased AI text, lightly-edited AI text and plainly-written AI text all score low too, and this lane has no way to tell those apart from a person writing normally. Nothing in the product can certify content as human-written except a signed provenance manifest on the original bytes, which prose pasted into a box does not have.
The sentence reaches you; the numbers do not
The coverage figure is not buried. It is carried in the first line of the reasoning the engine returns, in plain language: the document is 29,260 tokens, four evenly-spaced windows covering 2,048 of them (7%) were scored, so the rest was not read by the model. The dashboard renders that reasoning, so if you run a long document today you will see that sentence.
What you will not see is anything you can check it against. Search this repository's web application for `tokensScored` or `textClassifier` and there are no matches — the fields exist in the inference service's response schema and in the JSON a direct API caller receives, and the Next.js app never names them. There is no coverage meter next to the verdict, no scored-versus-total pair, and no view of `windowScores`, the per-window readings the headline number is the mean of. If window three read high and the other three read low, the response contains that fact and no screen shows it to you.
That is a gap in our interface, not a design decision, and describing it as anything else would be the sort of claim this engine exists to refuse.
Why this rules out charging by the word
The API accepts 60,000 characters — about 10,000 words. The classifier scores 2,048 tokens, near 1,860 words. Both of those are true at the same time right now, and the space between them is a pricing problem.
Most of the market meters words. Published entry prices, as recorded on 27 August 2026:
| Product | Entry price | Published allowance | | --- | --- | --- | | GPTZero | ~$10.40/mo | free tier 10,000 words/month | | Copyleaks Personal | $13.99/mo annual | ~25,000 words/month | | Originality.ai | $14.95/mo Pro; $30 one-off | 1 credit = 100 words; $30 = 300,000 words | | Winston AI | $18/mo | — |
Compare them on price if you like; that is what published figures are for. The point for us is the unit. A word allowance is a billing unit, and it only doubles as a promise about reading if the tool reads every word it charges for. Ours does not, above about 1,860 words, and it tells you so in the response.
Which means we cannot sell words. Billing for 10,000 of them while scoring 1,860 is precisely the species of claim we refuse everywhere else in this product. There are two ways out and both cost something real: raise `MAX_WINDOWS`, which is linear CPU time on a box where four windows already take a minute, or cap the accepted document at what the engine actually scores. That cap has already moved once — it was 200,000 characters until 27 August 2026 — and 60,000 is still about five times what gets scored. Until that gap closes, the accepted length and the scored length stay different numbers, and the response will keep saying which is which.