Skip to content

The benchmark

The engine ships with a labeled corpus and a benchmark that runs with the test suite on every push. When a change drops detection or flags human writing, the build fails. This page shows what is measured, the current numbers, and the limits of what they mean, so the claim on the front page has its receipt.

Everything here reproduces from the repository:

go test ./sanitize/ -run TestBenchmark -v

What the score claims, and what it does not

The slop score measures the density of patterns from the engine's ruleset: buzzwords, stock phrases, sentence shapes, hedging, sentences that keep closing on an -ing clause, a comma in nearly every sentence, and the characters models type. A high score means the text carries many machine-writing patterns. It does not determine who wrote the text. A person can write in the polished-assistant register, and a machine can be prompted away from every pattern the rules know. The score is a lint result, not an authorship verdict.

The corpus

The corpus is one JSON object per line at sanitize/testdata/corpus.jsonl, embedded in the engine and versioned with it. Each passage carries a label and a note naming the tell or the trap it exercises.

Label Passages What it holds
ai 71 Machine-register prose: each passage exercises specific tells, from buzzword density to the polished 2026 register with no lexical tells at all. 
human 46 Human prose chosen to trip a careless detector: poetry heavy with em-dashes, ornate academic writing, plain conversational notes, a graduation speech.
technical 36 Precision traps: RFC normative language, legal parallel structure, reference docs that repeat their subject, prose that has every right to sound formal.

The passages were written and curated during adversarial audit rounds, on purpose, to probe where the engine fails. That is also the corpus's main limitation, covered below.

There is a second development corpus beside it at sanitize/testdata/longform.jsonl, holding 145 full-length passages: 80 human READMEs from repositories untouched since 2021, and 65 machine passages across five local model families, seven genres, and three prompt styles. It exists because the corpus above is two sentences long at the median, which is the right size for pinning what a rule matches and too short for any signal that reads a whole passage. Both files are held under the same lock that keeps the evaluation corpus out of rule development.

Current numbers

The benchmark reports these on every run and fails below the floors.

Metric Current Floor
Tell recall (AI passages with a tell) 0.99 0.95
Technical precision (no false tell) 1.00 0.98
Score recall (AI at 25 or higher) 0.99 0.95
Score precision (at 25 or higher) 0.99 0.98
Mean score, AI passages 83.4
Mean score, human passages 2.5
Score margin (AI mean minus human) 80.9 70

The floors sit below the current numbers so ordinary changes pass while a real regression fails. They ratchet up as the engine and the corpus improve.

What fires on what

122 distinct rules fire across the 71 AI passages. By class:

Class Findings Score weight
Structural 90 2 per finding
Word 74 1
Phrase 13 1
Character 5 1 for the em-dash and invisibles 
Tidy 2 0

Sentence shapes, not word lists, carry the most weight. That is deliberate: a stock sentence shape is stronger evidence than one word, and word lists are the first thing a model gets trained away from.

On the 46 human passages the engine finds typography almost exclusively: nineteen curly quotes, en-dashes, and ellipses. Those carry zero score weight, which is why a professionally typeset human page averages 1.5 rather than getting flagged for its punctuation. One that said, and one semicolon round out the human findings. The 36 technical passages produce zero findings.

Attacking it on purpose

slop-chop attack rewrites text to dodge the rules without improving the writing: a listed buzzword becomes an unlisted one, a stock opener becomes an unstocked one, an em-dash becomes punctuation no rule reads. Then it reports what survived. Every replacement it reaches for is itself checked against the default profile by a test, so an evasion that stops evading fails the build.

Run against all 71 AI passages, the attack tells the story the score weighting rests on:

Class Evaded Held What that means
Word 33 41 A word list is a lookup, and a lookup loses to a thesaurus. 
Structural 1 89 No swap reaches a sentence shape, so this bounds the attacker. 

67 of the 71 passages still carry a tell after the attack. That gap is why a structural tell counts two and a word counts one, and the test that measures it fails the build if the classes ever invert.

The asymmetry is what the score weighting rests on. It says a published word list is a depreciating asset, and that the structural rules are worth more per finding.

Read both rows as facts about this one attacker rather than about evasion in general. The word row is a floor: the evasion table covers a few dozen entries against a block list of 161, so a fuller thesaurus would evade more.

The structural row carries a sharper limit, and it is the one to be careful with. slop-chop attack substitutes, and a substitution does not rebuild a sentence, so the structural rules are measured here against an attacker that cannot reach them. The row shows the attacker's reach rather than the rules' durability. Nothing in this run tried to escape a sentence shape, so nothing in this run says a sentence shape is hard to escape.

Testing that needs an attacker that rewrites rather than swaps, which means a model told to keep the meaning and change the shape. The published paraphrase attacks on neural detectors do exactly that, with a separate control for reordering, and they report large drops in detection. Whether a fixed rule set holds up better than a classifier is a fair question and an open one. Until that run happens, treat the durability of the structural half as unmeasured.

slop-chop on slop-chop turns the engine on its own documentation and output: whether the docs pass, whether chopping settles, and what one pass actually leaves behind.

Prose the rules never saw

The corpus above shares an author with the rules, which is the limitation its own section admits. One question it cannot answer is whether the engine fires on ordinary professional writing nobody involved here has read. That one has an answer that needs no raters, because ground truth can come from the calendar: a README in a repository with no push after 2021 was written before a general writing model existed.

Abandoned projects Maintained projects 
READMEs scored 618 268
Ecosystems 7 7
Median prose 369 words 573 words
Median score 1 4
p90 / p99 13 / 22 15 / 26
Worst sample 35 36
Scoring 25 or higher 4 4

886 documents across Go, Python, Rust, JavaScript, Java, Ruby, and C++, of which eight reach the reads-clean line: 0.9 percent. The maintained column read three until the participle tail signal of 2026-10-05 moved one document from 24 to 25, which the recheck that day missed because it ran the abandoned column only. The clause habit and the three rules added beside it on 2026-10-08 moved no document in either column, measured on the engine before and after. The second column is the check on the first: a repository untouched since 2021 is an abandoned one, and abandoned projects might write differently. They do, in the direction that makes the test harder rather than easier, since maintained projects carry half again as much prose per README for the engine to trip on.

This measurement read zero for a long time, across a larger draw of 1270 documents. The seven are the price of the evidence component, which is what lets the engine see a long machine document at all, and subtracting that component back out returns every one of them to clean. The section on full-length prose above carries the other half of that trade. The numbers here come from a fresh collection under the current ruleset rather than the older one, so the sample is smaller than the draw that reported zero.

The rules that do fire on human prose are worth naming: word:powerful leads at six percent of documents, then curly quotes, structural:bold-bullet-run, and structural:template-stem. Nothing reaches seven percent. The character rules dominate the raw counts and contribute nothing to the score, which is the separation between a finding and a score weight doing its job on writing it has never seen.

This measures quiet, not detection. There are no machine samples in it, so it says nothing about recall, and a clean sweep is evidence the engine stays out of the way rather than evidence it works. It is also one genre, and a README is terse and list-heavy, so the number describes README prose and is quoted that way. evaldata/README.md carries the method, and the two manifests beside it carry a row per sample with the commit it was read at, so the numbers can be rechecked rather than believed.

Full-length prose, both halves

The measurement above has no machine samples in it, so it reports quiet rather than detection. The long-form corpus is the pair to it: human and machine prose at the same length, in the same genres, scored by the same ruleset.

Two things come out of it, and they point in opposite directions.

The engine separates full-length prose very well. Machine passages mean 38.1 against 5.4 for human, which is a Cohen's d of 2.66. As a ranking instrument on real documents it works, and the gap is not an artifact of the adversarial prompt style: passages written under an instruction to avoid every cliche of AI writing score about the same as the plain ones.

The verdict did not follow it there, and the reason was the score itself. Before the evidence component, 13 of 65 machine passages reached the line a reader acts on, against 0 of 80 human. The score was a pure density, so the same evidence spread across four hundred words read thinner than across twenty. The exemplar corpus reports a mean machine score in the eighties because its passages average twenty words. Real machine prose at three hundred and fifty words scored 17 and was called clean.

Moving the line was the obvious fix and the wrong one, since it changes every verdict the tool has ever given and does nothing about the cause. The cause is visible in what a rate discards. Counting total weighted evidence rather than only its rate against length:

Median weighted tells Maximum 
Machine, long-form 4.0 17.2
Human, long-form 1.0 5.5

A human document accumulates about one weighted tell however long it runs. A machine one accumulates four. The score now adds up to fifteen points for accumulated evidence, at two points per weighted tell, capped low enough that a count on its own cannot reach a verdict. The multiplier was read off a flat region rather than a peak: every value between 1.5 and 3 recovers between 46 and 51 percent of long machine prose at the same cost.

The corpus is split in half by a hash of the passage text, and the shape was chosen on one half only. On the half it was not chosen on, machine passages reaching the line went from 7 of 24 to 15 of 24, with human documents staying at 0 of 38.

It is not free. Scored against the abandoned-repository half of the false positive corpus, the evidence component takes that measurement from 0 human documents at the line to 4 of 663. All four are caused by this component and none by the rhythm penalty, which was checked by subtracting each one back out. Density does not separate them from machine prose either: they run between 1.8 and 4.0 tells per hundred words, and machine long-form averages 2.05, so there is no filter hiding here that keeps the recall and drops the cost. The trade is two thirds of long machine prose caught against six human documents in a thousand flagged, and it is a trade rather than a free improvement.

Cadence replicates here. The rhythm penalty was set on the evaluation corpus and burned it, which left the size of its effect unmeasured. On this corpus, collected afterward and never used to adjust it, machine prose runs at a coefficient of variation of 0.360 against 0.707 for human, a Cohen's d of -1.78. It fires on 86 percent of machine passages and 20 percent of human ones. That is a replication rather than a held-out result, since this is development data and tuning anything against it would burn it too.

It is also the one signal a thesaurus cannot reach. Running the substitution attack across the machine half removes 43 percent of the tells and 2 percent of the cadence penalty. The 2 percent is real rather than zero: an evasion that swaps one word for two changes a sentence length. But escaping a rhythm means rewriting sentences, and no lookup table does that.

Participle tails catch more without flagging more. A sentence closing on a comma and an -ing clause, "the fleet sat offshore, deploying its guns", is ordinary writing once and the model register when it keeps happening. On this corpus 85 percent of machine passages carry one against 36 percent of human passages, and machine prose runs at about eight times the human rate. The score treats it like hedging: the rate counts and the instance never does, the first tail is free, and the habit adds at most ten points.

Its settings were chosen while the whole corpus was in view, before the split was noticed. Choosing again on the development half alone lands on the same settings, so the holdout did not pick them, but it was seen, and these figures carry that. On the holdout half, machine passages reaching the line went from 15 of 24 to 17 of 24, with human documents staying at 0 of 38. Separation on that half did not improve: 0.959 before and 0.957 after. So the tail moves borderline machine prose over the line without ranking documents any better. Scored against the 618 abandoned-repository READMEs of the false positive corpus, which nothing here was tuned on, the change moves no document at all.

The clause habit is the signal the attack cannot reach at all. Half of human prose sentences carry a comma. Three quarters of machine sentences do: a qualifier, an aside, a consequence clause, on every sentence, so that nothing is ever stated flat. On the development half the comma-bearing share runs 0.75 for machine prose against 0.49 for human, a Cohen's d of 1.74, and on the holdout half 0.81 against 0.44, a d of 2.26. The score charges it the way it charges cadence: nothing until the share clears 0.70, the human ninetieth percentile on the development half, then a gradient to eight points at a comma in every sentence, and nothing at all under six prose sentences. It touches three human passages in each half and ten of the 618 abandoned-repository READMEs, moves none of them to the line, and the substitution attack removes one percent of it, against two percent of the cadence penalty and thirty-five percent of the tells.

Three lexical rules came out of the same measurement, and they are the evadable kind. ensure appears in two thirds of machine long-form passages and one human passage in twenty, the widest gap of any single word measured. It now fires on five percent of abandoned-repository READMEs and thirteen percent of maintained-project ones, the highest rate of any rule on human prose, and that cost is stated here rather than hidden. The release-note fanfare, we are thrilled to announce, appears in no README of 852 written before 2022 and in the one machine release note that scored zero. The chat sign-off now takes any verb, since Happy automating! is the same sign-off as Happy coding!.

Together, on the holdout half, machine passages reaching the line went from 17 of 24 to 20 of 24 and on the development half from 24 of 41 to 28 of 41, with human documents staying at 0 of 38 and 1 of 42. The probability that a random machine passage outscores a random human one went from 0.878 to 0.924 on the development half and from 0.957 to 0.992 on the holdout. In the simulation that chose these, the clause habit alone moved two holdout passages and ensure alone moved three. The word did more of the verdict work and the habit did the durable part.

One candidate was measured and left out. Nominalization density, the share of words ending in -tion, -ment, -ness, and the like, separates the halves about as well on the holdout (d 2.12) and runs at 0.58 correlation with the clause habit, so it moved the same passages and no others, and it touches eighteen percent of human READMEs, since technical prose is built from those nouns. The record of the whole measurement, with every feature tried, is INVESTIGATION-clause-habit.md in the repository.

Limitations, plainly

The corpus grades the engine on its own homework. The passages were written during the audits that also shaped the rules, so recall of 0.99 here demonstrates rule coverage of known registers, not generalization to arbitrary text in the wild. The numbers are honest about what they measure, and what they measure is narrower than "any AI text anywhere."

A low score is not a human verdict. Text written to dodge every known pattern will score low and may still read machine-made to a person. The score measures compliance with the ruleset. The ruleset chases the current registers, and the registers move.

No stratification. The corpus is not sampled across models, prompts, genres, or authors, and there are no confidence intervals on 153 passages. It is a regression guard with teeth, not a study.

A rate is not a verdict. The score is tell density, so its headline numbers move with passage length. The exemplar corpus reports a machine mean of 85 on twenty-word passages and the long-form corpus reports 30 on three-hundred-word ones, from the same engine and the same ruleset. Both are true and neither is the number. Any figure quoted off this page has a passage length attached to it, stated or not.

The gate was blind to anything longer than a paragraph. Until the long-form corpus existed, ten of the exemplar corpus's hundred and thirteen passages were long enough to trigger a signal that reads sentence rhythm, so the benchmark could not have caught a regression in one. That is how the rhythm work ended up measured against the evaluation corpus, which burned it. The second corpus closes the hole for signals at that scale, and the same hole will open again for any signal that needs more context than a single document.

What would settle it. Blind human raters over a corpus the rules never saw: does the score order texts the way people do? The scaffolding for that experiment lives at evaldata/ in the repository, with the collection protocol, the rating protocol, a CI lock that keeps eval samples out of rule development, and a harness that computes the correlation once ratings exist. The experiment has not been run. Until it has, the honest claim stays: same input, same output, and the patterns the rules know get caught every time.

Growing it

Add a line to corpus.jsonl with a label, the text, and a note naming what it exercises, then run the benchmark. A passage that exposes a miss is worth more than ten that pass. When the numbers rise and hold, raise the floors so the gain is locked in.