Companion artifact for the CISOSE 2026 tutorial Building and Verifying AI Systems with Agentic AI (Chia-Kai Chang).
It exists to make three claims checkable rather than asserted:
| Claim | How this repo makes it checkable |
|---|---|
| runnable | one command, no network, no API key, no third-party packages |
| versioned prompts | a rubric is a file with a version and a content hash; change it without bumping the version and the harness refuses to run |
| cross-model reproducibility | every run writes a record pinning rubric hash, model ids, parameters and every raw reply |
The headline output is not a mean score. It is the list of items the judges disagree on — with each other and with your human labels. Agreement is a summary; disagreement is a work queue.
python judge.py run \
--rubric rubrics/dialogue_quality.v3.toml \
--set goldensets/dialogue.jsonl \
--models mock:strict,mock:lenient
No key, no install, nothing to sign up for. Python 3.11+ (for tomllib; on 3.9/3.10
run pip install tomli).
Then swap in real models:
export OPENAI_API_KEY=... # and/or ANTHROPIC_API_KEY
pip install openai anthropic
python judge.py run \
--rubric rubrics/dialogue_quality.v3.toml \
--set goldensets/dialogue.jsonl \
--models openai:gpt-4o-mini,anthropic:claude-haiku-4-5
python judge.py check rubrics/dialogue_quality.v3.toml [--show-prompt]
python judge.py pin rubrics/dialogue_quality.v3.toml
python judge.py run --rubric R --set S --models M1,M2 [--limit N] [--out DIR]
python judge.py report runs/<record>.json
check validates the rubric and reports its pin status. --show-prompt prints the
system prompt the harness will actually send — read it before you trust a score.
judge.py CLI
judgekit/rubric.py rubric loading, validation, version pinning
judgekit/providers.py mock / openai / anthropic adapters, one return shape
judgekit/runner.py golden set x judges -> run record
judgekit/report.py agreement statistics + the disagreement queue
rubrics/ two real rubrics (redacted) + the pin lock file
goldensets/ a ten-item synthetic set + a starter template
example-run.json one complete run record, so you can see the shape before running
runs/ your own run records land here
Open example-run.json before you run anything. It is what "reproducible" means in
practice: the rubric id and content hash, the model ids and parameters, and every raw
reply — enough for someone else to tell whether your numbers came from what you say they
came from.
rubrics/dialogue_quality.v3.toml and rubrics/dialogue_participation.v1.toml judge
the same transcripts to different standards, and they are both correct.
The strict one asks "is this good dialogue?". The lenient one asks "is this a real person engaging honestly?". Both shipped in production, because the strict rubric had been used as the pass mark and returned a 0–38% pass rate — which contradicted what students had been promised. The fix was not to loosen the strict rubric; it was to add a second question and keep the first intact as a higher tier.
Run both over goldensets/dialogue.jsonl and compare. A judge encodes a policy, not
a truth — that is the single most useful thing in this repository.
Note the negative_instructions block in the lenient rubric. Every line in it was
added because the judge had been observed failing an item for that exact reason.
$ python judge.py run --rubric rubrics/dialogue_quality.v3.toml ...
REFUSING TO RUN -- rubric drift
rubric 'dialogue_quality' version 'v3' is pinned to 75003a6086bd
but this file hashes to 29a083291bf3.
Editing a judge prompt without bumping its version silently invalidates every score
you have ever recorded under that name. This is the one error the harness will not let
you make. --allow-drift exists; do not use it.
mock:strict and mock:lenient are not models. They are a transparent heuristic
over turn length, filler ratio, causal markers and repetition, with a downward or
upward bias. They exist so the harness runs when the conference wifi does not.
They are still instructive. Run the offline demo and look at item dlg-05 — a fluent,
well-formed pasted essay that never answers the tutor. Both heuristic judges score it
highly; the human label is near zero. That is length-and-fluency bias, reproduced
offline, with no model involved — which tells you the bias is a property of shallow
evaluation, not of any particular vendor.
Dependency-free and deliberately plain:
Two judges that rank items the same way but at different levels will show low exact agreement and high kappa. That is systematic bias, and it is fixable by rewriting the rubric. Low kappa is noise, and it is not.
If a golden set item carries a human field, human joins the comparison as another
rater automatically. Calibrating a judge against a person is the only way to know
whether it measures what you think it measures.
goldensets/_starter.jsonl. Five items is enough on day one: three
representative, two adversarial. Add one item every time production surprises you.id, set version = "v1", run judge.py pin.The set matters more than the prompt. Models change; a good evaluation set outlives all of them.
No real student data ships here. The transcripts in goldensets/dialogue.jsonl were
written to reproduce failure shapes seen in production, not copied from it. That is
also the rule worth taking home: an eval set you cannot publish is an eval set your
collaborators cannot check. If yours contains real user data, build a synthetic
mirror of it for exactly this reason.
MIT — see LICENSE. Use it, change it, ship it. If it is useful in published work, cite
the tutorial:
C.-K. Chang, Building and Verifying AI Systems with Agentic AI, tutorial at IEEE CISOSE 2026, Fukuoka, Japan.