Repository navigation
feat(evals): case-level live board, per-case logs and a run summary - #3090
Merged
Merged
Conversation
Suite rows all share the task name (agent/hardbenchmark), so the board keyed by task × model collapsed every in-flight case into one flipping row. Progress events now carry a row key, the case (short id, domain, question) and the trial, and the board draws one row per case in flight with its phase (session → agent → verify, reported through an AsyncLocalStorage row context), elapsed time, a queue line and the last few finishes with their outcome kind and session URL. Every case writes its own log file under .trajectories/<run>/logs/; -v streams all of them above the board, --follow <id> one case, and the v key cycles off → all → one during a run (esc stops, ? shows keys, in the REPL and argv modes). Non-TTY output is one line per event. --json writes only the summary to stdout. The summary reports suite · runs · duration · tokens · cost, a per-cell table (pass rate, max_turns, sdk_error, gated, ungraded, retried), infra failures with session URLs, and exits 1 with a banner when the judge graded nothing. README screenshots are rendered from the real renderer.
|
This was referenced Oct 1, 2026
Contributor
There was a problem hiding this comment.
2 issues found across 40 files
Confidence score: 3/5
caseIdentity.tscan give cases from different suites the same log file and summary name when dataset IDs overlap. Include the suite or task name in both identities.parse.test.tsduplicates the suite list frombenchSuites.ts, so adding a benchmark can break this test or require a manual, order-sensitive update. Build the expected message from the shared suite list.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/evals/framework/caseIdentity.ts">
<violation number="1" location="packages/evals/framework/caseIdentity.ts:66">
P2: This case label is not unique across suites, so overlapping dataset IDs cause separate rows to share a log file and summary name. Include the suite/task name in the derived log and display identity.</violation>
</file>
<file name="packages/evals/tests/tui/parse.test.ts">
<violation number="1" location="packages/evals/tests/tui/parse.test.ts:165">
P3: This test duplicates the suite list that benchSuites.ts is meant to own, so adding a benchmark to BENCH_SUITE_NAMES breaks this test and forces a manual, order-sensitive update here. Build the expected message from the registry instead: `new RegExp(`Supported: ${listBenchSuites().join(", ")}\.`)`, importing `listBenchSuites` from `../../framework/benchSuites.js`.</violation>
</file>
Architecture diagram
sequenceDiagram
participant CLI as CLI / REPL
participant Runner as Bench Runner
participant RC as RowContext (AsyncLocalStorage)
participant RowLog as RowLogWriter
participant Harness as Harness / Agent
participant BB as Browserbase
participant Verifier as Verifier / Judge
participant TUI as TUI Renderer / Board
Note over CLI,Runner: Run lifecycle with per-case identity and logs
CLI->>Runner: runEvals(options)
Runner->>Runner: generate run token + logDir
loop Each testcase
Runner->>Runner: rowKey (cell + case + trial)
Runner->>RC: runInRowContext(rowContext)
RC->>RowLog: RowLogWriter (per-case file)
alt Provider slot available
Runner->>Harness: executeBenchTask(input)
Harness->>RC: reportRowPhase("session")
Harness->>BB: launch browser session
BB-->>Harness: session URL
Harness->>RC: reportRowPhase("agent", {sessionUrl})
Harness->>Harness: run agent with tool surface
Harness->>RC: reportRowPhase("verify")
Harness->>Verifier: grade external trajectory
Verifier->>RC: verify phase
else Provider throttled/queued
Runner->>TUI: onQueue (queued count)
Note over Runner,TUI: Row stays queued, never takes board row
end
Note over Harness,RC: Log lines from EvalLogger or console
Harness->>RC: logToRow(entry)
RC->>RowLog: write entry to per-case log file
RC->>TUI: onProgress log event (if verbose)
alt Retry on 429 / backpressure
Runner->>RowLog: attempt(n) marker
Runner->>Harness: retry execution
end
RowLog->>Runner: close() + logPath
Runner->>TUI: started/passed/failed with rowKey, case, outcome
end
Note over Runner,TUI: Live board rendering
TUI->>TUI: onStart (one row per case in flight)
TUI->>TUI: onPhase (session → agent → verify)
TUI->>TUI: p75 elapsed threshold (yellow highlight)
alt TTY animated
TUI->>CLI: keys (esc/v/?)
CLI->>TUI: onKey / setStopping
else JSON mode
Runner-->>CLI: --json summary
CLI->>TUI: stderr only
end
Note over Runner,TUI: End-of-run summary
Runner->>Runner: buildRunSummaryJson()
Runner->>TUI: renderRunSummary
alt Dead judge (graded 0)
TUI->>CLI: red banner + exit 1
else Normal completion
TUI->>CLI: cell table + failures + links
end
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Log file names keep the model's provider (openai/foo and anthropic/foo are different rows), and RowLogNames gives colliding rows in one run (the same case id in two suites, two tool surfaces of one cell) ~2, ~3 suffixes. A filesystem error disables the row's file instead of throwing. EvalLogger.error() defaults row entries to level 0 and keeps the serialized stack. The console-routing test now uses an echoing logger so it can fail.
Under --json the board draws on stderr, so it now takes its width from that stream (falling back to stdout's when it isn't a terminal). A failed row with no outcome (aborted while queued) shows its error in the recent list. Summary failure rows collapse multi-line reasons to one line and move the session URL to its own line when it would push the row past the terminal width.
…e hand-off Ctrl/Meta chords no longer reach the live board's key handler, so Ctrl+C stays on readline's SIGINT path. Clearing input typed during a run jumps to the end of the line before Ctrl-U so nothing right of the cursor survives. The welcome hand-off runs through the same muted dispatch as a typed command, so v / ? aren't echoed onto its board.
…dth test With --model, the constants line no longer repeats the model the 'Model override:' line already shows. Restores the run-header test for providers.<p>.concurrency (dropped in this branch), now also checking that EVAL_PROVIDER_CONCURRENCY wins over the config width and that the config widths are forwarded to the runner.
discovery.ts kept its own copy of the agent suite names, so a suite added to benchSuites.ts would parse and plan but never be discovered. It now reads listBenchSuiteTaskNames(), the registry test pins the concrete list and checks discovery against it, and the parse test builds its expected message from the registry.
Phase contract tests: the stagehand bench runner reports session then agent, and external harnesses report agent after the adapter is prepared (both fail if the report is removed). The summary test now checks a row's logPath reaches collectFailures and the --json payload. README and run help no longer claim a log file per case regardless of output, 'retried' is described as a retry count rather than a failure kind, and the live board is described as linking failures' session URLs.
# Conflicts: # packages/evals/README.md # packages/evals/tests/tui/parse.test.ts # packages/evals/tests/tui/run.test.ts # packages/evals/tui/commands/parse.ts # packages/evals/tui/commands/run.ts
miguelg719
added this pull request to stack #3091
October 1, 2026 19:27
# Conflicts: # packages/evals/framework/benchHarness.ts # packages/evals/tests/framework/benchHarness.test.ts
akeimach
approved these changes
Oct 9, 2026
miguelg719
added a commit
that referenced
this pull request
Oct 12, 2026
## Stack 2 of 4 in the evals TUI overhaul. Base: #3087. #3087 concurrency · **→ #3088** · #3089 doctor · #3090 run experience ## Summary **Personal settings no longer go in the tracked config file, and there is now one `.env` loader.** **Config v2** - `evals.config.json` gains `version: 2` and the sections `defaults.harness` / `defaults.successMode`, `benchmarks.<suite>.limit`, `harnesses.<h>.{models,tool}`, `providers.<p>.concurrency`, `verifier.{model,maxUnverifiableCriteria}` and `campaign.tag`. - **Env twins:** every key in the new sections has one, and config values resolve to env overrides only when the twin is unset (`flag → env → local → tracked → default`). - **`defaults.*` keeps its existing order:** flag, then local, then tracked, then the ambient `EVAL_*` vars. The help, `evals config` and README now state both rules. - v1 files load unchanged. - **`evals.config.local.json`** (gitignored) holds personal overrides. `evals config set …` and the first-run marker write there. `--shared` edits the tracked file and can't leak local overrides into it. - New subcommands: `config harnesses | providers | verifier | campaign`. - **Dist migration:** `build-cli` moves a pre-v2 dist config's personal edits into `dist/cli/evals.config.local.json`. Values equal to the old tracked defaults are not treated as edits, so dist installs pick up the new `concurrency: 3` instead of pinning the old 10. ``` $ evals config providers set openai concurrency 6 ✓ Set providers.openai.concurrency to 6 (evals.config.local.json) $ evals config set harness codex ✓ Set harness to codex (evals.config.local.json) $ evals config Configuration: env local trials 3 concurrency 3 api false verbose false model (default per category) harness codex (local) successMode (outcome) providers (local overrides) openai {"concurrency":6} Precedence: flag → env → evals.config.local.json → evals.config.json → default Files: …/packages/evals/evals.config.json + evals.config.local.json ``` **Env** - `loadEvalsEnv()` (`evalsEnv.ts`) replaces the two inline `dotenv.config` calls in `cli.ts`, with the same order: the cwd `.env` first, then `packages/evals/.env`, and shell exports always win. - It records which file supplied each key and which keys were shadowed; the doctor reports that in #3089. - The welcome wizard's `promotePackageEnv` is removed: with one loader it could never fire. **Run header and preflight** - Missing Browserbase credentials fail once before planning, naming the missing keys, instead of failing per task inside session creation. - Bench runs print the judge model (and whether config set it), the success mode and whether trajectories persist: ``` Env: BROWSERBASE Harness: codex Tool: stagehand_facade Concurrency: 6 global · openai 6 Judge: google/gemini-3.5-flash Success: outcome Trajectories: on (.trajectories) ``` ## Test plan - [x] `vitest packages/evals/tests`: 866 passing. New or updated: `config.test.ts` (merge, diff, `--shared` isolation, v1 read), `evalsEnv.test.ts`, `parse.test.ts` (config sections → env overrides, env twins win), `run.test.ts` (Browserbase preflight, config widths in the header), and `cli.test.ts` (writes land in the local file; the tracked file is untouched). - [x] tsc, `fmt:check` - [x] Ran `evals config …` against a scratch worktree (output above). <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Personal settings now live in a gitignored local config file, and the CLI loads `.env` through one loader, so the doctor reports exactly what the runner loaded. **Config v2** - `evals.config.json` becomes v2; `evals config set` writes personal overrides to the gitignored `evals.config.local.json`, and `--shared` edits the tracked file without leaking local overrides; `--shared` is rejected for `core`/`tracing`, which stay personal-only. - New sections (`defaults.harness`/`successMode`, `benchmarks`, `harnesses`, `providers`, `verifier`, `campaign`) only apply when their env twin is unset; `EVAL_SUCCESS_MODE` and `STAGEHAND_BROWSER_TARGET` beat the config by exception. - New subcommands: `config harnesses | providers | verifier | campaign`; `config path local` prints the gitignored file. - Validation tightened: integers must be safe (`maxUnverifiableCriteria` accepts 0), judge ids need provider and model segments, `harnesses.<h>.models` is rejected for harnesses that pick models per task category (e.g. `stagehand`), and a malformed local `defaults` fails loudly. - v1 configs load unchanged; `build-cli` migrates pre-v2 personal edits from `dist` into the local file without pinning old defaults. **Env and run header** - `loadEvalsEnv()` replaces the two inline `dotenv.config` calls, loading cwd `.env` first, then `packages/evals/.env`, with shell exports always winning; it records provenance for the doctor. - Missing Browserbase credentials fail once before planning instead of per task; bench run headers print the judge model, success mode, trajectory persistence, and the per-provider concurrency widths that actually gate the run. <sup>Written for commit 25519c2. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/3088?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="View guided diff" src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a> <a href="https://www.cubic.dev/action/auto-fix/pr/browserbase/stagehand/3088?returnTo=https%3A%2F%2Fgithub.com%2Fbrowserbase%2Fstagehand%2Fpull%2F3088&source=description" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"><img alt="Turn on auto-fix" src="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
miguelg719
added a commit
that referenced
this pull request
Oct 12, 2026
## Stack 3 of 4 in the evals TUI overhaul. Base: #3088. #3087 concurrency · #3088 config v2 + env · **→ #3089** · #3090 run experience ## Summary **`evals doctor --harness a,b [--probe]`** prints a probe matrix with one row per harness, plus the judge, Browserbase and the env files. Each failing row prints the exact command that fixes it. - **Per harness:** binary or runtime resolvable, provider key present, key validity (with `--probe`: one request to the provider's models endpoint, so a dead key shows up here instead of as a silent `sdk_error` mid-run), and one extra per harness: `CODEX_HOME` writable for codex, Node heap for the in-process harnesses (mastra, pi) at concurrency ≥ 5. fx and cursor are listed as not probed yet. - **Judge:** the resolved verifier model and its key. With `--probe` it also checks that the model is still served; a retired judge would otherwise fail every criterion and make the run exit as dead-judge. - **Browserbase:** both credentials. With `--probe` it also looks up the project with the key (401/403 means the key was rejected, 404 means the project wasn't found). - **Env files:** which `.env` files were loaded and which keys they shadowed (from the #3088 loader). A shadowed key always warns. - **Verdict:** only requested harnesses (`--harness`, or `defaults.harness`) can fail it, and when one is requested, the judge and Browserbase rows can too. The rest of the matrix is informational. - **`--probe`** is now a public, documented flag. It was hidden before.  ## Test plan - [x] `vitest packages/evals/tests`: 882 passing. New: `doctorHarnesses.test.ts`, with probes stubbed and covering each row kind, verdict narrowing and fix hints. - [x] tsc, `fmt:check` <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds a probe matrix to `evals doctor` that checks each harness's binary, provider keys, key validity (with `--probe`), and harness-specific extras, plus the judge model and Browserbase project. Each failing row prints the exact command that fixes it, and `--probe` is now a public, documented flag. **Details** - `evals doctor --harness a,b [--probe]` probes every executable harness (stagehand through gemini_cua): binary or runtime resolvability, key presence for every provider in the harness's model matrix (in the planner's order), key validity via one request to each provider's model endpoint, and extras such as heap size for in-process harnesses or `CODEX_HOME` writability for codex. `fx` and `cursor` are not probed yet. - Also checks the resolved verifier model (env or config, and with `--probe` that it's still served on each provider), both Browserbase credentials plus project lookup with `--probe` (a rejected key reads as 401/403, an unknown project as 404, and 429/5xx as a rate limit or outage to retry rather than a bad key), and reports which `.env` files were loaded and which keys they shadowed. - Only requested harnesses (`--harness`, `defaults.harness`, or `--harness all` to make every row count) can fail the verdict; when one is requested, the verifier and Browserbase rows can too. Everything else is informational. Shadowed env keys always warn. <sup>Written for commit 329b2ae. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/3089?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="View guided diff" src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a> <a href="https://www.cubic.dev/action/auto-fix/pr/browserbase/stagehand/3089?returnTo=https%3A%2F%2Fgithub.com%2Fbrowserbase%2Fstagehand%2Fpull%2F3089&source=description" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"><img alt="Turn on auto-fix" src="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
3 tasks done
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.
Stack
4 of 4 in the evals TUI overhaul. Base: #3089.
#3087 concurrency · #3088 config v2 + env · #3089 doctor · → #3090
Summary
A 6-wide HardBench run used to render as one row that kept flipping between running, passed and failed. Every suite names all its cases
agent/<suite>, and the board keyed rows by task × model, so every in-flight case and every trial overwrote the same row. Concurrency looked like 1 whatever-csaid.Before (6 in flight):
After:
Live board
id|task_id,web|websiteandques|confirmed_task(framework/caseIdentity.ts).id domain · question · phase · elapsed.session→agent→verify) are reported from inside the harness and target code through an AsyncLocalStorage row context (framework/rowContext.ts).running · queued · openai 6/6 · throttled). Queued rows are counted there and never take a row.max_turns,sdk_error,gated,ungraded) and session URL.escstop,vlogs off → all → one,?help. They work in argv mode and the REPL, and the REPL no longer echoes keystrokes onto the board.Logs
.trajectories/<run>/logs/<id>-<site>__<provider>-<model>.log. Default runs used to drop all output.-vstreams the lines above the board, attributed by case id;--follow <id>streams one case.--jsonwrites only the summary object to stdout; the header, board and logs go to stderr.Summary
max_turns,sdk_error,gated(the judge passed it, a deterministic gate such asno_browser_usefailed it),ungraded,retried.Also
evals run --helplists each harness's tool surfaces (default starred) and the suites, from a registry (framework/benchSuites.ts) that parse and the planner now share.--previewheader lists every constant column, so a single-cell run still names its tool surface.run.gif.Notes
ProgressRendererand summary code with fixture events and a fixed clock (ANSI → HTML → Playwright), not mocked up.CODEX_HOME) touchesbenchHarness.ts,runner.tsandrun.test.ts. A trial merge conflicted in 6 hunks, all mechanical.Test plan
vitest packages/evals/tests: 954 passing. New:caseIdentity,rowLog,runSummary,progress(board layout, p75 colouring, non-TTY output,--jsonrouting to stderr),resultsSummary,benchSuites,help. Also extendedrunnerProgressEvents(distinct row keys across cases × trials, phases landing on the right concurrent row, per-row log files) andrun.test(vcycling,--follow, dead judge exit 1).fmt:checkevals run b:hardbenchmark --harness codex --tool stagehand_facade -e browserbase: the board showed one row per case with phases and the queue line. (That run's facade calls were cancelled by a local~/.codex/config.tomlapprovals_reviewersetting; Apply the shared eval contract across existing harnesses #2903's isolatedCODEX_HOMEfixes that and it's unrelated to this PR.)Summary by cubic
The evals run experience gets a case-level live board, per-case log files, and an end-of-run summary. A suite run used to collapse every in-flight case into one row (all cases share the task name
agent/<suite>), so concurrency looked like 1 regardless of-c; now each case in flight gets its own row.session→agent→verify) and elapsed time, and list the latest finishes with their outcome kind and session URL; queued rows only appear in the status line..trajectories/<run>/logs/;-vstreams all case logs above the board,--follow <id>streams one, and thev/esc/?keys work in both the REPL and argv modes. Filenames keep the model provider and get~2/~3suffixes on collision; a filesystem error disables that row's file instead of failing the row.max_turns,sdk_error,gated,ungraded,retried), infra failures with session URLs, and a dead-judge banner (exit 1) when the judge graded nothing.--jsonemits only the summary to stdout; non-TTY output is one line per event with no cursor escapes. The board draws on stderr and sizes to that stream, Ctrl/Meta chords stay off it so Ctrl+C still interrupts, and the preview header names an overridden model once.evals run --helplists each harness's tool surfaces and the suites from a shared registry that discovery also reads, so a listed suite always gets discovered. The README run section is rewritten around screenshots rendered from the real renderer, replacingrun.gif.Written for commit be4fc36. Summary will update on new commits.