Skip to content

fix(metrics): withhold OutcomeValidity when the final answer was not captured - #37

Merged
isadominguez314 merged 1 commit into
pradeepvrd:integrationfrom
isadominguez314:fix/report-empty-extraction
Sep 15, 2026
Merged

isadominguez314 merged 1 commit into
pradeepvrd:integrationfrom
isadominguez314:fix/report-empty-extraction

Conversation

@isadominguez314

@isadominguez314 isadominguez314 commented Sep 14, 2026 •

Copy link
Copy Markdown
Collaborator

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's output, 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 no verification_spec, that false zero becomes the published correctness.

What changed

  • core/run_status.py — new shared is_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 beside is_unscoreable_run — same "one definition, read by every layer" rationale.
  • metrics/pipeline.py — _build_context computes final_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 both metrics/pipeline.py and results/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.
  • New tests cover the predicate edges (both observed placeholders, whitespace, quoted-inside-an-answer, empty/non-string), the four _build_context cases, 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.
  • Behavior documented in docs/components/metrics.md beside 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.py so the assistant.message fallback 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 historical results.json artifacts on re-score.

…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant