Install the gate.

pip install 'courant-dev[mcp]'

# Python 3.11 or later; the command is courant

Or one signed file, no Python. macOS and Linux · arm64 and x86_64

curl -fsSL https://github.com/courantdev/courant/releases/latest/download/install.sh | sh

The installer checks the binary's SHA-256 and, where it can, the release's Ed25519 signature, against the key pinned in the installer; COURANT_REQUIRE_SIGNATURE=1 makes it refuse without the signature check.

courant demo

Four real OpenFOAM runs, offline, and an HTML report.

Licence
Apache-2.0
Key · Ed25519
04936391…5c821192

First verdict in seconds.

Then point it at a case directory, or at a solver log on its own.

Courant demo · offline · read-only · four real OpenFOAM v2406 runs

1  A healthy run: icoFoam cavity, as OpenFOAM ships it
     accept  (rule VER-5.6, grade A)

2  A broken run: the same cavity with a timestep ten times too large
     reject  (rule VER-5.3, grade C)
     why     cfl.max: Courant number exceeds stability limit: max Co=9.66 at t=0.15 exceeds limit 1.0; still 8.92 over the final fifth of the run
     why     convergence.residual: transient solve judged on stability rather than a residual threshold — Courant number settled at 8.92, above the stability limit of 1 — a transient solve that does not respect its timestep criterion is not converged, whatever the residuals say (final worst residual 3.61e-01)
     cause   NUM.CFL_VIOLATION (Courant number too high)
     next    Suggested, not applied: Reduce deltaT in controlDict

3  Before the run: the cavity with its viscosity in the wrong units (courant preflight)
     SETUP_FAULT  before the first timestep
     why     physics.nu: Suspicious viscosity value: nu=1e-09 m^2/s is outside typical fluid range [1e-8, 1e-2]
     next    Suggested, not applied: Correct nu in transportProperties (units m^2/s)

4  A steady run with a fixed budget: pitzDaily, 2,000 iterations, monitors on the walls and the inlet
     accept  (rule VER-5.6, grade B); convergence PASS
     1  Answer       courantWallForces/moment:total_z: value -5.55254e-06 at step 977, -5.55373e-06 at the end (step 2,000)
     2  Change       0.021% of scale after the stopping point (limit 1.0% of scale)
     3  Iterations   1,023 of 1,999 past convergence (51.2%)
     4  Wall clock   9 s of 21 s past convergence (solver ClockTime; rank-0 CPU 9.51 s)
     5  Core-hours   0.0025 past convergence (1 rank, nProcs in the log, x wall clock)

5  The compute audit over these 4 runs (courant audit)
     2 of 4 trustworthy · 1 of 4 with a setup fault preflight finds before the start · on 1 of 1 monitored runs the answer settled, with 51.2% of its iterations still to run
     On your runs it totals the core-hours, and the cost at your rate.
courant 0.5.0 · 19 Oct 2026 · the first screen of its output
courant verify ./my_case                   # a case directory: finds the solver log itself
courant verify log.simpleFoam              # or a log on its own

courant verify --about           # the error rates published for this version

Every verdict travels with this version's measured error rate.

Is it the problem that was asked?

A run can converge and still answer the wrong question: it checks every value the task names against the case.

Lock the hash before the run: an edited specification makes the verdict insufficient.

courant spec init ./reference_case -o spec.json --task "pitzDaily, inlet 10 m/s, k-epsilon"
courant spec hash spec.json              # lock it before the run; keep the hash
courant verify ./agent_case --spec spec.json --spec-sha256 <hash>

Every report states its basis: validity, validity+conformance or validity+unlocked-spec.

For AI agents.

An agent that runs a simulation can check its own result before it reports success.

courant mcp --root ~/runs

courant mcp --print-config --root ~/runs

claude mcp add courant -- courant mcp --root ~/runs

The server offers six read-only tools, confined to the directories you name:

  • verify_run
  • score_agent_output
  • check_convergence
  • explain_code
  • list_failure_modes
  • verifier_certification

In CI.

Any CI system that reads JUnit shows each run as a test: GitHub Actions, GitLab, Jenkins, Azure.

The batch exit code is 2 if any run is rejected, otherwise 3 if any is insufficient, otherwise 4 if any is unreadable.

courant verify --batch runs/ --junit courant.xml     # exit 2: runs/ holds rejected runs

The GitHub Action

Verdicts and exit codes.

VerdictExit codeMeaningIn JUnit
accept0No check failed, and the run produced enough output to be judged.a passed test
reject2At least one check failed. Each reason gives the file it read, the method, the threshold and the value.a failed test
insufficient3There was not enough to judge. The verdict carries one reason, insufficient.<name>, from a closed list, and the report says what was missing.a skipped test

An unreadable input exits 4.

The eleven reasons for insufficient
ReasonRuleCause
insufficient.no_outputVER-5.1run
insufficient.unrecognised_logVER-5.1fork
insufficient.no_residualsVER-5.5run
insufficient.no_time_markersVER-5.15run
insufficient.spec_hash_mismatchVER-5.8spec
insufficient.spec_value_unreadableVER-5.9spec
insufficient.unsupported_versionVER-5.10fork
insufficient.spec_invalidVER-5.11spec
insufficient.spec_extracted_mismatchVER-5.12spec
insufficient.spec_role_unresolvedVER-5.13fork
insufficient.time_scheme_unreadableVER-5.14fork

The previous verdict names (TRUSTWORTHY, NOT_TRUSTWORTHY, NOT_ASSESSABLE, courant.verdict/1) are available for one more minor release with --schema 1; the decision is the same.