Skip to content

feat(tasks): add Ag-LiveCodeBench-X — multi-language LiveCodeBench - #123

Merged
ethan-scitix merged 10 commits into
mainfrom
worktree-ag-livecodebench-x
Sep 11, 2026
Merged

ethan-scitix merged 10 commits into
mainfrom
worktree-ag-livecodebench-x

Conversation

@ethan-scitix

@ethan-scitix ethan-scitix commented Aug 22, 2026 •

Copy link
Copy Markdown
Collaborator

Type

  • feature — new benchmark, task, or capability

Summary

  • Adds Ag-LiveCodeBench-X (dataset, scripts, paper — Agnostics, ICLR 2026): LiveCodeBench v5's 499 stdin/stdout problems, made language-agnostic, so the target language is a task argument rather than a property of the data. Ships status="stable" on a reproduced paper cell — see "Alignment" below.
  • language is required with no default: the benchmark measures a different thing per language, so a silent default would let two runs of one task name mean different things. report.json carries language / container_lang / verifier_image, because the task name cannot hold them and a column that doesn't say which language it ran isn't comparable with anything.
  • The prompt is a frozen dspy==3.0.0b2 ChatAdapter rendering of upstream's ChainOfThought(SolveProblem), captured by running it: two properties are invisible in the signature (an 8-space instruction indent inside the system message; a trailing space on the first field-list line and not the last). Frozen rather than depending on DSPy at runtime, so a patch release can't re-word the fleet's prompts; pinned by sha256 in tests.
  • Response parsing and code extraction are reimplemented, not vendored — upstream ships no license (no Hub license field, no LICENSE) — including an upstream offset bug where an indented [[ ## field ## ]] header leaks ]] into the field body. Reproduced deliberately; "fixing" it would move scores.
  • vendor/code-evaluator gains source="agnostics", forwarding a submission to a per-language verifier container over the Agnostics protocol (one JSON line each way). That's what makes Lua / R / Julia / OCaml / Fortran reachable without installing five toolchains in the evaluator image. Purely additive — no existing source changes behaviour.
  • The verifier is pinned by digest, not by upstream's mutable tag, and a language with no pinned digest is refused (infra:unpinned-lang) rather than floated. Rationale and the re-pinning procedure are in vendor/code-evaluator/VENDORED.md.

Since the first review pass (rebased onto main, 19 commits): two defects found by the validation run are fixed, and the report was adapted to the interval API that landed on main in the meantime.

  • verifier_command enforced the digest-pin refusal only on the default podman path — the override branch substituted an empty string for {image} instead of raising infra:unpinned-lang. podman is the one runtime needing no override, so every other deployment (upstream's own apptainer included) ran the branch where the documented guarantee was missing.
  • The module and a test name attributed the rendering's 8-space instruction indent to "the source docstring's 8-space indentation". Upstream's docstring is indented 4, and Signature.instructions dedents it to zero — the 8 is ChatAdapter's own constant. Bytes were always right; the stated reason would mislead whoever re-captures the prompt on the next DSPy bump.

Enumerated divergence with a consequence: upstream's ChatAdapter.__call__ retries a failed parse through JSONAdapter with a differently-shaped prompt; this port makes one call. A reply missing either marker scores 0 here, so the number is a lower bound whose gap is exactly n_unextracted.

Alignment (updated — this section previously said neither had been done): the container hop is now exercised against the real ghcr.io/nuprl/agnostics lua verifier at the digest this branch pins, and one paper cell is reproduced.

pass@1
Paper Table 1, Qwen 3 8B × Lua 11
This port, 499 problems 10.42 (52/499)

0.58 pp, or 0.30 combined binomial σ (σ_D = 1.96 pp) — inside noise, which was the promotion criterion this PR set for itself. Upstream's README settings for Qwen 3: temperature 0.2, top_p 0.95, max_tokens 2048, language "Lua /nothink", one completion. Verdicts: 74.5% fail:error, 11.7% fail:wrong-output, 10.7% success, 3.1% infra:timeout — the error-dominated mix the paper's own Figure 6 describes for base models on low-resource languages.

Three caveats, all carried in the shipped docstring and reference_impl.notes rather than only here, none of which moves the number out of noise:

  • the paper served qwen3_8b_awq (AWQ 4-bit); OpenRouter's only Qwen3-8B provider reports quantization unknown. Same checkpoint, possibly different precision — recorded, not controlled;
  • 13 of 499 rollouts never generated (12 provider rate-limits, one content filter) and count as failures, as upstream's pass1 counts a generation that produced no solution. They bias the number down: at the observed rate it would be 10.70;
  • the no-JSONAdapter-retry divergence above cost exactly one row (n_unextracted = 1).

The other four languages and every other model in the table are unmeasured, and the docstring says so.

Test Plan

Automated

  • Lint/format clean (ruff check && ruff format --check)
  • Type check clean (ty check) — mypy errors on the new files are exactly the 8+1 repo-wide no-untyped-def stage-method pattern the sibling livecodebench_code_generation_0shot_gen already has, no more
  • Unit tests pass — 7296 unit tests after rebasing onto current main, 48 of them new; check_preflight.py 25/25 PASS (incl. check_links --level deep, check_meta_index_sync, check_reference_kind, check_report_declarations, check_dep_coverage)

Manual

  • Prompt bytes are byte-identical to the captured DSPy rendering — sha256 match, and mutation-tested: deleting the single trailing space that looks like a typo fails 2 tests.
  • Parser reproduces ChatAdapter.parse — all 10 parametrised expectations were produced by running dspy==3.0.0b2's own parser on the same inputs, the indented-header offset bug included.
  • End-to-end against the real verifier container — real uvicorn + ghcr.io/nuprl/agnostics at its pinned digest, run under udocker (this box has no podman): success / fail:wrong-output / fail:error come back verbatim from the container, infra:timeout / infra:bad-lang / infra:no-test all fire, and data.verifier_image carries the digest that ran. Control: a hand-written correct Lua solution scores success on a real decoded LiveCodeBench suite while a deliberately wrong variant scores fail:wrong-output — which is what rules out the base64→zlib→pickle→JSON decode and the whitespace handling as an explanation for any score.
  • Frozen prompt verified against a real dspy==3.0.0b2 — not just against its own hash. Installing the pinned library and rendering upstream's signature reproduces both the system and the user message byte-identically. (It also caught a mis-attributed comment: the 8-space indent is ChatAdapter's own constant, not the source docstring's 4-space indent, which Signature.instructions dedents away. Fixed.)
  • Digest pinning, both paths — lua / jl / ml / f90 resolve to their digests and report them; julia and rust are refused as infra:unpinned-lang; a {lang}-templated override reports no image (naming a digest that didn't run would be worse than naming none).
  • Model run and score alignment — 499 problems, Qwen 3 8B × Lua, 10.42 vs the paper's 11 (0.30 σ). See "Alignment" above.
  • All eight pinned digests re-checked against the registry (2026-09-11) — every tag still resolves to the digest the table pins, so the pin is currently exact rather than merely historical.

Checklist

Required (all PRs)

  • PR title follows conventional format (type(scope): description)
  • No internal paths, credentials, or personal info in committed files
  • AI-generated code has AI-Generated Code - <model> (<provider>) in module docstring — on all three sieval/ + tests/ files. Deliberately absent on vendor/code-evaluator/app/exec_agnostics.py: no file under vendor/ carries it, and that patch is written to be landed in scitix/code-evaluator upstream.
  • No new upper-layer dependencies added to core/ — core/ is untouched
  • Deleted code verified — nothing deleted

If: New or Modified Benchmark

  • Reference paper/repo linked in Summary
  • Score comparison table — in "Alignment" above: Qwen 3 8B × Lua, published 11 vs measured 10.42, 0.30 σ. Anchored on the paper's Table 1; the remaining cells (Qwen-3 4B, DeepSeek-Coder-6.7B-Instruct, Phi-4-Mini; Julia / R / OCaml / Fortran) are unmeasured — of the other three models, none currently has a serving provider reachable from here.
  • Dataset loading — the pinned revision 15bef6bf was downloaded and loaded: 5 parquet shards, 499 rows in test, exactly the loader's three string columns. (Loaded via the pinned revision's snapshot rather than staged with sieval dataset download, to avoid a second 3.9 GB copy on a box at 96% disk.)
  • Task registered — auto-discovered; check_tasks sees 66 exports, __init__.pyi and meta/index.json regenerated

If: New Dependency

  • None. The loader needs only datasets and the task only httpx, both core; check_dep_coverage passes with no new group.

ethan-scitix and others added 6 commits September 11, 2026 15:50
…language

LiveCodeBench v5's 499 stdin/stdout problems, prepared by Agnostics
(ICLR 2026, arXiv:2508.04865) so the same problem and the same test cases
score a solution in any language. Ships `status="experimental"`: faithful
on the axes below, but no run has been aligned against a published number.

The language is a task argument, not a property of the data, so `language`
is required with no default — the benchmark measures a different thing per
language, and a silent default would let two runs of one task name mean
different things. `container_lang` (the verifier tag) defaults to
`language`'s first word lowercased and stays overridable, because upstream
keeps the two independent and warns nothing checks them against each other.
Both are recorded in `report.json`, which the task name cannot do.

The prompt is a frozen dspy==3.0.0b2 ChatAdapter rendering of upstream's
ChainOfThought(SolveProblem), captured by running it: two of its properties
are invisible in the signature — the instructions are re-inserted into the
system message still carrying the source docstring's 8-space indent, and
the field list is stripped as a block so the first line keeps a trailing
space and the last loses it. Frozen rather than depending on DSPy at
runtime, so a patch release cannot re-word the fleet's prompts; pinned by
sha256 in the tests. Response parsing and code extraction are
reimplemented, not vendored (upstream ships no license), including an
upstream offset bug that leaks `]]` out of an indented field header.

Enumerated divergence with a consequence: upstream's ChatAdapter retries a
failed parse through JSONAdapter with a differently shaped prompt and this
port makes one call, so a reply missing either marker scores 0 here. The
number is a lower bound whose gap is exactly `n_unextracted`.

Execution goes through the code-evaluator's new `agnostics` source rather
than from the task process, so sieval keeps one code-execution boundary and
a client cannot name an arbitrary container to run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… names

`ghcr.io/nuprl/agnostics` publishes exactly `lua, r, python, jl, java,
cpp, ml, f90` — Julia is `jl`, OCaml is `ml`, Fortran is `f90`. Deriving
the tag by lowercasing the language's first word, as this shipped, named
no published image for three of the five languages the paper reports, so
every rollout would have come back `infra:exit` (no such image) and the
task would have scored a hard 0 for reasons that look like an
infrastructure outage.

`_CONTAINER_TAG_BY_LANGUAGE` maps the extension-tagged half and leaves the
rest falling through to the lowercased first word, which is correct for
`lua` / `r` / `python` / `java` and the only sensible guess for a container
the table has never seen. `container_lang` still overrides it, which is
what a private registry needs.

Found by querying the registry's tag list rather than by assuming it
mirrors the framework repo's directory names, which are spelled out in
full (`executors/julia`, `executors/ocaml`, `executors/fortran`).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The verifier decides scores, so it gets what a dataset revision gets. A
tag can be moved under a finished leaderboard without anything on disk
changing, which is exactly the drift `--resume` and pinned revisions exist
to prevent everywhere else in this repo.

`_IMAGE_DIGESTS` maps each of upstream's eight published tags to a digest
resolved from the registry on 2026-08-23, each verified against the
manifest's own `Docker-Content-Digest`. A language with no pinned digest
is REFUSED (`infra:unpinned-lang`) rather than falling back to the
floating tag: an unpinned verifier scores silently, which is the failure
the table exists to prevent. All eight are single-platform linux/amd64
manifests, so the pin binds the architecture too — arm64 needs the
command override.

A pin nothing records is not a pin, so the resolved reference travels back
as `data.verifier_image`, onto each rollout's judgement, and is surfaced
once as `report()`'s `verifier_image`. Two verifiers in one run (an
evaluator re-pointed mid-run) are both reported rather than one being
picked to stand for the rest, and an unrecorded one is omitted rather than
blanked — an empty string would read as "ran unpinned", a different claim.
Under a command override the field is `None` unless the override's
template actually contains `{image}`: naming a digest that did not run is
worse than reporting nothing.

Verified live on both paths: lua/jl/ml/f90 resolve and report their
digests, `julia` and `rust` are refused, and a `{lang}`-templated override
reports no image.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`verifier_command` enforced the digest-pin contract only on the default
(podman) path. The override branch returns early with `_IMAGE_DIGESTS.get`,
so a language with no pinned digest had `""` substituted into the `{image}`
slot instead of raising -- the documented `infra:unpinned-lang` refusal never
fired there.

podman is the one runtime that needs no override, so *every* other deployment
-- upstream's own apptainer included -- ran the branch where the guarantee was
missing. Nothing scored silently (an empty argv slot makes most runtimes
error), but the operator read whatever the runtime said about a missing
argument rather than the diagnostic that names the cause, which is exactly the
classifier split this module exists to make.

A template containing `{image}` is asking this table for a pinned digest, so
it is now refused the same way as the default path. A template *without*
`{image}` names its own image and is untouched -- that is the override taking
responsibility, which is the point of having one.

Verified end-to-end through the real `ghcr.io/nuprl/agnostics` lua verifier at
its pinned digest, run under udocker: `rust` now returns
`infra:unpinned-lang: 'rust'` where it previously surfaced as `infra:exit 64`.
All five override/default x pinned/unpinned combinations behave as documented.

VENDORED.md also records what that validation run established, since the file
still claimed the container hop was only exercised against a stub: the real
verifier returns `success` / `fail:wrong-output` / `fail:error` verbatim, a
hand-written correct Lua solution scores `success` on a real decoded
LiveCodeBench suite while a deliberately wrong variant does not, and all eight
pinned digests still matched the registry on 2026-09-11. Plus the two things
podman gives for free that a udocker deployment has to rebuild -- a private
workdir per invocation (19 of 40 verdicts were wrong without one) and
`--quiet`, because `udocker run` banners land on stdout and this module
json-decodes the whole stream.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The module docstring and the test's name both said the rendering keeps "the
source docstring's 8-space indentation". Upstream's docstring is indented
**4** in the class body, and `Signature.instructions` dedents it to zero
before the adapter ever sees it; the 8 comes from `ChatAdapter` joining the
instruction lines with a newline plus a hard-coded eight spaces.

The frozen bytes were always correct -- independently confirmed by rendering
upstream's signature under a real `dspy==3.0.0b2` install, which reproduces
both the system and the user message byte-for-byte. Only the stated reason was
wrong, and it is the kind of wrong that misleads whoever re-captures the prompt
on the next DSPy bump: they would go looking at upstream's source indentation,
find 4, and conclude the capture had drifted.

The hash test cannot catch this, because it pins this file's own literal
against itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rebase puts this task on top of the metrics rework, whose
`sampling_report` now returns interval and unit-declaration keys (`*_ci95` as a
list, `ci95_units` as a dict). `metrics` was annotated `dict[str, float | str]`,
so `metrics.update(rolled)` no longer type-checks -- two `ty` diagnostics, both
in this file, and neither visible until the branch sat on current main.

Widening the annotation is the whole of the type fix. The rest adopts the
convention that rework introduced and that every sibling pass@1 task now
follows (`human_eval_0shot_gen` is the structural twin): `score_key="pass@1"`
so the headline's interval is also published as `score_ci95` -- the same
estimate over the same values, not a second measurement -- and
`ungated_intervals` to lift `score` / `pass@1`'s intervals out of the `n > 1`
gate, because the metrics they bracket are published at every budget.
`grouping=self.problem_groups(finals)` is inert when one sample is one problem
and correct if a run configures `Dataset.repeat`.

Verified on a live 20-sample run, which is the only thing that exercises it:
the runner validates interval/unit pairing at report-write time and raises
there, so a green unit suite says nothing about it. The report now carries
`score_ci95` / `pass@1_ci95` = [0.0, 16.84], `n_problems` 20 and `ci95_units`
naming `n_problems` for both, with no raise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ethan-scitix
ethan-scitix force-pushed the worktree-ag-livecodebench-x branch from 27c67b5 to db74c27 Compare September 11, 2026 08:01
ethan-scitix and others added 4 commits September 11, 2026 16:08
The PR shipped `experimental` with its own promotion criterion stated: "one
paper cell reproduced within its noise". That has now been done, so both the
status and the prose explaining it were stale.

**Table 1, Qwen 3 8B x Lua: published 11, measured 10.42** (52/499) -- 0.58 pp,
or 0.30 combined binomial sigma (sigma_D = 1.96 pp). Run at upstream's README
settings for Qwen 3 (temperature 0.2, top_p 0.95, max_tokens 2048, language
"Lua /nothink", one completion), against the real `ghcr.io/nuprl/agnostics`
lua verifier at the digest this branch pins -- not a stub. Verdict mix:
74.5% fail:error, 11.7% fail:wrong-output, 10.7% success, 3.1% infra:timeout,
which matches the paper's own qualitative finding that base models on
low-resource languages fail mostly on syntax and input parsing.

Three caveats travel with the number, all in the shipped docstring and
`reference_impl.notes` rather than only here, and none of them moves it out of
noise:

- the paper served `qwen3_8b_awq` (AWQ 4-bit) where this used the only
  OpenRouter provider for the checkpoint, which reports quantization unknown;
- 13 of 499 rollouts never generated (12 provider rate-limits, one content
  filter) and count as failures, as upstream's `pass1` counts a generation that
  produced no solution -- biasing the result down from 10.70 to 10.42;
- the no-JSONAdapter-retry divergence cost exactly one row (`n_unextracted` 1).

Also recorded: a hand-written correct Lua solution scores `success` on a real
decoded LiveCodeBench suite while a deliberately wrong variant scores
`fail:wrong-output`, which is what rules out the decode and whitespace paths as
an explanation for the score; and all eight pinned verifier digests still
matched the registry on 2026-09-11.

The remaining four languages and every other model in the table are unmeasured
and the docstring says so. `meta/index.json` regenerated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…retry note

Three review findings on the same file, one behavioural and two declarations
the file makes about itself.

**The grade deadline was not outside the evaluator's own budgets.** It was
sized `timeout + 300`, but the evaluator spends those two in *series*, not one
covering the other: `exec_agnostics` writes the payload under its own 300s
`_STDIN_WRITE_TIMEOUT` -- upstream's `stdin_write_timeout`, kept because a
decoded suite is tens of MB -- and only then arms the container wall, plus
spawn and the monitor's teardown. So the client's deadline expired while the
server was still entitled to answer, and the `infra:stdin` verdict that budget
exists to report could never arrive.

The cost is larger than the lost diagnostic: a verdict fails one rollout,
whereas the transport error a too-tight deadline raises propagates out of
`feedback` and fails the whole sample, which `DENOMINATOR_REQUESTED` charges as
wrong. A narrow tail -- it needs a pathologically slow write, and the 499-problem
alignment run never hit it -- but it deflates a score rather than erroring, which
is the class worth closing. The two budgets are now named constants and the
margin is explicit; the sibling `livecodebench_code_generation_0shot_gen` states
the same invariant ("provably outside it") over its own whole-suite wall.

The test asserts the inequality as well as the literal, so the reason survives a
later change to either budget rather than reading as an arbitrary constant.

**The report slot was narrower than what `report()` returns.** It declared
`dict[str, float | str]` while `ungated_intervals` contributes `score_ci95` and
`pass@1_ci95` (`list[float]`) and `ci95_units` (`dict[str, str]`) -- verified by
dumping a real report. All 21 sibling tasks calling `ungated_intervals` declare
the full union, and this file's own local at the `metrics` assignment already
did; the class slot was missed when the report was adapted to the interval API.
Nothing catches it: the stage methods carry no return annotations, so the
generic slot is unchecked and a wrong declaration is simply wrong.

**The JSONAdapter divergence note overstated upstream's retry.** `__call__`
re-raises `ContextWindowExceededError` instead of retrying it. The lower-bound
claim is unaffected -- that case fails on both sides -- and the note now says so
rather than leaving the reader to find the exception.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`exec_agnostics` carries the guarantee the whole port rests on -- the verifier
is scoring data, so it is pinned by digest and an unpinned language is refused
rather than floated -- and had no test. That guarantee has already regressed
once: `verifier_command` enforced it on the default podman path while the
override branch substituted an empty string into the `{image}` slot, so the
documented `infra:unpinned-lang` never fired under any non-podman runtime,
upstream's own apptainer included. It was caught by a manual validation run,
not by CI, which is the gap this closes.

33 tests, none of which spawns a container: `verifier_command` is a pure
function of the environment, and the two `execute_agnostics` cases asserted here
both return before the spawn. They cover the digest table against upstream's
published tag list (spelled out rather than derived from the table, so a
misspelled tag cannot agree with itself), digest well-formedness, and all four
path/pin combinations -- default and `{image}`-override refusing an unpinned
language, an override naming its own image left alone and reporting no image,
and the pinned digest reported on both paths.

Verified discriminating by reverse-mutation: restoring the pre-fix module
(49f07b1) fails exactly the five override-refusal cases with
`DID NOT RAISE <class 'KeyError'>`, and passes the other 28.

Placed under `tests/` rather than beside the code, which is what VENDORED.md's
convention prescribes for an upstream-bound patch, because `[tool.pytest]
testpaths` covers `tests/` only -- a test under `vendor/code-evaluator/tests/`
is never collected by this suite and gates nothing. The file's docstring says
so, and asks a future re-vendor to leave an equivalent gate behind. This is the
placement the `quotebench` source already uses for the same reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A pass over the prose added by the two preceding commits. No behaviour, no
assertions, no test names change -- 78 lines out, 45 in.

- **The report slot now uses the family's own wording.** 49 of the 50 tasks
  declaring this union carry one verbatim three-line comment; the version added
  here was a bespoke six-line one that also explained why the generic slot goes
  unchecked. That reasoning belongs to whoever is deciding the convention, not
  to the fiftieth site following it.
- **The deadline reasoning is stated once, at the constants.** It was in both
  the constant block and the call site, in different words -- "in series",
  "strictly outside" and "entitled to answer" each appeared twice. The call site
  now carries no comment at all: `self._timeout + _EVALUATOR_STDIN_WRITE_BUDGET
  + _HTTP_DEADLINE_MARGIN` already reads as the invariant, which is what naming
  the budgets was for. This is the sibling's shape, where the wall's derivation
  is commented and the `+ 2` at the call site is not.
- **The `ContextWindowExceededError` note is an inline clause** rather than a
  trailing parenthetical restating the exception it had just named.
- **Test comments that restated the test name are gone**, and the two that
  duplicated `verifier_command`'s own docstring with it. What is kept is what a
  reader cannot recover from the code: the REGRESSION marker (so the override
  case is not "simplified" away), why the published tag list is spelled out
  instead of derived from the table under test, why an override reports no image
  even for a language the table could name, and that the `lang` pattern is not a
  sandbox.

Checked the rest of the PR rather than assuming: prose density is 44% for the
task, 51% for `exec_agnostics` -- inside the band its recent siblings already
occupy (`multipl_e/_base.py` 41%, `exec_quotebench.py` 59%), so the port's own
comments are not outliers and are left alone. The module-docstring /
`reference_impl.notes` overlap is likewise deliberate: shipped metadata has to
carry the divergences on its own, and `meta/index.json` embeds the notes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ethan-scitix
ethan-scitix merged commit a0ba68d into main Sep 11, 2026
9 checks passed
@ethan-scitix
ethan-scitix deleted the worktree-ag-livecodebench-x branch September 11, 2026 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant