Faster evals with Jev: how we grade AI-written code / Design the grader
Writing Noul checks for acceptance criteria
A Noul is a yes-or-no statement, and Jev returns one probability that the statement is true of the state. That makes it the natural shape for an acceptance criterion: one criterion, one statement, one probability. The craft is in the wording, because the model reads the statement exactly as written. The statement has to be positive, literal, and about one thing; the evidence it needs (the diff, the spec line, the trace) has to be in the state; and the criterion often turns out to be three statements rather than one. This page shows the rules and works one spec line through to its Noul checks.
Published September 22, 2026. Editorial.
Key takeaways
- One acceptance criterion becomes one or more Noul statements, each a positive sentence that is true or false of the state.
- Write the statement the way the model will read it: literally. Scoping words, negations, and implied conditions are read exactly as written.
- Avoid negations. Ask whether the handling exists, so a high probability always means the good outcome.
- Put the evidence in the state. If the statement mentions the spec line, the spec line is in the state, and nothing unrelated is.
- A Noul carries no separate confidence number, so the uncertain band is set on the probability itself.
An acceptance criterion is already almost a Noul. "An expired token returns 401 with the error code token_expired" is a sentence that is either true or false of a change, which is exactly what TypeSafe's Noul primitive grades: a yes-or-no statement, returning one probability from 0 to 1 [1]. The gap between the criterion and a good Noul check is in the wording, and this page is about closing it. Reveneau grades the acceptance criteria in its eval suite with Noul checks written from the specification before the code exists, and the rules below are the ones we apply.
The statement is read literally
TypeSafe's jaggedness page opens with the property that governs everything on this page: the model "answers the question you wrote, not the one you meant", and scoping words, negations, and implied conditions are read exactly as written [2]. A person reading an acceptance criterion fills in what the author meant. A decision model reads the sentence.
That has an immediate consequence for how you write. "The change handles expired tokens correctly" is a sentence a person understands and a model cannot grade, because "correctly" points at a standard that is not in the sentence. "The diff returns HTTP status 401 when the token's expiry time is in the past" names the standard. The difference is the same one that separates a testable spec line from a vague one, and writing specs an agent can verify covers the habit for the spec itself. A Noul is the spec line, tightened once more.
Rule one: positive statements only
TypeSafe says double negatives and indirection are answered less reliably [2], and a negation in a check has a second cost that is worse than reliability: it flips the meaning of the probability. If the statement is "the diff does not handle the expired-token case", a probability of 0.9 means the change is bad, and every threshold in the pipeline has to be inverted for that one check. Someone will get it wrong.
So every Noul asserts the good outcome. "The diff adds handling that returns 401 when the token is expired." High probability, good; low probability, bad; the same threshold logic for every check in the suite. If the criterion in the spec is written as a prohibition ("the endpoint must not return the user's email in the error body"), rewrite it as the positive form of what should be true ("the error body returned by the endpoint contains only the error code and message fields"). The rewrite is also more precise, which is usually the case.
Rule two: one thing per statement
"The diff returns 401 with the error code token_expired and logs the failure" is three claims in one sentence, and a probability of 0.6 on it tells you nothing about which claim the model doubted. TypeSafe's design guidance is to break a broad judgment into atomic questions asked in parallel [3], and since every question in a request is evaluated in parallel with almost no added response time [4], there is no cost to splitting.
Split until each statement could be wrong on its own. Then a low grade points at one thing, which is most of what a written rationale would have given you. The pattern for combining the results back into one verdict is in composite scoring for code quality; for acceptance criteria the combination is usually that every statement must pass.
Rule three: the evidence is in the state
The state is the only thing the model sees. TypeSafe names hidden context, relying on model knowledge instead of the state, as an anti-pattern [3], and separately warns that accuracy falls as the state grows with content unrelated to the decision [2]. Both point the same way: the state should hold exactly what the statement refers to.
For an acceptance-criterion check on a code change, that is the diff, or the part of it that touches the behaviour, and the spec line itself, quoted. If the statement says "the error code the spec names", the spec line with the error code is in the state. If the check is about an agent's trace, the trace is in the state and the plan is next to it. What is not in the state: the whole repository, unrelated files in the diff, the previous conversation with the agent. TypeSafe's context limit is 32k tokens for the state plus the longest question [5], and a large diff should be split by behaviour rather than truncated, which running Jev-graded evals on every pull request covers.
Rule four: name the evidence in the statement
A statement that says "the diff adds handling" is better than one that says "handling exists", because it tells the model where to look. Better still is a statement that names the observable artefact: "the diff adds a branch that returns status 401 and the error code token_expired when the token's expiry is in the past". A reader could find that branch. The model, reading literally, has the words to match against.
This is the same rule as the observable-behaviour rule for Score levels in turning a rubric into Score questions. A Noul is a one-level rubric.
A worked example
Spec line: "When a request carries an expired access token, the API returns HTTP 401 with the JSON error code token_expired, and the failure is logged with the request identifier and without the token value."
A person reads that as one requirement. It is four claims, and three of them are judged checks. The status code can be a deterministic test: send a request with an expired token to the running service and assert 401. That check stays in code and never goes to a model, in line with the rule to keep deterministic rules in code [3]. The other three become Noul statements, with a state of the diff plus the quoted spec line:
Statement one: "The diff adds a code path that returns the JSON error code token_expired when the access token's expiry time is in the past."
Statement two: "The diff logs the expired-token failure with the request identifier."
Statement three: "The log line written for the expired-token failure contains no part of the token value."
Each is positive. Each is one claim. Each names something in the diff. Statement three is the interesting one: the spec wrote it as a prohibition, and the positive rewrite ("contains no part of the token value") is still a positive statement about what the log line contains. It is a single positive claim and depends on no counting.
The three probabilities come back in one request. All three must pass for the criterion to pass. A low grade on statement two says the logging is the problem, and a person opening the change knows where to look.
Reading the probability
A Noul returns one probability and, unlike Choice and Score, no separate confidence number [1]. So the uncertain band is set on the probability itself. A statement at 0.93 passes. A statement at 0.12 fails. A statement at 0.55 is the model saying it cannot tell, and that change goes to a person, whose label is kept. The bands and how to set them by consequence are in thresholds, confidence and escalation, and the method for measuring where your own bands should sit is in calibrating Jev against your own human labels.
One caution on interpreting a middle probability. It can mean the change is borderline, or it can mean the statement was ambiguous and the model split its reading of the words. The second is more common in a new suite, and the label a person gives tells you which. If the reviewer says "obviously passes", the statement needs rewriting.
What a Noul cannot check
Anything that needs counting, comparing numbers, or ordering dates. TypeSafe lists all three as weaknesses [2], so "the diff adds at most two new dependencies" or "the retry delay is longer than the timeout" are deterministic checks, however tempting the yes-or-no shape is. Anything that needs knowledge outside the state, which is the hidden-context anti-pattern. And anything that needs a reason with the verdict, because a Noul returns a number and nothing else; what a Jev grade cannot tell you is the full list.
The habit
Write the criterion as a positive sentence about one observable thing, put the evidence next to it in the state, and split until each sentence could be wrong alone. Then the probability means what the sentence says. Every Noul in the Reveneau suite is written from the spec before the code exists, which is the order eval-driven development describes and the reason a grader can be trusted to grade against a standard the code did not write. The statement is the standard. The diff meets it or it does not.
Best for
- Acceptance criteria that can be stated as a positive sentence about the diff or trace
- Scope checks: the change touched only the files and behaviours the task named
- Instruction-following checks on generated text against a quoted instruction
Avoid if
- The criterion needs a count, a numeric comparison, or a date ordering to decide
- The evidence the statement refers to cannot be placed in the state
- You need the grader to explain a failure in words
Check before you decide
- Every statement is positive, names one claim, and points at something in the state
- The quoted spec line sits in the state next to the diff
- The uncertain band on the probability routes to a person whose label is stored
Common questions
What is a Noul check?
A Noul is a yes-or-no statement that Jev grades with one probability from 0 to 1 that the statement is true of the state. As an eval check it is one acceptance criterion written as a positive sentence, with the diff and the quoted spec line in the state. Code compares the probability to a threshold: below it the change fails, in the middle band a person decides, above it the check passes.
Why must a Noul statement be positive rather than negative?
Two reasons. TypeSafe says double negatives and indirection are answered less reliably. And a negated statement flips the meaning of the probability, so 0.9 would mean bad for that one check and the threshold logic would have to be inverted, which someone will eventually get wrong. Assert the good outcome every time, so a high probability always means pass across the whole suite.
How many Noul statements does one acceptance criterion become?
As many as there are claims that could be wrong on their own. A spec line about an expired token that names a status code, an error code, a log line, and a redaction is four claims; the status code becomes a deterministic test and the other three become three statements. Splitting costs nothing, because every question in a request is evaluated in parallel with almost no added response time.
What goes in the state for a Noul check on a code change?
The diff, or the part that touches the behaviour, and the spec line quoted next to it. Nothing else. TypeSafe names hidden context as an anti-pattern and says accuracy falls as the state grows with unrelated content, and the limit is 32k tokens for the state plus the longest question. If the statement mentions the spec, the spec text must be in the state or the model is grading against nothing.
How do I read a Noul probability of 0.55?
As the model saying it cannot tell, which sends the change to a person. It can mean the change is borderline, or that the statement was ambiguous and the model split its reading of the words. In a new suite the second is more common. If the reviewer's label says the change obviously passes or obviously fails, rewrite the statement; if the reviewer also found it hard, the change itself is borderline.
Does a Noul return a confidence number?
No. TypeSafe's confidence number exists for Choice and Score only, computed from how spread out the probabilities are. A Noul returns one probability, so the uncertain band is set directly on it: for example fail below 0.3, route to a person from 0.3 to 0.8, pass above 0.8, with the exact lines set per check from your own labelled calibration set rather than from a default.
Can a Noul check whether a diff added at most two dependencies?
It should not. TypeSafe says the model does not count reliably and cannot reliably compare numeric values, so a yes-or-no statement that needs a count is unreliable however natural it sounds. Count dependencies in code: parse the manifest diff and assert the number. Reserve Noul statements for claims with no exact answer, such as whether the added dependency is used for the purpose the task described.
How does a Noul check differ from a Score question?
A Noul is one statement with one probability and no confidence number; a Score is 2 to 10 worded levels with a probability per level, a combined score, and a confidence number. Use a Noul when the criterion is true or false. Use a Score when there are ordered degrees you act on differently, such as error handling that is absent, generic, or specific. A Noul is a one-level rubric.
How should a prohibition in the spec be written as a Noul?
As the positive form of what should be true. The spec line the error body must not contain the user's email becomes the statement the error body contains only the error code and message fields. That keeps the probability's meaning aligned with the rest of the suite, avoids the double-negative weakness, and is usually more precise than the prohibition, because it names what is allowed rather than one thing that is not.
When does Reveneau write its Noul checks?
From the specification, before the code exists, in the same pass as the deterministic checks. The statement is the standard the diff has to meet, and a statement written after reading the finished code would describe the code rather than the requirement. The coding agent then writes code until the suite, including these checks, passes, and a grade in the uncertain band goes to a person whose label is stored.
References
- [1] TypeSafe docs, Noul primitive: a yes/no statement; returns one probability from 0 to 1; Noul answers carry no separate confidence number.
- [2] TypeSafe docs, Model jaggedness for jev-1.13: answers the question you wrote, not the one you meant; scoping words, negations, and implied conditions are read exactly as written; double negatives and indirection answered less reliably; does not count reliably; reads dates as text; accuracy falls as the state grows with unrelated content.
- [3] TypeSafe docs, How to build with System One: keep deterministic rules in code; break a broad judgment into atomic questions asked in parallel; hidden context is an anti-pattern.
- [4] TypeSafe docs, Introduction: every question in a request is evaluated in parallel; adding questions barely changes the response time.
- [5] TypeSafe docs, Models: 64k tokens of context per request; 32k tokens for the state plus the longest question.
Related reading
How to write an acceptance test a machine can run
Most acceptance criteria are written for a human reader who will add the missing details. A machine adds nothing. Here is how to write the sentence so an automated check can enforce it.
The difference between a bug and a missing spec
A bug is code that breaks a rule someone wrote down, and a missing spec is behavior nobody defined, and mixing up the two sends teams to fix things that were never actually broken.
Our eval suite now runs ten times faster. Here is what we changed.
This month we replaced the language model that graded the judged checks in our eval suite with Jev. The deterministic checks did not move, every rubric is now written inside the check, and the suite runs ten times faster.
More in Design the grader
Turning a rubric into Score questions
A Score question places a change on an ordered scale of 2 to 10 levels that you describe in words, and returns a probability for each level plus a combined score. The whole design task is writing the levels. Each level has to name one behaviour a reader could see in the diff, the levels have to be ordered so that a higher one is unambiguously better, and no level may lean on a number, because TypeSafe says Score levels are weak in numerical calibration. This page walks through the docs' example, a rubric for a code change, and where to set the pass line.
Composite scoring for code quality
The question every eval suite wants to ask is whether a change is good, and it is the one question a decision model answers badly. TypeSafe names broad questions as an anti-pattern in its own design guidance and gives the alternative: split the judgment into atomic questions, ask them in parallel in one request, and combine the answers in code with weighted arithmetic. This page shows the split for a code change, gives example weights, and explains why the arithmetic has to live in a file that shows up in a diff, so that a change to how the suite grades is reviewed like any other change.