← All work Case study · Model evaluation · Rubric design

Grading the grader: does my AI-rewrite checker actually work?

PrinciPAL checks every AI rewrite of a principal's note before it's saved. I wrote a faithfulness rubric and 52 graded test cases, ran them through the real validator code, found what it misses, prototyped a fix, and tested the fix on cases it had never seen.

Test cases
36 development + 16 held-out
Method
6-point rubric · real validator code
Held-out catch rate
0 of 8 → 5 of 8
False rejections
8 → 0 across both sets

Why test it

In PrinciPAL, an on-device model rewrites a principal's quick notes about staff. Those notes become personnel records. The only thing between the model and the record is a validator that rejects rewrites that add numbers, invent names, flip perspective or drift in length.

I built that validator, and it had unit tests. But unit tests confirm that the checks I wrote do what I intended. They don't tell me what I didn't think to check. For that I needed a rubric that defines faithfulness independently of the code, and cases graded against it.

The rubric

One question: would a principal be comfortable signing this rewrite as an accurate record of their note? A rewrite must pass all six criteria:

#CriterionFails when…
R1Nothing addedNew numbers, names, places, events, reasons, opinions or praise appear.
R2Nothing droppedA fact goes missing (concise style may drop minor details but must keep who, what and next steps)
R3Who did whatThe actor, speaker or feeler changes
R4Status keptPlans become done, done becomes planned, or a negation is dropped or added.
R5Stance keptA feeling, evaluation or level of certainty changes direction or strength.
R6Fits the styleClearly the wrong shape for the chosen style

The hard part of any rubric is the edge cases, so I wrote down the rulings too:

  • Precision counts as addition. "a couple of parents" → "2 parents" fails R1.
  • Expanding shorthand doesn't. "thurs" → "Thursday" and "third grade" → "3rd grade" pass.
  • Keeping a hedge passes. "like a hundred essays" → "about 100 essays" passes; dropping the hedge would fail R5.

Test set design

I graded every case against the rubric before running the validator. The development set covers what the validator was built to catch, faithful rewrites it must let through, and two categories I suspected it would get wrong:

CategoryCasesHuman verdictPurpose
Faithful rewrites10AcceptGood rewrites in all 4 styles must pass
Added / dropped numbers5RejectWhat the validator was designed for
Added names3RejectIncluding one at the start of a sentence
Perspective swaps3Reject"she drafted" → "I drafted"
Length drift2RejectWrong shape for the style
Meaning changes8RejectNegation, tense, stance, invented events and reasons: no new names or numbers
Faithful edge cases5AcceptLegitimate shorthand expansion that looks like "adding"

Baseline results

I copied the validator's checking code exactly (confirmed with a diff) and ran all 36 cases through it:

Categoryv1 correct
Faithful rewrites10 / 10
Added / dropped numbers5 / 5
Added names2 / 3
Perspective swaps2 / 3
Length drift2 / 2
Meaning changes0 / 8
Faithful edge cases0 / 5
The pattern: v1 is excellent at surface changes to numbers, names and length, and blind to meaning changes that use the same words. It's also too literal, rejecting harmless expansions of shorthand.

The overall accuracy (58%) isn't a meaningful real-world number, because the set deliberately concentrates on suspected weak spots. The per-category results are what matter.

What it missed

A dropped "not" reverses the record

Original notechen did not turn in his lesson plans this week
RewriteMr. Chen turned in his lesson plans this week.
Rubric: fails R4 · v1: accepted ✘. No new names or numbers, so nothing tripped.

A suspicion hardens into an accusation

Original noteI think a student may have taken the tablet from the cart
RewriteA student took the tablet from the cart.
Rubric: fails R5 · v1: accepted ✘

The most dangerous one: actor and victim swapped

Original notea student pushed ms rivera's aide on the playground
RewriteMs. Rivera pushed a student on the playground.
Rubric: fails R3 · v1: accepted ✘. Every word was already in the note.

A false alarm: correct expansion rejected

Original noteokafor wants to meet thurs about the parent email. I'll join.
RewriteMs. Okafor wants to meet on Thursday about the parent email, and I will join.
Rubric: passes · v1: rejected ✘ ("added Thursday"). The principal loses a good rewrite and sees a scary error.

The v2 fix

I prototyped v2 as a wrapper around v1, so nothing v1 already caught could regress. It adds two layers:

1 · Canonicalize before comparing

Both texts get the same shorthand expansion before v1 runs: day and month abbreviations, ordinals before words like "grade" and "period", "a hundred" and dotted acronyms. "thurs" and "Thursday" now compare as the same word.

2 · Five cheap meaning checks

  • Negation count changed → reject (catches "did not" → "did")
  • Future markers vanished → reject (catches "I'll meet" → "Met")
  • Hedges vanished → reject (catches "may have taken" → "took")
  • New causal word ("because", "due to") → reject (catches invented reasons)
  • New judgment word ("excellent", "impressive") → reject (catches added praise)

I deliberately did not try to catch swapped actors, reversed feelings or invented events with keyword rules. Any rule I could write for those would be overfit to my own examples.

On the development set, v2 still misses those three plus two instructive ones. An invented name at the start of a sentence slips through, because v1 skips sentence-initial capitals, which are usually ordinary words. And a full swap ("she drafted it and sent it to me" → "I drafted it and sent it to her") passes, because v1 detects swaps by counting pronouns, and a clean swap leaves the counts balanced.

Held-out test

A fix designed by studying failures can simply memorize them. So after freezing v2, I wrote 16 new cases with different people, wording and situations, graded them, and ran both versions:

Unfaithful caughtFaithful wrongly rejected
v1 · development11 / 215 / 15
v2 · development16 / 210 / 15
v1 · held-out0 / 83 / 8
v2 · held-out5 / 80 / 8

v2 held up: it caught every negation, tense, hedge, reason and judgment change in the new set, rejected no faithful rewrites, and regressed nothing v1 already handled.

The three it still missed are exactly the three classes I predicted. They are a reversed stance ("skeptical" → "supports"), a swapped actor ("a parent yelled at the secretary" → "the secretary yelled at a parent"), and an invented detail ("several classes were slow to exit").

What rules can't do

Keyword rules can catch changes to which words appear. They can't catch changes to who did what to whom. My recommendations for production:

  1. Ship v2's rules. They're cheap, deterministic, and removed every false rejection in both sets.
  2. Add a focused semantic check for the remaining classes: ask the on-device model yes/no rubric questions ("Does the rewrite change who performed the action?") rather than "is this faithful?", since narrow questions are easier to evaluate and audit.
  3. Keep a human in the loop for actor changes. Swapped roles are the costliest error in a personnel record. When the people mentioned in a sentence are reordered, show the principal a side-by-side and require confirmation.
  4. Grow the test set from real outputs, graded by a second rater, and track agreement between raters.

Limitations

  • Constructed rewrites. The rewrites were hand-written to mimic model errors, not sampled from the model, so they measure the validator, not how often the model makes each error.
  • Small sample. 52 cases is enough to expose failure classes, not to estimate rates precisely.
  • One rater. I wrote and graded every case, including the held-out set, so an independent rater would make the result stronger.
  • Brittle keywords. "since" can be temporal and "may" can be a month; I expect some false rejections at scale.

Reproduce it

Everything is in evals/principal-faithfulness:

swiftc -O Validator.swift ValidatorV2.swift main.swift -o eval ./eval "$PWD"

What this shows for AI training roles

  • Writing a rubric that defines quality separately from the system being tested, with documented edge-case rulings
  • Designing test sets that target failure classes, not just happy paths
  • Weighing both error types: dangerous misses vs. costly false alarms
  • Guarding against overfitting with a held-out set
  • Being honest about what the numbers do and don't mean