Skip to content

fix: Enhancement: Eval history observability with repo SHA provenance and judge drift markers - #5

Merged
coston merged 5 commits into
mainfrom
operator/4-enhancement-eval-history-observability-w
Aug 31, 2026
Merged

fix: Enhancement: Eval history observability with repo SHA provenance and judge drift markers#5
coston merged 5 commits into
mainfrom
operator/4-enhancement-eval-history-observability-w

Conversation

@coston

@coston coston commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Summary

Multi-model eval comparisons currently only show a single run in isolation, so decision-makers can't tell whether a score change over time reflects the model under test getting better/worse, or the judge/eval regime itself shifting underneath them. This adds run provenance (repo, git SHA, judge/prompt/eval fingerprints), persists cost and duration alongside score for every run, and adds a historical trend view with regime-change markers so drift is explicit rather than hidden.

Changes

  • src/eval/provenance.ts (new) — builds a RunProvenance record per run: runAt, repo (from the origin remote), gitSha, judgeProvider/judgeModel/judgeVersion, judgePromptHash, evalHash, and a comparisonFingerprint hashing all of the above for strict-comparability checks.
  • src/eval/runner.tsrunPrompt now captures the Claude output:cost event and returns cost alongside output, so per-case API cost flows through to TestResult.
  • src/eval/index.ts — threads cost and duration through runEval, attaches provenance to every EvalComparison (single- and multi-model paths), and adds an opt-in --history <path> flag that appends one JSONL record per model/eval to a history log.
  • src/eval/export.ts — JSON/CSV output now includes provenance and cost_usd/duration_ms columns; loadExistingResults restores costUsd from prior CSVs.
  • src/eval/history.ts (new) — appendHistory/loadHistory for the JSONL log, and buildTrends, which groups records by eval+model, sorts by time, supports strict (matching comparisonFingerprint) vs all modes, and flags regime-change points.
  • src/eval/report.tsprintTrends() renders the terminal trend view, printing a regime-change marker line wherever the judge model/version, judge prompt, or eval spec changed since the previous run.
  • src/eval/trend-index.ts (new) — npm run eval:trend CLI entry point (--eval, --mode, --history flags).
  • src/eval/types.ts — adds `RunProvenanc

(output truncated)

Related Issue

Fixes #4


Generated by Operator

coston and others added 4 commits August 31, 2026 12:14
…ombie-reap race)

Three tests failed on GitHub Actions while passing locally:

- Ink checks `is-in-ci` at module load and, in CI, defers all frame
  writes until unmount (and the CI unmount path overwrites the final
  frame with a bare newline in debug mode), so tests asserting on
  frames written to a fake stdout captured nothing. A new shared
  force-non-ci.ts helper — imported first in every ink-rendering test
  file — pins CI="false", which is-in-ci short-circuits on even when
  GITHUB_ACTIONS/CI_* vars are set.

- The process-group timeout test asserted ESRCH on the first signal-0
  probe, but a just-SIGTERMed grandchild can still be an unreaped
  zombie (signal 0 succeeds on zombies), so it raced on Linux. The
  assertion now polls up to 2s for the process to disappear.

Repro for the ink failures: `CI=true npm test` fails identically on
macOS; green after this change both with and without CI=true.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019thBZNhFBYfFP7Dk5cgzMW
An observability feature must never produce records that are confidently
wrong. Four ways it could, found by running it, now fixed:

- A resumed run (cached --output-csv results) appended a history record
  stamped with fresh provenance — new runAt/gitSha/fingerprint — for
  scores produced under the previous run's regime. recordHistory now
  refuses to append when any case was reused, and says why.
- The judge model was asserted, not observed: provenance recorded
  EXECUTANT_MODEL ?? sonnet while the judge ran with no --model flag at
  all, i.e. the user's CLI default. judge.ts now pins the model it runs
  to the exact value provenance records (resolveJudgeModel, shared).
  Likewise the bare single-model path hard-coded the "claude/sonnet"
  series label; it now records the model that actually ran.
- judgeVersion was captured but excluded from comparisonFingerprint,
  though the docs and the regime-marker text claim version changes are
  flagged — a Claude CLI upgrade shifted judging silently. The version
  is now part of the fingerprint.
- One corrupt history line (a half-written append) aborted eval:trend
  entirely; it is now skipped with a warning naming the file and line.

Plus smaller honesty fixes: judgePromptHash now hashes the
header-stripped prompt (what the judge actually receives), evalHash is
insensitive to YAML var-key order, trend flags missing a value now error
instead of silently falling back (a silent --mode strict miss hands the
user non-comparable data), an --eval filter that matches nothing says so
instead of claiming no history exists, and trend group identity no
longer round-trips through the "::" map key.

Every fix carries a regression test (9 new).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019thBZNhFBYfFP7Dk5cgzMW
`npm run eval:trend -- --html <path>` now also renders the history log
as one self-contained HTML file: a leaderboard card per eval (latest
run per model, ranked by score then cost, LOCAL/API badges) with
expandable per-model run histories carrying cost, duration, git sha,
and the same regime-change markers as the terminal view.

Themed with the @coston/design-tokens purple-dark theme the TUI already
uses (src/ui/theme.ts) — the tokens are CSS-native oklch() strings, so
they are injected verbatim as custom properties at generation time. No
build step, no script, no external stylesheet, font, or asset: the file
works offline and can be attached to a PR or CI artifact as-is.

renderHtmlReport is pure (history entries in, HTML string out); the
only I/O is the flag handling in trend-index.ts. HistoryEntry is now
exported for consumers of loadHistory's return type.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019thBZNhFBYfFP7Dk5cgzMW
A 10-bin histogram over each model's latest run per eval, in the card
header — it shows at a glance whether the eval separates models or
everyone clusters at a ceiling. Pure CSS bars (no script, keeping the
single-file guarantee); per-bin counts live in title tooltips and every
score is also in the table, so the strip summarizes rather than carries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019thBZNhFBYfFP7Dk5cgzMW
@coston
coston merged commit ca07460 into main Aug 31, 2026
1 check passed
@coston
coston deleted the operator/4-enhancement-eval-history-observability-w branch August 31, 2026 18:47
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 2.14.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhancement: Eval history observability with repo SHA provenance and judge drift markers

1 participant