HealthBench Evals: a desktop app built with spec-driven development

Rebuilding my HealthBench tooling as a cross-platform Flutter app, to test spec-driven development with an AI agent against a known objective.

  • flutter
  • llm-evals
  • healthbench
  • spec-driven-development
  • ai-assisted

This project had two goals. The visible one is a desktop app that makes running and exploring HealthBench evaluations easy. The real one was an experiment: how far can spec-driven development (SDD) with an AI coding agent go when the objective is already well understood?

I had a good reference point. My earlier Python harness, later merged into a local web dashboard, already defined exactly what correct behaviour looks like: same dataset, same grading procedure, same scoring formulas. That makes it a fair test. The new app either reproduces the reference results or it doesn't.

Code, specs and releases: carlos-dev-research/simple-desktop-healthbench.

A finished run in HealthBench Evals: run score 0.880 ± 0.033 over 50 conversations, summary tiles, a score histogram and the conversation list

Spec first, code second#

No code was written at the start. The first part of the work went into building the specification with the AI agent:

  1. Research. We explored the reference repo, the HealthBench paper and dataset, and the Flutter desktop toolchain, and wrote the findings into reference documents.
  2. Constitution. We agreed on twelve project principles, including spec first, scoring fidelity, privacy and secrets, run-file integrity and cross-platform from day one.
  3. Features, one at a time. Each feature went through the same pipeline, with a human review at the end of every phase, before the next feature started:
flowchart LR
  S[specify] --> C[clarify] --> P[plan] --> T[tasks] --> I[implement] --> V[verify]

Six features were built this way, each with its own spec, plan and task list in the repo:

#Feature
001Project scaffold and project-rule tests
002Evaluation core: dataset parsing, API client, grading, scoring, run files
003Evaluation UI: new run, live progress, results
004Run-file management: export, import, delete, continue
005Dataset management: verified downloads, sampling, library
006Score statistics: median, standard deviation, bootstrap uncertainty

A CLAUDE.md file records the rules the agent followed: read the reference documents before any feature, keep the scoring formulas exact, never write API keys to disk, and stage only the files that changed.

What the app does#

  • Datasets. Download the official HealthBench files (main, Hard and Consensus). Each download is verified against its known size, SHA-256 and conversation count. You can also add your own files and make reproducible random samples (same size and seed, same sample).
  • New run. Point it at any OpenAI-compatible endpoint (OpenAI, Groq, Ollama, vLLM, LM Studio), choose the model under test and the grader, and start. The API key stays in memory only and is never saved.
  • Live progress. See conversations done, criteria graded, the score so far and the estimated time left. Stop finishes the current conversation cleanly, and Continue later grades only what's missing.
  • Results. Summary tiles, a score histogram, and every conversation with the model's reply and each criterion's verdict and explanation.
  • Run files. Each run is a self-contained, append-only file that can be exported, imported or deleted, and read without the original dataset.

The Datasets view: HealthBench Hard and Consensus downloaded and verified, with a library offering Sample… and Delete

A run in progress: the New run form with the API key hidden, and a progress panel showing 11 of 50 conversations graded, elapsed and remaining time, and the score so far

Uncertainty, not just a score#

The earlier harness reported a single number. The app also shows how much that number can be trusted. Alongside the median and the standard deviation of conversation scores, it reports a bootstrap standard deviation of the run score. It resamples the conversation scores with replacement 1,000 times, scores each resample the same way as the run (mean, clipped to [0,1][0, 1]), and takes the standard deviation of those 1,000 scores:

run score±std⁡({clip⁡(xˉb∗, 0, 1)}b=11000)\text{run score} \pm \operatorname{std}\Big(\big\{\operatorname{clip}(\bar{x}^{*}_b,\,0,\,1)\big\}_{b=1}^{1000}\Big)

The resampling uses a seeded generator, so the same run always shows the same uncertainty. It was checked against an independent Python implementation. On a 50-conversation run, the ± is a useful reminder of how much a small sample can move.

Engineering for trust#

The rules from the constitution are enforced by tests, not just written down:

  • Scoring fidelity. The pure-Dart evaluation core is ported from the Python reference and tested against golden fixtures generated by the original toolkit.
  • Project rules as tests. A test suite checks project decisions: the core stays free of Flutter, the displayed version matches pubspec.yaml, the macOS permissions are exactly the allowed set, the app ID matches on every OS, and committed run files are valid.
  • Cross-platform CI. Formatting, static analysis with zero issues, unit and widget tests, integration tests that launch the real app, and release builds run on macOS, Windows and Linux on every push.
  • Robust API client. Retries on 408, 409, 429 and 5xx responses, with timeouts, and API keys redacted from errors.
  • Safe setup. A setup script installs the pinned Flutter version through FVM, asks before every change, and finally verifies that nothing else on the machine was modified.

Results#

I signed off each feature after testing the finished app against a real endpoint, using openai/gpt-oss-20b at low reasoning effort as both model and grader. The runs are committed in the repo for auditing:

DatasetConversationsScoreMedian
HealthBench (main)first 1000.4630.529
HealthBench Hardfirst 500.2120.273
HealthBench Hardfirst 500.2010.309
HealthBench Consensusfirst 500.8801.000

The same model scores very differently depending on the subset. The Consensus criteria are the ones physicians agreed on most, and they are mostly met. The Hard subset, selected because frontier models struggled with it, brings the score down to about 0.2. The two Hard runs on the same 50 conversations differ by 0.011, a small but real measure of run-to-run noise from sampling at temperature 0.3.

These are small subsets with self-grading, so they aren't comparable with OpenAI's published numbers.

What I learned about SDD with AI#

  • The spec is the leverage. Research, a constitution and a written spec gave the agent a precise target for every feature, and gave me something concrete to review against.
  • A known objective makes AI work verifiable. Golden fixtures from the reference implementation turned "does it look right?" into "does it match?".
  • Human gates matter. A review at the end of every phase meant the decisions on privacy, data formats and what gets published went through me.
  • Rules as tests scale better than rules as text. Anything important enough for the constitution ended up enforced by a test.

The repository's history runs from the first commit to version 0.2.0, with six signed-off features, over two days.

Try it#

A ready-to-use macOS build (v0.2.0) is in the repo's dist/ folder. Windows and Linux are built and tested in CI, and can be built from source. Next on the list are per-theme and per-axis score breakdowns, and showing the physicians' reference answer next to the model's reply.

Resources#