Repository navigation
fix(metrics): withhold OutcomeValidity when the final answer was not captured - #37
Merged
isadominguez314 merged 1 commit intoSep 15, 2026
Conversation
…captured A run whose final assistant message was destroyed before the harness read it -- openclaw's sanitizer storing '[Malformed diagnostic JSON redacted]' over it, or an empty capture beside a real trajectory -- still sent that stand-in to the OutcomeValidity judge, which confidently scored it 0.0. That zero is indistinguishable from the agent turning in a bad answer, and on a task with no verification_spec it became the published correctness. OutcomeValidity is the only metric that grades the final text alone (the checklist/safety/grounding judges read the full trajectory case), so it now abstains there: a null score with the reason recorded, skipped by every correctness preference chain, withholding the composite when nothing else measured correctness. An empty output beside an empty trajectory is still judged -- an agent that turned in nothing earned that zero. Observed live: run_20260911_172304_ocvertex-oc, 149 trajectory steps, passing verification, OutcomeValidity 0.0 graded off the placeholder.
This was referenced Sep 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A run whose final assistant message was destroyed before the harness read it still sent the stand-in text to the OutcomeValidity judge, which confidently scored it 0.0. Observed live on
run_20260911_172304_ocvertex-oc(the #32 validation run): openclaw's own sanitizer stored[Malformed diagnostic JSON redacted]over the final message inside its sqlite, the placeholder landed in the record'soutput, and the judge graded it — OutcomeValidity 0.0 on a run with a 149-step trajectory and a passing verification report. On a task with noverification_spec, that false zero becomes the published correctness.What changed
core/run_status.py— new sharedis_placeholder_output(): full-string match on the bracketed redaction family ([Malformed diagnostic JSON redacted],[Oversized diagnostic JSON redacted], and shape-siblings). Anchored to the whole string, so a real answer that merely quotes a placeholder never matches. Lives besideis_unscoreable_run— same "one definition, read by every layer" rationale.metrics/pipeline.py—_build_contextcomputesfinal_output_missing: a redaction placeholder always counts; an empty output counts only beside a non-empty trajectory (there the blank is a capture failure — an empty output beside an empty trajectory is the agent genuinely turning in nothing, and that zero is real and still judged).metrics/outcome_validity.py— when the flag is set the metric abstains instead of calling the judge:{"score": null, "reason": "withheld: …"}plus a warning log. A null score is skipped by the correctness preference chains in bothmetrics/pipeline.pyandresults/normalize.py(verified — no changes needed there), so correctness falls through to the deterministic sources, and when nothing measured anything the composite is withheld: a null row, not a confident zero nobody measured.The abstention is deliberately scoped to OutcomeValidity: it is the only metric that grades the final text alone. The checklist / safety / grounding judges read the combined case that embeds the full trajectory, so they keep real signal even when the final text is gone.
Validation
uv run pytest tests/unit→ 1861 passed (9 new); ruff check + format clean._build_contextcases, and an end-to-end batch test reproducing the live run's exact record shape — asserting the judge is never invoked and no composite is emitted.docs/components/metrics.mdbeside the existing OutcomeValidity notes.Relation to other follow-ups
This is the scoring-side backstop. The parsing-side fix (mapping the placeholder to empty in
openclaw/parsing.pyso theassistant.messagefallback can recover the real text) is a separate, complementary change — once it lands, this layer still protects records where the fallback also comes up empty, and historicalresults.jsonartifacts on re-score.