Repository navigation
feat(reporting): add quantitative skill performance dashboard - #179
chrisknvidia wants to merge 46 commits into
Conversation
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Conflicts resolved: - CHANGELOG.md: kept every entry from both sides. The dashboard Added entry and the two dashboard Fixed entries move into main's new Unreleased section; the released 0.5.0 section stays as on main. - src/skillevaluator/evaluation/tier3_report.py: took main. Main already stops reporting unmeasured token counters as zero (since 0.5.0), with bounds checks and multi-row trial handling; canonical trials omit the token record when prompt or completion is unavailable. - src/skillevaluator/tier3/harbor/report_data.py: took main's _trajectory_token_counter, which returns None for missing or unrepresentable counters and None for missing steps. - tests/reporting/test_report_data_bounds.py: kept main's loader and ATIF v1.7 tests and the PR's canonical-export token test. The PR test now expects main's representation (no token record when prompt or completion is unmeasured); its dashboard, native-recovery and HTML assertions are unchanged. - uv.lock: took main's lock and re-ran uv lock for the PR's dashboard extra (streamlit>=1.49,<2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
Main's canonical Tier 3 report now collapses reward rows that share one physical trial into one logical trial and omits their token counters, and the collector's num_trials counts logical trials (num_reward_rows counts the rows). The dashboard found multi-step trials only through duplicate trial IDs in canonical trials, so after the merge a retained multi-step run without step_results reported the root mirror's tokens as the trial's usage. Count shared reward trial IDs in the retained trial directories, require authoritative step_results for those trials, and keep the ambiguity notice. The test fixture now writes the collector's current summary shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
After the merge, canonical Tier 3 trials take main's representation: they leave out the token record when prompt or completion usage was not measured instead of writing null counters. Update the changelog entry and the dashboard guide to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
Resolve the CHANGELOG.md conflict with #203 by keeping both sides' Fixed entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
Resolve the CHANGELOG.md conflict with #196 by keeping both sides' Fixed entries. uv.lock merged without conflict and `uv lock --check` passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
Resolve the CHANGELOG.md conflict with #190 by keeping both sides' Fixed entries. `uv lock --check` passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
The Windows CI job decodes encoding-less read_text/write_text as cp1252, so every dashboard test file read or write now passes encoding="utf-8". AppTest's default_timeout is a wall-clock budget, and the first st.dataframe in each xdist worker imports pandas and pyarrow inside that timed thread (about 7 s cold against 1.5 s warm). Import them before AppTest and raise the ceiling to 60 s; passing runs are not slower. A cold `-n 8` run of tests/reporting had two AppTest failures that passed on the next run. Also assert that `dashboard` is listed under Core workflows in --help. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
…ime rule Canonical Tier 3 JSON records each single-step trial's agent execution time as `agent_seconds`, but the dashboard only read a per-trial `duration_seconds` that no producer writes. Agent time and its delta were therefore blank for every uploaded, canonical, or validate report. Read `agent_seconds` first; an invalid value stays unavailable rather than falling back, and the legacy field is still read for hand-built payloads. Retained runs now use Harbor's shared `agent_execution_seconds`, which treats timestamps without a timezone as unavailable, so a run's dashboard time matches what its own canonical JSON and HTML report record. Label the column "Agent time" so it is not mistaken for wall-clock duration, add a contract test that builds the canonical payload with the producer and checks the dashboard against its `resources` block, and document the rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
…eport Harbor names each repeated attempt <task>__<random> without an attempt ordinal, so the dashboard treated every --n-attempts run above 1 as unpaired: scores and pass@k were blanked and every comparison was shown as mismatched coverage. The HTML report pairs these runs by matching case counts. Unnumbered attempts now pair by case count when every attempt has its own trial ID, which is the recorded identity of a physical attempt. Missing or shared trial IDs, mixed numbered and unnumbered attempts, and differing explicit attempt sets still fail closed. The dashboard no longer guesses attempt numbers from trial names, which also misread authored case IDs such as retry-attempt1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
… loader bound The report loader caps each JSON artifact at 2 MiB, and Harbor trajectories are bounded by node count, not bytes. One large with-skill trajectory recorded an artifact_loading reason, and the dashboard then treated the whole report as truncated trial coverage and blanked every score, pass@k and comparison. The HTML report keeps those scores, because a trajectory feeds only per-trial tokens and steps, and canonical trials then omit tokens so coverage counts already keep token totals unavailable. Treat trajectory-only loader reasons as telemetry. Any other artifact, a malformed reason list, or a bare truncated marker still blocks comparisons, and the separate trial-count check still catches a missing trial. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
…la escaping Report notices can carry text taken from a shared report, such as agent keys, upload names and catalog paths. Streamlit parses alert bodies as Markdown, so a crafted key could render a remote image or a link. Each notice now renders with st.text under one fixed warning, and the expander counts unique notices instead of every duplicate. The CSV export also missed full-width formula prefixes (for example U+FF1D), which spreadsheets fold to "=". Text cells are now probed after NFKC normalization, and tests cover the CR, LF and NBSP cases too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
…ports Streamlit reruns the whole script on every widget change, so each filter click reloaded every command-line path and re-decoded every upload. On a large retained run that made a filter change take seconds. Command-line inputs are now cached by path plus a cheap stat fingerprint, so a rewritten file or a new entry directly inside a directory is picked up on the next interaction. Deeper edits inside a directory, such as a run still writing trials, appear after the new Reload reports button. Uploads are cached by their widget file ID instead of hashing the bytes. The size check now uses the upload's recorded size, and every message and source label is unchanged. Both CSV download buttons no longer trigger a rerun, and new AppTests cover the upload path, including the 16 MB boundary and invalid JSON. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
The dashboard skipped only inputs whose resolved paths were equal, so natural invocations such as a results directory plus a run inside it, a results directory plus its latest symlink, or a run plus its result.json loaded the same rows twice under the same source. Every pair then read as ambiguous duplicate conditions with blank deltas, and the condition row count doubled. Uploading the same file twice doubled the rows and the run count too. Rows reloaded from the same source, report, agent and condition are now kept once with a notice. That key is unique within one parse, so identical embedded reports in one file are still separate rows, and a CLI path and an upload of the same file keep their distinct sources. Byte-identical uploads are skipped with a notice; the digest is computed inside the cached upload parse, so it runs once per uploaded file. The existing notice-count test loads a directory and a file inside it, so it now also expects the overlap notice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
…eir own row A declared trial count of about 2**64 or more passed the count check, and when the trials could not be keyed it became the row's trial count. The performance table then failed Arrow conversion, so one malformed report blanked every valid report on the page. Counts above one million, far beyond the 512-trial loader bound, are now treated as invalid coverage counts, so that row shows the usual notice with no score or usage. An unreadable trials directory in a retained run raised out of the run loader and dropped every agent and condition in the run. That condition now gets a notice and blank usage totals while the run's other rows load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
…un loader The retained-run loader hard-coded 512 and 4096, the same trial bounds the canonical report loader uses, with nothing tying the two together. They are now named constants, and a test checks that they still match report_data. A repeated directory check that could never trigger is removed, the JSON reader stats each file once, the catalog and discovery limit notices are written once, _set_usage always takes an explicit expected count, and the multi-step ambiguity notice is matched exactly rather than by a copied substring. Messages and behaviour are unchanged. New tests cover the trial discovery limit without creating 513 directories, the local JSON size limit at and just past the boundary, and that a resolved multi-step scope leaves no ambiguity notice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
"incomplete or mismatched coverage" covered several different causes, so users had to read the JSON to learn why a pair had no deltas. A new Comparison reason column, included in every comparison view and in the CSV, names the first failing check: a failed or skipped arm, invalid trial counts, truncated trial data, incomplete coverage in one arm, different cases or attempts, a different dataset or attempt policy, a missing arm, or an arm loaded more than once. comparison_status and the pairing rules are unchanged. The status filter also ran before pairing, so filtering to succeeded rows turned a recorded failed baseline into "missing condition". Status now narrows the performance table only, like the condition filter, and the comparison shows the failed arm as the reason instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
When the filters excluded every row, the comparison tab still told users to load reports with both conditions, although such reports were loaded. It now says that no rows match the filters, as the performance tab does. When pairs exist but none is comparable, an info line points to the Comparison reason column. Coverage cells now read "1 trial" instead of "1 trials". No SkillEvaluator command writes dashboard_metadata.harness_features, so every evaluator-produced run shows features as unknown. When no loaded row records features, a caption says how to add them, and the reports guide states that the field is added by hand. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
The performance table showed only usage totals summed over each row's trials, so a model or harness that ran more trials looked proportionally more expensive. Issue #170 is about comparing models and harnesses, where trial counts can differ. Each row now also carries tokens, agent time, and cost per trial. A total is published only when every expected trial is measured, so dividing it by the expected trial count gives an exact mean, and the per-trial value stays blank whenever the total does. The columns sit right after the score in the table and CSV; the totals remain. Comparison deltas are unchanged because pairs already require identical trial keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
… extra skillevaluator is not published on PyPI, so the pip install command in the missing-extra error and the reports guide could not succeed and invited dependency confusion. Point both at the same uv tool install from GitHub that the installation guide uses, with [all,dashboard] so a reinstall keeps the evaluator extras. List the dashboard extra in the extras table and stop saying [all] installs every extra. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
…ctory and prompts The launcher ran python -m streamlit from the caller's directory, so a streamlit.py there ran instead of Streamlit, a project .streamlit/config.toml could reopen CORS, and the reporting package directory shadowed stdlib html. Start Streamlit with -P from a dedicated dashboard_ui directory, restrict WebSocket Host headers to loopback, and pin CORS and XSRF protection. An explicit headless=false made Streamlit ask for an email and exit 255 without a TTY, so headless is now set only for --no-browser and the prompt is off. The file watcher banner and developer toolbar are switched off too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
The launcher always passed --server.port=8501. Streamlit treats a port on the command line as manually set and exits with status 1 when it is busy, so a second dashboard or any other Streamlit app on 8501 made the plain command fail. Pass the port only when the user chose one, so Streamlit moves to the next free port otherwise, and print a hint when an explicit port fails. The --port option also gains help text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
Ctrl+C reaches the whole process group, but a SIGTERM sent only to the launcher, as an IDE stop button, a process supervisor, or kill PID does, killed the launcher and left Streamlit running and holding the port. Run Streamlit with Popen and forward SIGTERM and SIGHUP to it while waiting, so the launcher stays alive to report the exit status and the port hint. After Ctrl+C the launcher waits up to 10 seconds for Streamlit to stop before terminating it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
The Unreleased Fixed entry for omitting unmeasured token usage describes behaviour main shipped in v0.5.0 (#83), and this PR changes no producer; the second Fixed entry describes the dashboard, which is itself new. Fold both into one Added entry that names what the dashboard compares, where time and cost come from, and how inputs are cached. In the reports guide, describe a performance row and the pairing rule precisely (same report and agent, dataset digest and attempt policy), drop the advice to regenerate canonical JSON (no command does that), describe multi-step trials as v0.5.0 canonical JSON writes them, document the agent_result token fallback, and list the accepted harness_features forms and their precedence. Two tests pin the precedence and the accepted forms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
Follow-up enhancements and fixes (pushed to this branch)@chrisknvidia: I merged main into the branch, then added 18 follow-up commits from a review and enhancement pass. Every commit is signed off. The full local suite passes (11,861 tests); the only 2 failures are sidecar tests that fail the same way on main in my environment. Head is Merges
Correctness
Performance
Safety
UX
Install and docs
Hygiene
Deferred, not in this PR
A full inline review of the whole PR follows separately. |
rng1995
left a comment
There was a problem hiding this comment.
Automated review: 14 findings (0 P1, 5 P2, 9 P3); 14 posted inline.
…ccurately The reports guide said every loader truncation other than an oversized trajectory blocks comparisons, but truncation limited to diagnostic details leaves pairs comparable with a notice, as a test already pins. The CHANGELOG entry placed per-trial means among the paired differences; they appear only in the performance table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
…coverage When a canonical condition listed fewer trials than num_trials, the usage coverage denominator added the shortfall on top of the declared count, so 9 of 10 trials read "tokens 9/11". Trials missing from the declared count are now counted once; totals still stay unavailable until every trial is measured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
…ned runs Retained runs were parsed through the canonical builder with its 2 MiB embedded-report budget, so a large multi-agent run lost every non-best agent's trials and showed "0 trials" with no score. The builder now takes enforce_embedded_size (default True for every other caller), and the dashboard turns it off because it never embeds the payload; loader truncation is still recorded. Canonical JSON that was pruned this way still fails closed, but the row now shows the declared trial count and the comparison reason says the trial details were pruned and to load the retained run directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
…nreadable A malformed, oversized, or symlinked trajectory.json made the whole retained trial unmeasured, dropping the agent time and cost that result.json records and the documented agent_result token fallback. The canonical export keeps that time, so the retained run showed less than its own export. Now only the trajectory is skipped, with a per-trial notice, and tokens fall back to agent_result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
…g.json The guide says to add dashboard_metadata to run_config.json by hand, but the loader read that file only when result.json had no embedded run_config, and every real run embeds one, so hand-added features stayed "unknown". The run directory's run_config.json now wins; the embedded copy is used, with a notice, only when the file cannot be read. The test fixture now embeds run_config in result.json the way the runner does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
…vailable Canonical exports written before v0.5.0 recorded missing token counters as zero, and the dashboard showed those zeros as measured and published token differences against them. When a report's evaluator_version is earlier than 0.5.0, zero token counters are now unavailable and the source gets a notice pointing to the retained run directory. Missing or unparsable versions keep their values. Retained runs read counters from disk and do not show the notice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
A directory input is cached on the directory's own fingerprint, which does not change when a file inside it is rewritten in place. When the directory came before that file on the command line, dedupe_rows kept the directory's stale rows and dropped the file's fresh ones. Load the deepest inputs first so the explicitly named file wins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
A directory input with a subdirectory the user cannot search (such as a root-owned trial directory from a container run) raised PermissionError from the native-run probe, and main() let it replace the whole page with a traceback. The directory walk now records a notice for that subdirectory and keeps going, and main() turns any unexpected load error for one input into a notice so the other inputs still render. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
The long-attempt test only set trial_id, which stopped carrying attempt numbers when attempt pairing moved to the attempt and attempt_index fields, so it passed regardless of how attempts were normalized. It now sets a 5000-digit attempt with leading zeros on one arm and checks that it pairs with the same value, and not with a different one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
The directory-walk test created a symlink without the OSError guard the other symlink tests use, so it errored on Windows hosts without symlink rights. The malformed and missing-input checks now live in their own test, and the symlink test skips when the host cannot create one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
The launcher limits browser connections to 127.0.0.1 and localhost through server.allowedHosts, which Streamlit added in 1.61. Older releases ignore the setting without a warning, so the dashboard extra now requires 1.61. A new test checks that every setting the launcher passes exists in the installed Streamlit, and the packaging test pins the new floor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
When a signal killed Streamlit, the launcher passed the negative return code to SystemExit, so a forwarded SIGHUP exited 255 and SIGKILL 247. The launcher now exits 128 plus the signal number (129 and 137), as a shell does, and the busy-port hint stays limited to Streamlit's own errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
…ts it The launcher assumed Ctrl+C always reached the whole process group. A SIGINT sent to the launcher's pid alone left Streamlit serving the port for 10 seconds until it was terminated. The launcher now sends SIGINT to Streamlit before waiting, outside Windows. A new test covers the timeout path that terminates a Streamlit that does not stop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
Review follow-upI posted 14 verified inline findings (5 P2, 9 P3). All are fixed, replied to, and resolved, one commit each (the two docs findings share one). Head is now
@chrisknvidia, three decisions are yours:
|
Resolve the THIRD_PARTY_NOTICES.md conflict with #77 by keeping the dashboard row in the dependency table and main's new Unicode security data section. `uv lock --check` passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
…ompacted Since #77 the embedded-size budget compacts a non-best agent's conditions (status, errors and attempt counts) instead of dropping them, so requiring empty conditions no longer recognized a pruned agent. Its canonical row then read "0 with / 0 without" with a generic reason. Detect pruning from the truncation marker and the emptied trial lists only, which covers both report shapes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
The launcher tests replaced importlib.util.find_spec for every module with a
stub that returned a bare object(). Under coverage, a sys.path change makes
coverage look up the measured package through find_spec while the CLI runs;
it got the stub, raised AttributeError ('object' has no attribute 'origin'),
and the command exited 1. The Python 3.12 CI job (serial, with coverage)
failed test_launch_uses_loopback_and_literal_absolute_paths and
test_explicit_port_failure_has_hint this way. Fake only the "streamlit"
lookup, with a real ModuleSpec, and pass every other name through.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
Merge #124 cleanly. `uv lock --check` passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
Summary
Closes #170.
Saved reports have to be inspected one at a time to compare agent skill quality and resource usage across models and harnesses. This PR adds an optional Python/Streamlit dashboard,
skillevaluator dashboard, that loads canonical Tier 3 JSON, consolidated reports, local catalogs, or retained Tier 3 run directories. It shows:Measurement rules:
agent_seconds(fix: target dependency coverage and Tier 3 result integrity #196) or from retainedagent_execution, under the same timezone-aware rule main uses. Cost appears only when a retained run recordsagent_result.cost_usd.Loading and safety:
python -P. It disables usage telemetry and the first-run email prompt, uses the next free port unless--portis given, and forwards SIGTERM/SIGHUP to Streamlit.The dashboard reads saved outputs only. It never starts evaluations or estimates prices.
Changes
skillevaluator dashboard [PATHS...] [--port N] [--no-browser]under Core workflows;dashboardoptional extra (streamlit>=1.61,<2; 1.61 is the first release that honours the allowed-hosts setting)reporting/dashboard_data.py: normalization, pairing, fail-closed totals, bounded loaders, row dedupereporting/dashboard_app.py(cached loading, filters, tables, CSV) andreporting/dashboard_ui/skillevaluator_dashboard.py(isolated Streamlit entry script)reporting/dashboard.pydocs/reports.mdx;docs/cli-reference.mdx;dashboardrow in thedocs/installation.mdxextras table; CHANGELOG; third-party noticesFollow-up commits on this branch
After the original five commits, the branch was merged with main (latest d891b69) and got 18 follow-up commits from an enhancement pass, then 13 more that address a 14-finding inline review (all threads resolved), a fix for #77's compacted pruned-agent conditions, and a CI-only test fix. Their details are in the PR comments and review threads.
Verification
Signed-off commits; documentation, changelog and third-party notices updated.
Final head
2c0c013(main d891b69 merged): all 17 CI checks pass, including Tests (Python 3.12 and 3.13), Tier 2 on Windows and macOS, DCO, Gitleaks and CodeQL. Earlier, at4a945f8, two launcher tests failed only in the serial Python 3.12 coverage job, because they stubbedfind_specfor all modules;92e6219stubs only the Streamlit lookup. Local full suite (Python 3.13, Streamlit 1.65) at4a945f8: 13,090 passed, 22 skipped. There were 6 local failures, all outside the dashboard: 2test_harbor_secure_docker_environmentsidecar tests that fail the same way on main in this environment, and 4 bridge/preflight timing tests that pass on rerun. After the last merge, 5,554 reporting/CLI/evaluation/Tier 3 tests passed.Dashboard and CLI gate
tests/reporting tests/test_cli.py tests/golden tests/test_oss_packaging.py: passed. Includes producer-contract tests that run main's canonical exporter and check that the dashboard's totals and deltas match the report'sresourcesfor 1 and 3 attempts.ruff check src tests,ruff formaton the PR files,uv lock --check,git diff --check, andfern check(no errors; the authenticated redirect check is skipped without a Fern token).Launcher behaviour checked live against Streamlit 1.65:
streamlit.pyin the current directory no longer runs;.streamlit/config.tomlcan't open CORS;Original author verification at
7b0448f:These were not all re-run after the follow-up commits; the automated suites above were.
No credentials, private datasets or proprietary benchmark content added.
No provider-backed agent evaluation or billing request ran. Physical mobile/Safari devices, assistive technology, and AppTest upload support on the Streamlit 1.61 floor were not tested; the upload tests skip if AppTest lacks
file_uploader.Deferred follow-ups
dashboard_metadata.harness_features, which would make those columns populate without hand edits.Outstanding review gate
The original author asked for dedicated Bugbot and Security Review passes, which were unavailable in their session. A multi-area review with adversarial verification has since run on this branch (see the review threads), but it does not replace those specific passes if they are still required.
Release Impact
CHANGELOG.md🤖 Generated with Claude Code