fv-worlds
babelbench
Working repo of the Cross-Paradigm Proof-Translation Benchmark (Secure Program
Synthesis Fellowship): measuring whether LLMs understand proof structure or merely
pattern-match system-specific syntax, via verifier-grounded translation tasks across
proof systems (target: ~150 theorem pairs; Coq↔Lean, TLAPS↔Dafny, cross-cluster).
The repo began life at the gr.inc OpenReward hackathon (April 2026) as LTLVerifyEnv,
a single-language-family RL environment for formal-verification tasks. It has since
been restructured into the installable babelbench package and repointed at the
translation-benchmark research arc described above — see History and
ROADMAP.md for where it's headed.
What exists today
- 8 verifier adapters (Dafny, TLA+/TLC, FlyVy, FizzBee, mypyvy, Lean 4, Agda, Rocq) —
src/babelbench/verifiers/ - An OpenReward RL/eval environment with 9 splits, 2,054 tasks total —
src/babelbench/server.py - Rollout drivers (OpenAI + Anthropic/Vertex) and verifier-grounded scoring —
src/babelbench/rollout/,src/babelbench/scoring/ - Audited hackathon-era results —
results/hackathon/(seeAUDIT.mdthere for which numbers are trustworthy)
What's not built yet — the pluggable scorer and the curated theorem-pair corpus — is
tracked in ROADMAP.md. The Coq/Lean adapters now exist and their toolchains
ship in the image; what they still lack is corpus breadth, not plumbing.
Quickstart (first hour)
git clone git@github.com:swist/fv-worlds.git && cd fv-worlds
python3 -m pip install -e ".[dev,rollout]"
bash scripts/fetch_datasets.sh # populates datasets/ (pinned upstreams)
python3 -m pytest tests/ # offline selftests — no verifiers needed
# Run 3 Dafny->TLA translation tasks against the deployed env:
export OPENREWARD_API_KEY=... # + ANTHROPIC_API_KEY, or Vertex (below)
python3 -m babelbench.rollout.run_rollout_claude \
--env-name swist/fv-worlds --split translation_dafny_to_tla \
--max-tasks 3 --run-name hello-xlate
python3 -m babelbench.scoring.score_translation results-translation_dafny_to_tla-hello-xlate.jsonlrun_rollout_claude.py also accepts --task-index (pick a starting task),
--max-steps / --max-tokens (per-turn caps), --model, and --results (override
the output path). Run python3 -m babelbench.rollout.run_rollout_claude --help for
the full, current list — flags occasionally change as the harness evolves, so treat
--help as the source of truth over any doc, including this one.
Auth
- Anthropic API key:
export ANTHROPIC_API_KEY=...(console.anthropic.com) - Google Vertex (no Anthropic key):
export ANTHROPIC_VERTEX_PROJECT_ID=... CLOUD_ML_REGION=global
— the drivers auto-detect from these env vars (--providerforces one path).
This is the path for anyone using a corporate GCP project instead of a personal
Anthropic key (rungcloud auth application-default loginonce if credentials
are missing). - OpenAI (
run_rollout.py):export OPENAI_API_KEY=...
Verifiers & the Docker images
The verifier adapters shell out to real toolchains, which ship in one of two images.
Nothing else needs installing locally — scripts/fv.py is the only entrypoint.
Dockerfile("base") — all 8 verifiers.Dockerfile.lite("lite") — 4 verifiers only: Dafny, TLA+/TLC, Lean 4, Rocq.
Same app stage as base; the dropped languages' splits reportTOOL_MISSINGhere.
Both auto-publish to ghcr.io/<org>/<repo>:base / :lite on push to master
(.github/workflows/publish-images.yml); build locally with the commands below.
# Build once (~15 min+; the first stage compiles FlyVy from Rust source, and
# the Mathlib cache fetch adds more on top).
docker build --platform linux/amd64 --load -t fv-worlds:base .
docker build --platform linux/amd64 --load -f Dockerfile.lite -t fv-worlds:lite .
# Confirm the image works: runs real proofs, false theorems, and malformed
# sources through every proof assistant and checks the exact Status of each.
scripts/fv.py --selftest
FV_WORLDS_IMAGE=fv-worlds:lite scripts/fv.py --selftest --langs dafny,tlaplus,lean4,rocq
# Verify a single file (this is what the rollout agent calls each round).
scripts/fv.py lean4 datasets/translation_pa/lean4/add_zero.lean
scripts/fv.py dafny path/to/task.dfy
scripts/fv.py tlaplus path/to/Spec.tla --translation # composite rewardfv.py prints the verifier's feedback plus a STATUS=<value> trailer and exits 0
iff the source verifies. It bind-mounts the live src/ over the image's editable
install, so adapter edits take effect without rebuilding. Override the image tag
with FV_WORLDS_IMAGE or --image; scope the self-test with --langs lean4,rocq.
--platform linux/amd64 is required everywhere: the image bundles x86_64 binaries,
and a native arm64 run dies with a rosetta ld error. On Apple Silicon everything runs
emulated, so verification is slower than the 60s default timeout might suggest
(LTLV_VERIFY_TIMEOUT raises it).
| Toolchain | Version | Notes |
|---|---|---|
| Dafny | 4.9.0 | pinned release zip |
| TLA+ (TLC) | latest | tla2tools.jar, wrapped as tlc |
| TLAPS | 1.6.0-pre | ~880 MB; base only, dropped from lite |
| FlyVy | HEAD | compiled from Rust in stage 1; base only |
| FizzBee | 0.3.1 | tarball; siblings must travel with fizz; base only |
| mypyvy | master | checkout + pip deps; base only |
| Lean 4 | 4.32.2 | pinned .tar.zst; LLVM/clang and IDE indexes pruned |
| Mathlib | v4.32.2 tag | fetched via lake exe cache get, not compiled; lean is wrapped (lake env lean) so single-file import Mathlib... resolves — see docker/lean-mathlib/ |
| Agda | 2.6.4.3 | agda-bin (apt); base only |
| Rocq | 8.20.1 | coq (apt) — provides coqc; the adapter falls back to it |
base was 8.21 GB before Mathlib was added (now several GB more — Mathlib's
prebuilt cache alone typically runs multiple GB); lite is smaller on both
verifier count and (once trimmed) build time. Every install step hard-fails the
build rather than warning: a missing toolchain makes its adapter report
TOOL_MISSING, which scores 0.0 and reads as a model failure in results rather
than as broken infrastructure. The Dockerfile header documents what to prune
first if you need base smaller.
Splits
9 splits, 2,054 tasks total. All splits share the same env API
(check(language, source) non-terminal reward 0, submit(language, source)
terminal). Most splits use a binary reward — 1.0 iff the verifier accepts, 0.0
otherwise — but spec_synthesis and translation_dafny_to_tla layer a composite
reward on top instead (see below).
| Split | Type | Count | Source |
|---|---|---|---|
dataset_invariants | train | 539 | masked TLA+/FlyVy/FizzBee/mypyvy/ivybench, invariants stripped. DafnyBench is excluded — see below. |
dataset_proof | train | 655 | same upstream corpora, whole proof body stripped instead of just invariants |
dataset_dafnybench | test | 503 | DafnyBench ground_truth files, proof-masked in-repo by our own loader (not DafnyBench's hints_removed release) — in-distribution for any 2024+ frontier model |
dataset_mutated | test | 245 | DafnyBench problems passed through syntactic mutation (loop conversion, method splitting, requires generalisation, ghost-variable injection) — out-of-training-distribution |
spec_synthesis | train | 10 | hand-curated Dafny method bodies; model writes the spec; composite reward |
translation_dafny_to_tla | train | 30 | small Dafny methods to translate into TLA+ — the split this fellowship's work extends |
synthesis | train | 61 | 7 hand-written + 3 IronFleet-distilled + 51 Dwyer LTL-pattern instantiations; model picks the language |
smoke | test | 5 | one already-verifying file per language that has a datasets/<lang>/ seed dir — currently 5 of the 8, for end-to-end plumbing checks |
translation_proof | train | 6 | proof-assistant translation: 3 in-repo fixtures (datasets/translation_pa/{lean4,agda,rocq}/) × 2 target languages each. Binary reward; the corpus is deliberately tiny until problem-set sourcing lands |
See docs/splits.md for per-split task shape, field semantics, and the difficulty
ladder these splits were originally designed around.
Why DafnyBench is excluded from dataset_invariants
DafnyBench has been in every frontier model's training corpus since 2024. Including
it in the main invariant-filling split would inflate the headline number with
memorization, not capability. We keep DafnyBench on disk for two purposes instead:
dataset_dafnybench— the contaminated baseline, useful as a known recall surfacedataset_mutated— the same problems, structurally rewritten; the gap between
pass rates on these two splits is the memorization measurement
babelbench.scoring.eval_mutation_gap computes that gap directly.
Reward structure
Sparse and verifier-grounded — reward comes from a real verifier's accept/reject
decision, delivered only at episode termination via submit.
Standard splits (dataset_*, synthesis, smoke):
- 1.0 if the verifier exits cleanly AND the output classifier returns
VERIFIED - 0.0 if rejected, parse error, timeout, or
Status.REWARD_HACK(Dafny's syntactic
guard caught a literalassume false/assume {:axiom} falsevariant)
spec_synthesis split (composite, still fully programmatic — no LLM judge):
- 0.0 if the source contains a reward-hack construct (multiplicative gate)
- 0.5 if the spec verifies but is vacuous (probe: delete the first body statement;
if the spec still verifies, it wasn't load-bearing) - 1.0 if the spec verifies AND the probe rejects (the spec was genuinely needed)
translation_dafny_to_tla split (composite, still fully programmatic — no LLM
judge — see docs/splits.md for the full detail):
- 0.5 if TLC accepts the submitted TLA+ source
- 0.3 if the property is non-trivial (a mutation probe shows it's load-bearing)
- 0.2 reserved for a bidirectional check — not yet implemented
The check tool returns the same verifier feedback mid-episode but yields reward 0
and does not terminate, so the model can iterate against it.
This is becoming pluggable. The verifier's accept/reject decision remains ground
truth for every task, but the scoring layer around it — how "verified" gets turned
into a number, and eventually how cross-system translations get judged as "proving
the same thing" — is being generalized so alternative scorers (proof-trace heuristics,
an LLM grader evaluated against verifier truth) can be compared on equal footing.
See ROADMAP.md and CLAUDE.md's Scoring policy section.
Repo layout
pyproject.toml # pip install -e .
README.md CLAUDE.md ROADMAP.md
src/babelbench/
server.py # OpenReward env: splits, list_tasks, check/submit tools
paths.py # single source of truth for repo-root/data-dir resolution
verifiers/ # base.py (Status enum, VerifyResult) + one adapter per language
tasks/ # mask.py, translation.py, mutate.py, spec_synth.py,
# perturb_synthesis.py, dwyer/{patterns,generate}.py
scoring/ # score_run.py, score_translation.py, rescore_non_trivial.py,
# eval_mutation_gap.py, deck_analyses.py, present_results.py
rollout/ # run_rollout.py (OpenAI), run_rollout_claude.py (Anthropic/Vertex),
# test_agent.py
tests/ # pytest-runnable: selftest, mutate, translation, paths, rollout-client
scripts/
fv.py # send an FV task into the image, or --selftest it
fetch_datasets.sh # pinned corpus fetch
spec_synth_demo.py
datasets/ # vendored corpora + generated mutations (data, not code)
examples/ # synthesis JSONLs + ironfleet_reference/ (reference only)
results/hackathon/ # audit-verified headline jsonls + AUDIT.md
docs/
archive/ # HANDOFF.md, DECK.md, DECK_BRIEF.md, findings-2026-04-25.md
splits.md # per-split task shape and field semantics
COMPUTE_ESTIMATE.md # due 2026-07-10, see ROADMAP.md
training/tinker/ # vendored RL training cookbook code, own dep stack
Dockerfile # multi-stage "base" image; builds + ships all 8 verifiers
Dockerfile.lite # "lite" image: Dafny, TLA+/TLC, Lean 4, Rocq only
docker/lean-mathlib/ # Lake project pinning Mathlib for both images' Lean stage
.github/workflows/
publish-images.yml # builds + pushes base/lite to ghcr.io on push to master
run_full_eval.sh # 6-tier eval orchestrator
Provenance & licenses
MIT for the environment scaffolding. Vendored corpora retain their upstream licenses
(all permissive — DafnyBench MIT, tlaplus/Examples MIT, FlyVy MIT, FizzBee Apache-2.0,
mypyvy BSD-3-Clause, IronFleet MIT). Full attribution, source repos, and pinned
commits are in datasets/PROVENANCE.md — run bash scripts/fetch_datasets.sh to
populate datasets/ and examples/ironfleet_reference/ from those pins.
IronFleet is vendored as reference material only for synthesis prompts, not
graded directly — it requires Dafny 3.4.0 and ~50K LoC of interdependent proofs that
won't verify file-by-file. Don't try to "fix" this; it's a deliberate scope boundary.
History
Built at the gr.inc OpenReward hackathon (April 2026) as LTLVerifyEnv, a
verifier-grounded RL environment spanning five formal-verification languages. The
hackathon-2026-04-final git tag preserves that state in full, including the eval
logs that were later pruned from master. docs/archive/ carries the deck-era
narrative docs (DECK.md, DECK_BRIEF.md, HANDOFF.md, findings-2026-04-25.md) —
useful for the "how did we get here" story, but their quantitative claims should be
checked against results/hackathon/AUDIT.md before being cited anywhere new.