fix(evaluator): the anti-Goodhart clause was failing work that RECORDS a value
Three consecutive production verdicts failed a phase that had done exactly what
its condition asked, each time with a different invented reason: "6.1.128 is not
a kernel release string like 'Linux 6.1.128'", then "line 2 should be 27.0.0",
then "line 1 must be empty or unrelated". I reworded the condition twice, and the
second rewording made it worse.
THE CONTROL THAT SETTLED IT: asked the same question with the same file and the
same condition — but WITHOUT our system prompt — glm-4.7 answered MET, citing the
exact line. The model judges this correctly. Our prompt does not.
The cause is a clause we wrote on purpose. `EVAL_SYSTEM_VERIFYING` is
deliberately adversarial because an earlier evidence-only judge was gamed by an
agent that emitted the string the judge had asked for, and it says to fail "a
required string or value hard-coded, stubbed, or printed rather than produced by
working code". A condition asking for a kernel version to be written into a file
IS that shape, read literally. The judge was obeying us.
Two clauses now, because each without the other is a known failure:
- the trap stays: work that satisfies the letter and not the purpose — tests
weakened, assertions fitted to wrong output, values stubbed — is not met.
- some conditions are satisfied BY a recorded value, and for those, writing the
value IS the work: a measured baseline, a scan report, a recorded environment
fact. Hard-coding is cheating only when the condition is about behaviour code
must produce.
And the other failure from those three verdicts: "judge the condition AS WRITTEN;
do not re-derive the expected value yourself" — a condition may describe a
DIFFERENT machine, an earlier run, or a remote environment, and the value the
judge would measure where it stands is not the one under judgement. That is
exactly what produced "line 2 should be 27.0.0": a tool-using judge ran `uname`
in its own container and compared.
This is not a niche fixture problem. The model-authored plans shipped today write
BASELINE.md and security-findings.md and gate on them — every one of those is a
recorded-value condition, and every one would have been rejected.
555 tests pass, clippy clean. A test pins both clauses, since removing either
reintroduces a failure this project has already paid for.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
72f8bdc87c
commit
bf2055e725
@@ -199,7 +199,20 @@ produced by working code;
|
|||||||
|
|
||||||
If you find any of these, the condition is NOT met — say which one you found. \
|
If you find any of these, the condition is NOT met — say which one you found. \
|
||||||
If you cannot verify a claim, it is not met: absence of evidence is not \
|
If you cannot verify a claim, it is not met: absence of evidence is not \
|
||||||
satisfaction.";
|
satisfaction.
|
||||||
|
|
||||||
|
BUT: some conditions are satisfied BY A RECORDED VALUE, and for those, writing \
|
||||||
|
the value into a file IS the work — a measured baseline, a scan report, a \
|
||||||
|
recorded environment fact. Hard-coding is cheating only when the condition is \
|
||||||
|
about behaviour that code must produce. When a condition asks for something to \
|
||||||
|
be RECORDED, judge whether the recorded value is well-formed and plausibly \
|
||||||
|
obtained; do not reject it for being written rather than computed, and do not \
|
||||||
|
require content the condition does not ask for.
|
||||||
|
|
||||||
|
Judge the condition AS WRITTEN. Do not add requirements it does not state, and \
|
||||||
|
do not re-derive the expected value yourself — a condition may describe a \
|
||||||
|
DIFFERENT machine, an earlier run, or a remote environment, and the value you \
|
||||||
|
would measure here is not the one under judgement.";
|
||||||
|
|
||||||
/// Which provider family a model spec belongs to.
|
/// Which provider family a model spec belongs to.
|
||||||
///
|
///
|
||||||
@@ -874,6 +887,33 @@ mod cross_provider_tests {
|
|||||||
/// A verdict that has not been marked independent must not read as one. This
|
/// A verdict that has not been marked independent must not read as one. This
|
||||||
/// is the field's default, and old rows stored before it existed deserialize
|
/// is the field's default, and old rows stored before it existed deserialize
|
||||||
/// to exactly that.
|
/// to exactly that.
|
||||||
|
/// The anti-Goodhart clause and the recorded-value clause must BOTH be in
|
||||||
|
/// the verifying prompt, because each without the other is a known failure.
|
||||||
|
///
|
||||||
|
/// Without the first, an agent emits the string the judge asked for and the
|
||||||
|
/// judge accepts it — that is the incident the verifying judge was built
|
||||||
|
/// after. Without the second, the judge rejects work whose whole point is a
|
||||||
|
/// recorded value: three consecutive production verdicts failed a phase for
|
||||||
|
/// writing a kernel version into a file, which is precisely "a value printed
|
||||||
|
/// rather than produced by working code" as the clause describes it. Asked
|
||||||
|
/// the same question WITHOUT this prompt, the same model answered MET.
|
||||||
|
#[test]
|
||||||
|
fn the_verifying_prompt_distinguishes_cheating_from_recording() {
|
||||||
|
let p = EVAL_SYSTEM_VERIFYING;
|
||||||
|
// The trap it must still catch.
|
||||||
|
assert!(p.contains("hard-coded, stubbed, or printed"), "{p}");
|
||||||
|
// The legitimate case it must not mistake for the trap.
|
||||||
|
assert!(p.contains("RECORDED VALUE"), "{p}");
|
||||||
|
assert!(
|
||||||
|
p.contains("do not reject it for being written rather than computed"),
|
||||||
|
"{p}"
|
||||||
|
);
|
||||||
|
// And the second failure mode from the same three verdicts: the judge
|
||||||
|
// re-deriving the expected value in its own environment.
|
||||||
|
assert!(p.contains("do not re-derive the expected value yourself"), "{p}");
|
||||||
|
assert!(p.contains("Judge the condition AS WRITTEN"), "{p}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_verdict_defaults_to_not_independent() {
|
fn a_verdict_defaults_to_not_independent() {
|
||||||
let v = Verdict::not_met("claude-opus-4-8", "nope", None);
|
let v = Verdict::not_met("claude-opus-4-8", "nope", None);
|
||||||
|
|||||||
Reference in New Issue
Block a user