v0.0.16 — 8-model matrix, Vera 0.1.x compatibility, reasoning-mode benchmarking
Highlights
Benchmark-matrix release. Expands to an 8-model, 3-tier matrix across three providers, adds AILANG as a fifth comparison language throughout the charting path, and restores compatibility with Vera 0.1.x (which had left vera-bench validate at 22/60 failures on CI).
| Tier | Anthropic | OpenAI | Moonshot |
|---|---|---|---|
| ceiling | claude-fable-5 |
openai-pro/gpt-5.6-sol |
— |
| flagship | claude-opus-4-8 |
gpt-5.6-sol |
moonshot/kimi-k3 |
| workhorse | claude-sonnet-5 |
gpt-5.6-terra |
moonshot/kimi-k2.6 |
The new openai-pro/ routing gives a controlled reasoning-budget comparison: the same model at reasoning.mode standard vs pro, so deliberation is the only variable between the two entries.
Also new: scripts/preflight.sh, a pre-sweep gate that verifies every configured model id, provider auth, the request parameters each model accepts, and all four toolchains — at one problem per check, before a sweep spends hours and real money.
A note on what this release is
This is a methodology and harness release, not a results release. It contains no benchmark results — the v0.0.16 sweep has not been run yet. The assets/results-graph.png in the repository still shows v0.0.7 data, and the README results narrative is still the v0.0.7 one. Both are updated in a follow-up once the sweep completes.
Several fixes here were found by running the harness against live APIs rather than by CI, and a few of them were bugs that produced plausible-looking wrong numbers rather than failures — an un-run comparison language rendering as a Vera win, a partially-failed sweep reporting an excellent score, a compiler crash scored against the model. Those are detailed in Fixed below.
Added
-
Per-test subprocess-failure diagnostics for the Aver evaluator (#72). The
_evaluate_aver_codeper-test loop previouslycontinued silently on timeout and non-zero exit — a model whose type-correct Aver crashed at runtime was indistinguishable in JSONL from one with wrong logic (error_message=None, check_pass=True, run_correct=False). The loop now captures the first failing test's diagnostic (stderr-or-stdout coalesce, 400-char truncation, explicit exit-code marker when silent) intoerror_message, matching the AILANG evaluator's existing pattern. Both evaluators now share a_first_run_errorformatting helper. -
openai-pro/model-routing prefix — reasoning-mode benchmark entries.openai-pro/gpt-5.6-solruns Sol atreasoning.mode: proas a distinct benchmark entry from default-modegpt-5.6-sol: same model, different reasoning budget — a controlled comparison of whether Vera's contracts do the work the reasoning budget otherwise would. Mechanics:create_clientroutes the prefix toOpenAIClient(..., reasoning_mode="pro"); the runner needs no changes (thecomplete()Protocol carries no per-call config by design).- Both Sol arms run through the Responses API, which is the only endpoint carrying
reasoning.mode, and both get the same 16k output floor — an unequal budget would be a second variable in a comparison that claims to have one.gpt-5.6-terrais not half of the pair and stays on Chat Completions. - JSONL rows are self-describing:
model: "<api-model>#pro"and"<api-model>#standard"respectively. Result filenames are already distinct (built from the CLI model string). - The response echoes the effective mode, and a mismatch — or an absent echo — raises rather than being recorded as a pro result.
OpenAIClientnow sendsmax_completion_tokens(the GPT-5.x reasoning families reject the legacymax_tokenskwarg).scripts/run_full_benchmark.py_detect_providerrecognises the prefix (→OPENAI_API_KEY).
-
Prompt-cache instrumentation for OpenAI and Moonshot clients (#61). Both providers now cache automatically server-side (OpenAI ≥1024-token prompts; Moonshot longest-prefix matching — a request cache-hits only when it shares a prefix with a prior request) — the work is routing and observability, not cache management:
- New
cached_tokensfield onLLMResponseandProblemResult(JSONL rows): the cache-hit portion ofinput_tokens. Anthropic reportscache_read_input_tokens; OpenAI-compatible providers reportusage.prompt_tokens_details.cached_tokens(read via a shared_openai_cached_tokensguard that tolerates absent / None / non-int fields). Sweep cost analysis can now compute cache-hit rates per provider directly from result files. - OpenAI requests carry a stable
prompt_cache_key(SHA-256 of the system prompt, viaextra_body) so same-prefix requests route to the same cache shard — recommended by OpenAI for the GPT-5.6 family. During sweeps the ~28k-token SKILL.md prefix gets its own shard per language-mode. - Moonshot sends no cache parameter — their Context Caching is fully automatic with no routing key (their earlier explicit X-Msh-* header scheme is obsolete).
- New
-
OpenAI and Moonshot clients hardened to the OpenRouter error standard:
AuthenticationError→EnvironmentError(abort the run — retrying 60 problems on a bad key is waste),RateLimitError/BadRequestError/APIStatusError→ cleanRuntimeErrormessages, and explicit errors on emptychoices/ emptycontent(previously returnedtext=""and the harness blamed the model for "did not define entry point").
Changed
- Minimum
openaiSDK raised from>=1.50to>=2.45. Theopenai-pro/path callsresponses.createwithprompt_cache_keyandreasoning={"mode": "pro"}; the declared floor supported none of the three. Verified against the wheels rather than inferred:responses.createarrived in 1.66.0,prompt_cache_keyin 2.0.0, andreasoning.modein 2.45.0. 2.0 would in fact work at runtime —maybe_transformforwards keys a TypedDict does not declare, and the response model is pydanticextra="allow"— but relying on that makes a silent failure possible: if either behaviour changed, the "pro" entry would run in standard mode, the effective-mode guard would no-op, and the reasoning chart would compare a model against itself while looking entirely normal.
Fixed
-
A sweep with most calls failing could report an excellent score (#95).
run_correctis measured overrun_eligible— problems whose attempt compiled — so rows that never reached the compiler (API errors, auth rejections, timeouts) left the denominator entirely. Verified: 40 API failures plus 20 successes reportedrun_correct = 100%, and nothing in the output said so;check@1merely dropped, which is a normal result for a model struggling with Vera. The rate itself is unchanged, since altering it would break comparability with published results. InsteadBenchmarkMetricsgainedrun_eligibleanderrored, the CLI summary now prints the graded denominator whenever it differs from the problem count plus anerrored: N/60row in red, andextract_datatreats a file with nothing gradeable as missing rather than plotting it as a genuine 0% — the same conflation themissingset already prevents for absent files, one layer down. -
vera runfailures were indistinguishable from wrong answers. The per-test loop in_evaluate_vera_codediscarded the exit code and stderr on failure, and swallowed every exception through a bareexcept Exceptionwith no message — so a compiler crash was recorded aserror_message=None, check_pass=True, run_correct=False, byte-identical to a model writing a wrong program. The #72 diagnostics work reached the Aver and AILANG evaluators but never the Vera path, which is the headline number. Now captures the first failure via a_vera_run_errorsibling of_first_run_error, including signal detection:subprocessreports a signal death as a negative return code, so a compiler SIGBUS arrives asexit_code == -10and is now named as such. Not hypothetical — vera SIGBUS'd repeatedly on 2026-07-23 (aallan/vera#1145), and every one of those would have been scored against the model. -
preflight.shnow exits non-zero when the gate fails. It could print*** pro may be SILENTLY IGNORED ***— the one finding that invalidates the reasoning slide — and still exit 0, because the S2 and S3 analyses printed their verdicts from inside Python heredocs without touching the pass/fail counters. A chainedpreflight.sh && run_full_benchmark.pywould have sailed straight past it. S2's verdict now feeds the counters and the script ends on its own tally. -
Both Sol arms now send
store=False. The Responses API defaults tostore=Truewhere Chat Completions does not persist at all, so the pro routing silently introduced 30-day server-side retention of every prompt and completion. Beyond the data-handling question, retained content could feed cross-run caching or personalisation — and a benchmark whose second run is informed by its first is not measuring what it claims to. -
Claude Fable 5 returned no code at all.
AnthropicClient.completereadresponse.content[0].text, but models with extended thinking returnThinkingBlockentries ahead of theTextBlock— so every Fable 5 call died with'ThinkingBlock' object has no attribute 'text', recorded as an API-error row with no generated code. The whole fable tier of the v0.0.16 matrix would have come back empty. A new_anthropic_texthelper selects blocks by.type == "text"and joins them, ignoring thinking and redacted-thinking blocks. Caught by smoke S1 (2026-07-23); the pre-existing unit test missed it because a bareMagicMockauto-supplies any attribute, including the.textthe realThinkingBlocklacks. -
openai-pro/went to the wrong endpoint — now uses the Responses API. The reasoning tier was passed asextra_body={"reasoning": {"mode": "pro"}}on Chat Completions, which rejects it with400 Unknown parameter: 'reasoning', so every Sol@pro call failed. Per OpenAI's reasoning guide, mode and effort are independent axes — mode selects standard vs pro execution, effort controls how much reasoning happens — andreasoning.modeexists only on the Responses API (Literal["standard", "pro"]in openai-python 2.47). Pro is therefore not expressible on Chat Completions at all:reasoningis rejected as unknown, andreasoning_effort="max"is rejected too (gpt-5.6-solaccepts onlynone/low/medium/high/xhighthere —maxis Responses-only). A client with a reasoning mode set now routes toresponses.createwithinstructions/input/max_output_tokensand reads usage frominput_tokens/output_tokens/input_tokens_details.cached_tokens. Two new guards: an unknown mode raises at construction, and a response whose echoed effectivereasoning.modediffers from the requested one raises rather than being recorded as a pro result — a silent downgrade would turn the headline pro-vs-default comparison into a model compared against itself. Confirms the CodeRabbit finding on #92 that was declined pending evidence.Both Sol entries are pinned to the Responses API via
RESPONSES_API_MODELS, with the default entry sending an explicitreasoning.mode: "standard". Pro mode exists only on Responses, so leaving the default arm on Chat Completions would vary endpoint and mode together and the reasoning-budget comparison could not attribute its delta to deliberation. The 16000-token output floor applies to both arms for the same reason.gpt-5.6-terrais a separate tier row rather than half of a controlled pair, and stays on Chat Completions. Sol rows now reportmodelasgpt-5.6-sol#standard/gpt-5.6-sol#pro; charts key on filenames, which are unchanged. -
Long result paths wrapped mid-token in console output.
Output: <path>was printed through rich, which wraps at the console width (80 when not a tty — CI, and any sweep log piped to a file), breaking paths across lines mid-word and making them un-greppable and un-copy-pasteable. Now printed withsoft_wrap=True. This was also failingtest_run_ailang_full_path_successonmain. -
aver/ailangversion-probe timeouts reported the wrong cause. ATimeoutExpiredfrom--versionwas either unhandled (ailang) or folded into the not-found branch (aver), which advised reinstalling a compiler that is already installed. Both now report the timeout distinctly. -
AILANG version parsing corrupted result filenames.
run --language ailangbuilt its output filename from the compiler's raw--versionstdout.ailang --versionprints a seven-line banner (version, commit, full SHA, build stamp, blank, tagline, copyright), and the old.strip().replace("ailang ", "")matched nothing in it — the binary printsAILANG v0.30.0, capitalised. The entire banner therefore landed in the filename, producing a 216-character name containing embedded newlines and colons. macOS creates such a file happily, so this failed silently: every shell glob in the sweep runbook, thefile_prefixmatching inplot_results.py, and the release tarball would all have missed the AILANG results. Never caught becausebaselines --language ailanguses a separate code path that does not embed versions. Both the Aver and AILANG probes now share a_parse_version_bannerhelper that takes the first line and extracts the numeric version token, falling back to"unknown"(which callers already treat as "omit the version from the filename"). The AILANG probe also now catchesTimeoutExpired, matching the Aver probe. -
Vera 0.1.x compatibility for the problem set and canonical solutions. Vera moved from v0.0.177 to v0.1.6+ (the v0.1.0 bug burndown plus the 0.1.x line) and three compiler changes broke
vera-bench validate(22/60 failures). All three are now resolved; the benchmark validates 60/60 against Vera HEAD (v0.1.6,f6f586b0):- VB-T1-002 renamed
clamp→clamp_to_rangeacross the problem JSON (signature, entry_point, prose) and all five canonical solutions (TypeScript asclampToRange). Vera 0.1.x makesclampa built-in (spec §9.6) and rejects redefinition withE151— the problem was unwritable as specified. Problemidis unchanged (VB-T1-002). - State-handler solutions updated for intrinsic-hybrid clause semantics (vera#1003, shipped in Vera 0.1.x; see also vera#976/#973/#988). Handler clause bodies used to be type-checked but never executed — the runtime always used builtin state-cell semantics. Now clause bodies execute, and the old
put(@Int) -> { resume(()) } with @Int = @Int.0idiom resolves@Int.0to the handler state, so the "update" is state = old-state — a silent no-op that made VB-T5-001 return 0 instead of 3 and broke 3 of 4 VB-T5-006 test cases. All four State solutions (T5-001, T5-004, T5-006, T5-009) drop thewithclause; the intrinsic store threads state natively (the canonical form in SKILL.md'srun_counterexample). T5-004/T5-009 passed tests only incidentally and carried the same latent no-op. vera_verify_tier1flipped tofalseon 19 problems (T1-008, T1-010, T2-001, T2-010, T2-014, T3-001..005, T3-008, T3-012, T4-001, T4-004, T4-005, T4-007, T4-008, T4-010, T5-004). Vera 0.1.x auto-synthesisesint_overflow/nat_to_int_coerceproof obligations on all integer arithmetic; on unboundedInt/Natinputs these are legitimately unprovable at Tier 1 (proving them would require adding range preconditions — changing the published contracts of 19 problems). The obligations are checked at Tier 3 (runtime) instead, which is the correct classification. This flag gates only canonical validation; LLMverify@1scoring is computed fromverify_passand is unaffected.
- VB-T1-002 renamed
Compatibility note
Requires Vera ≥ 0.1.x (the intrinsic-hybrid handler semantics and clamp built-in). Canonical Vera solutions no longer compile/run correctly on Vera ≤ 0.0.x: the renamed clamp_to_range does not collide there (fine), but the State solutions rely on the new clause-execution semantics. Aver baselines verified unchanged on Aver 0.27.1 (the v0.0.14 Int.div migration was forward-compatible throughout); AILANG baselines verified on AILANG v0.30.0.