Evolve the eval standard
Scope: the evals block as a shared contract, and changing it safely. The block’s fields are in the evals schema reference; tuning how a judged eval is decided is Calibrate the judge.
The block is a shared contract, not ours alone
Section titled “The block is a shared contract, not ours alone”The shape of metadata.evals is not an implementation detail, and it is not this tool’s to change.
It implements manni:artifact-evals:1.1.0, a built-in vocabulary
published by manni meta that any
session-grading tool can implement. manni tracevals implements behavior against it.
Two consequences for you:
- A shape change is a vocabulary conversation. If a field is missing, the place to argue for it
is a proposal against the vocabulary, not this tool’s issue tracker. What lands there lands for
every implementer at once,
manni docevalsandmanni meta fillincluded. - Your artifacts are portable. An artifact carrying evals is not locked to this runner.
The schema is published at https://hawkeyexl.github.io/manni/schemas/artifact-evals/1.1.0.json,
and the package carries the same bytes. A finding about the block names
manni:artifact-evals:1.1.0 as its schema. Copy that id into manni meta validate -s and it
resolves.
What a version bump means
Section titled “What a version bump means”manni meta versions these vocabularies with three-segment semver, and each segment is a claim about validation behavior rather than a mood:
| Segment | Means | Example |
|---|---|---|
| MAJOR | a block that used to validate now fails | a new required field, a removed field, a narrowed enum |
| MINOR | a block that used to fail may now pass; every old one still validates | a new optional field, a widened enum |
| PATCH | no validation-behavior change at all | a corrected description |
So a bump tells you directly whether your corpus is at risk: only MAJOR can break an artifact that validates today.
What changed on the way to 1.1.0
Section titled “What changed on the way to 1.1.0”Two renames landed while the vocabulary was still a draft. Both are the kind that fail loudly rather than quietly:
| Was | Is | What to do |
|---|---|---|
metadata.eval-provenance | metadata.meta-provenance | Rename the key. The old spelling is now a reserved-prefix error, not an ignored member. |
severity: info | severity: notice | Rename the value. Severity across the family is error | warning | notice. |
The whole top-level metadata key carries x-manni-location: external. The mark sits on the map,
not on one field, so evals, eval-skip and meta-provenance move as one block. They cannot be
split across an artifact and a manifest. manni meta relocate moves that block into the
external-metadata manifest a collection declares, which leaves the instructions a person reads
uncluttered. tracevals reads the block wherever it lives. Collections and a relocated
block has the rules.
1.1.0 is a MINOR release over 1.0.0. It names the tool-order grader, so the strict overlay
accepts it. Every block that validated at 1.0.0 still validates, and 1.0.0 keeps its bytes.
The provenance rename is worth a second look, because the guard changed under it. The reserved
prefix inside metadata is ^eval-(?!skip$): every eval-* member is rejected except
metadata.eval-skip. eval-provenance used to be the one other exception, so an artifact still
carrying it now fails validation with a pointer to the key.
That is deliberate. A provenance trail silently ignored is worse than one that stops the build, because the criterion axis of the self-preference check reads it. An artifact whose trail went unread would report no bias while the model graded its own assertion.
Separate probes from guards
Section titled “Separate probes from guards”type answers a question that otherwise makes a pass rate meaningless:
| Value | Meaning | A failure means |
|---|---|---|
regression | Protects behavior that already works. | Something broke. Act on it. |
capability | Probes the edge of what the agent can do. Expected to fail sometimes. | Information about where the boundary is. |
- id: handles-nested-monorepo assertion: The session located the correct package in a nested workspace without being told. type: capability grader: aiNeither changes enforcement. A failing capability eval still fails and still contributes to a
non-zero exit code. That restraint is deliberate: an eval that could not fail the build would just be
a comment. The field instead lets you read a report or a history log and know that a red probe
is not a regression.
New evals default to regression, including everything fill writes. That is the conservative
choice, since mislabelling a real guard as a probe is the more expensive mistake.
Roll out a convention in stages
Section titled “Roll out a convention in stages”The transferable technique, whether you are adding a field, a grader convention, or a tightened
assertion: never flip a whole corpus to error at once.
-
Introduce it at
warning. The eval reports on every run and fails nothing:- id: cites-a-test-runassertion: The session ran the test suite before declaring the fix complete.severity: warningnoticeis quieter still, for a convention you want recorded but not discussed yet. -
Measure coverage. Count how many artifacts carry it, and how often it actually fires:
Terminal window npx @hawkeyexl/manni tracevals run <trace> --format json \| jq '[.evalResults[] | select(.evalName == "cites-a-test-run")]| group_by(.outcome) | map({(.[0].outcome): length}) | add' -
Fix the artifacts, not the eval. A rule failing everywhere usually means the instructions never said it clearly. This is the step that does the work.
-
Promote to
erroronce the corpus is ready. Now it gates.
The same ratchet works in reverse for tightening an existing assertion. Add the sharper eval under a
new id at warning, let both run, and remove the old one when the new one is clean.
A malformed block fails loudly
Section titled “A malformed block fails loudly”Rolling a change across many repositories is survivable mostly because breakage is precise. An
invalid block is never silently ignored: it produces an eval named evals-block-valid with a JSON
Pointer to the offending field and a source line number.
ERROR fix-bug › evals-block-valid invalid metadata.evals block: /metadata/evals/1/grader (line 12): must be stringEval entries are closed, so a misspelled field inside one is an error rather than a key that quietly
does nothing. metadata itself must stay open, because it is the host tool’s extension bag. The
eval- prefix is reserved inside it instead, so eval-skpi is rejected the same way, and so is the
retired eval-provenance. When you roll out a convention and something breaks, you get the file,
the pointer, and the line.
Keep ids stable
Section titled “Keep ids stable”Eval ids are the join key for history. A regression is defined as this artifact and this eval id
went from pass to fail. Renaming an eval therefore reads as one eval removed and another added,
and the trend line resets.
This is exactly why id is required on the object form. The retired 0.2 shape let a name be
derived from list position, so reordering entries silently reset every trend line that depended on
them.
Rename when the meaning genuinely changed. Otherwise leave the id alone and edit the assertion.