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 | shThe 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.
Or the file itself:
courant demoFour real OpenFOAM runs, offline, and an HTML report.
- Licence
- Apache-2.0
- Release
- 0.5.0 · 12 Oct 2026
- 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 verify ./my_case # a case directory: finds the solver log itself
courant verify log.simpleFoam # or a log on its owncourant verify --about # the error rates published for this versionEvery 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 ~/runscourant mcp --print-config --root ~/runsclaude mcp add courant -- courant mcp --root ~/runsThe server offers six read-only tools, confined to the directories you name:
verify_runscore_agent_outputcheck_convergenceexplain_codelist_failure_modesverifier_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 runsVerdicts and exit codes.
| Verdict | Exit code | Meaning | In JUnit |
|---|---|---|---|
accept | 0 | No check failed, and the run produced enough output to be judged. | a passed test |
reject | 2 | At least one check failed. Each reason gives the file it read, the method, the threshold and the value. | a failed test |
insufficient | 3 | There 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
| Reason | Rule | Cause |
|---|---|---|
insufficient.no_output | VER-5.1 | run |
insufficient.unrecognised_log | VER-5.1 | fork |
insufficient.no_residuals | VER-5.5 | run |
insufficient.no_time_markers | VER-5.15 | run |
insufficient.spec_hash_mismatch | VER-5.8 | spec |
insufficient.spec_value_unreadable | VER-5.9 | spec |
insufficient.unsupported_version | VER-5.10 | fork |
insufficient.spec_invalid | VER-5.11 | spec |
insufficient.spec_extracted_mismatch | VER-5.12 | spec |
insufficient.spec_role_unresolved | VER-5.13 | fork |
insufficient.time_scheme_unreadable | VER-5.14 | fork |
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.
