{"data":{"kind":"file","path":"README.md","version_id":"h7lmhdszq2bgw12x4xwk42bi","entry":{"name":"README.md","path":"README.md","is_directory":false,"size":24000,"modified_at":"2026-08-11T23:20:25.262000","content_hash":"68646f215dd20fe70a450dc7249db888179f5b6cfa8955133af6b6e9e9324ff0"},"entries":[],"content":"# `rlmath-decomp` — the decomposition environment in `verifiers` format\n\nThe Phase-0 shipping artifact (DIRECTION.md §5.5): `harness.episode.run_episode` wrapped so\nPrime Intellect's Environments Hub can serve it and `prime-rl` can train against it (§5.3).\nOne completion = one decomposition plan = one kernel verdict.\n\nCode: `src/rlmath/envs/decomp_env.py`. Tests: `tests/test_env.py`. Hub package:\n`environments/rlmath_decomp/` (re-export only — one scoring path, one set of tests).\n\nThis file is also the Hub package's `README.md`, verbatim: `environments/rlmath_decomp/README.md`\nis a copy, and `tests/test_env.py::test_hub_package_readme_is_a_verbatim_copy` fails if they drift.\nEdit this one.\n\nEvery `verifiers` fact below is checked against the **installed** 0.3.0 source, not\nresearch/verifiers.md — the notes were taken from a repo clone and are wrong in two places, both\nnow fixed here (see \"What the research notes got wrong\").\n\n## Which verifiers surface this targets, and why\n\n`verifiers` is mid-migration (research/verifiers.md §1). Both stacks ship in 0.3.0:\n\n| | v0 (`import verifiers as vf`) | v1 (`import verifiers.v1 as vf`) |\n|---|---|---|\n| Objects | `SingleTurnEnv` / `Rubric` / `Parser` | `Taskset` / `Task` / `TaskData` / `Harness` / `Trace` |\n| Status | default namespace, **documented deprecated** | what new environments are told to target |\n| Entry point | `load_environment(**kwargs)` | `Taskset` subclass exported in `__all__` |\n| `prime-rl` | **not used at all** | the only surface its orchestrator speaks |\n\n**Primary target: v1.** DIRECTION.md §5.3 names `prime-rl` as the trainer and §5.5 makes the\npublished environment the Phase-0 artifact; `prime-rl`'s orchestrator is built exclusively against\n`verifiers.v1` (research §6), so the training path decides it.\n\n**v0 is kept as a thin shim** over the same scorer (`load_environment`), because it is ~40 lines\nonce `score_plan` exists and it buys the whole legacy eval toolchain — `prime eval run`, `vf-eval`,\n`prime gepa run` — which the Phase-2 zero-shot study (§5.5) needs long before a trainer.\n\nBoth surfaces call one function, `decomp_env.score_plan`, which calls `run_episode`. There is no\nsecond scoring path and no second sanitizer.\n\n## Install\n\n`verifiers` is heavy (pulls the training stack), so it sits behind an optional extra. The module\nimports and the offline tests pass **without** it.\n\n```bash\nuv sync                       # core: harness, tests, no verifiers\nuv sync --extra envhub        # + verifiers, for the Hub / eval / training surfaces\nuv tool install prime         # the Prime CLI (companion; loads verifiers as a plugin)\n```\n\n`decomp_env.HAS_VERIFIERS` / `HAS_VERIFIERS_V0` / `HAS_VERIFIERS_V1` report what is present;\n`build_taskset()` and `load_environment()` raise a clear `ImportError` naming `uv sync --extra\nenvhub` when it is not.\n\n## Wire format\n\nSingle-shot, line-oriented, chatter-tolerant outside the markers and strict inside. Parsed by\n`core/plan_format.py`; the exact spec is in `decomp_env.SYSTEM_PROMPT`, which is the only place the\npolicy is told about it.\n\n```text\n#lemma <name> : <prop>      zero or more, one per line, before #assembly\n#assembly                   exactly once, after all #lemma lines\n<tactic lines>              verbatim, as many as needed\n#end                        required\n```\n\n- `<name>` matches `[A-Za-z][A-Za-z0-9_']*`, is unique in the plan, does not start with `_`, is not\n  reserved (`_plan`, `_stmt_check`, `_proof_check`, `goal`, `this`) and is not the goal's own\n  declaration name — a lemma that would shadow the goal is rejected, never silently renamed\n  (`harness/composer.py`).\n- `<prop>` is a closed Lean 4 proposition on one line. Mathlib is imported.\n- Inside the assembly every lemma is in scope as a hypothesis under the stated name and proposition.\n- Zero `#lemma` lines is legal: the direct-close action, where the assembly alone must prove the goal.\n- Text before the first marker and after `#end` is ignored; anything else between them is a\n  `format_error`.\n- Banned anywhere (`rlmath/sanitize.py`): `sorry`, `admit`, `axiom`, `native_decide`, `set_option`,\n  `macro`, `macro_rules`, `elab`, `notation`, `notation3`, `syntax`, `unsafe`, `partial`, `opaque`,\n  `implemented_by`, `extern`, `initialize`, `#exit`.\n\nExample (goal `∀ x : ℝ, 0 ≤ x ^ 2 + 2 * x + 1`):\n\n```text\n#lemma h1 : ∀ x : ℝ, 0 ≤ (x + 1) ^ 2\n#lemma h2 : ∀ x : ℝ, (x + 1) ^ 2 = x ^ 2 + 2 * x + 1\n#assembly\nintro x\nhave h := h1 x\nrw [h2 x] at h\nexact h\n#end\n```\n\n## Reward semantics\n\n**Terminal reward only** (DIRECTION.md §5.6): `reward = 1.0` iff the composed artifact passes the\nLean kernel *and* the axiom audit, else `0.0`. `r_plan` (§5.2) is diagnostic and is deliberately\n**not** mixed into the scalar — it reaches the logs as the status taxonomy and `plan_stats`.\nBudgets are hard caps, not cost penalties, for the reason given in §5.6: penalties suppress\ndecomposition before it can pay off.\n\nEvery other channel is weight-0 (a metric, not a reward): one 0/1 indicator per status — so a group\nmean *is* that status's rate — plus `leaf_attempts_used`, `elapsed_s` and the `plan_*` shape\nstatistics from `harness/detectors.py`. The full JSON blob (including `detail`, truncated to\n`INFO_DETAIL_CHARS`) rides the per-sample info channel.\n\nThe two real channels, per surface:\n\n| | numbers | the JSON blob |\n|---|---|---|\n| v1 | `Trace.record_metrics(...)` → `trace.metrics` (`dict[str, float \\| None]`) | `trace.info[\"rlmath\"]` (`dict[str, Any]`, the declared task-metadata scratch space) |\n| v0 | weight-0 rubric functions → the `metrics` columns | `state[\"rlmath\"]` |\n\nBoth v1 dicts are ordinary pydantic fields on `Trace` and survive `Trace.to_record()` into\n`traces.jsonl`; the blob must **not** go on `trace.metrics`, which is float-typed and feeds the\naggregate means. `tests/test_env.py::test_v1_p4_diagnostics_reach_the_trace_channels` asserts the\nwhole path, `to_record()` included — it is the regression guard for the one failure mode that\nwould make a finished training run uninterpretable.\n\nThese are the §5.7 **P4** instrument (\"degenerate restatement rises under RL; hard budgets contain\nit\"). `plan_restatement_max ≈ 1.0` means the root restated the goal as a single lemma and delegated\nit — a decomposition that decomposes nothing. It must be in the training logs from the first run,\nnot added after the plot looks wrong.\n\n### Status taxonomy (`core/types.py`, verbatim semantics)\n\n| Status | Stage that failed | Class |\n|---|---|---|\n| `verified` | — kernel-checked and axiom-audited | success (reward 1.0) |\n| `format_error` | completion did not parse into a plan (or a lemma name collides with the goal) | policy failure |\n| `sanitizer_rejected` | banned token, multi-declaration artifact, or axiom-audit failure | policy failure |\n| `statement_ill_formed` | a lemma statement failed elaboration | policy failure |\n| `plan_invalid` | assembly fails **even granting all the lemmas** | policy failure |\n| `leaf_failed` | plan valid, but a lemma resisted the frozen leaf prover | policy failure |\n| `compose_failed` | plan + leaves ok, spliced artifact failed the kernel (rare — always investigate) | policy failure |\n| `budget_exhausted` | max lemmas or max leaf attempts hit | policy failure |\n| `context_window_exceeded` | root prompt/completion beyond the model's window | **feasibility evidence, not scored as failure** (excluded from mean scores) |\n| `error` | infrastructure: dead Lean server, timeout, network | **not evidence** — re-run the sample |\n\nStatus separation is non-negotiable (§5.7, §6): `plan_invalid` / `leaf_failed` / `budget_exhausted`\n/ `context_window_exceeded` / `statement_ill_formed` must never share a bucket, or Phase 3's\ntransfer plot is uninterpretable after the fact. The last two rows are never produced by this\nwrapper: `run_episode` lets backend exceptions propagate so the runner can record an error row and\nre-run it (`../rl`'s `repair_errors.py` pattern), and the window guard belongs to the runner, which\nknows the root model's context size.\n\n## Context isolation (DIRECTION.md §5.1–§5.2)\n\nThe RLM premise being tested is context isolation via external state, so the prompt path is the\nexperiment:\n\n- The policy sees **goal statements and child statuses only — never proof text.** Leaf proofs and\n  the composed artifact live in `EpisodeResult`, go to the kernel and the result log, and are never\n  rendered into a message. `tests/test_env.py::test_prompt_never_contains_proof_text` asserts this\n  against a real verified artifact rather than by inspection.\n- The format spec sits in a **real system slot on both surfaces**, never inlined into the user turn:\n  v0 `SingleTurnEnv(system_prompt=...)`, v1 `TaskData.system_prompt` (placed by\n  `Harness.resolve_prompt` — emitted as a system message by any harness with\n  `APPENDS_SYSTEM_PROMPT`, e.g. `null`). `TasksetConfig.system_prompt` is a file-path override\n  applied on iteration, which is what makes the environment GEPA-optimizable.\n- The action space is **restricted to decomposition** (`decompose` / direct `close`), not a free\n  REPL. §5.1's design note: given a free REPL in `../rl`, the root wrote a Python BFS and made zero\n  sub-calls. The analog here would be a root string-generating whole Lean proofs, at which point the\n  experiment measures program synthesis instead of decomposition policy.\n- **Two-stage verification** (§5.2) is what makes the reward informative: the assembly is checked\n  *granting* the lemmas as hypotheses, before any leaf runs, so `plan_invalid` and `leaf_failed`\n  are separable and `r = r_plan × r_leaves` factorizes. The prompt states this contract to the\n  policy explicitly, because it changes what a good plan looks like.\n- v1 is single-shot on purpose (§5.1): vanilla GRPO on completions, no multi-turn machinery.\n  Reacting to failed children is v1.5.\n\n## Live resources (the one piece of construction friction)\n\nA rollout needs a `LeanBackend`, a `LeafProver` and `Budgets`. v1 puts only serializable data on the\nwire — `TaskData` is a frozen pydantic model shipped per rollout, and a `TasksetConfig` is parsed\nfrom TOML/CLI — so a REPL pool or an OpenAI-backed leaf can travel through neither. They are\nregistered process-globally instead:\n\n```python\nfrom rlmath.core.types import Budgets, GoalSpec\nfrom rlmath.envs import EpisodeResources, set_resources, build_taskset, load_environment\n\nbackend = ...   # rlmath.lean.repl_pool.ReplPool | rlmath.lean.kimina.KiminaClient\nleaf    = ...   # rlmath.leaf.LeafProver\n\n# v1 (primary): registers the resources and returns the taskset\ntaskset = build_taskset(goals=\"data/goals.jsonl\", backend=backend, leaf=leaf,\n                        budgets=Budgets(max_lemmas=8))\n\n# v0 (shim): same handles, passed directly or read from the registry\nenv = load_environment(goals=\"data/goals.jsonl\", backend=backend, leaf=leaf)\n\n# CLI paths (`prime eval run`) cannot pass objects through --env-args:\nset_resources(EpisodeResources(backend=backend, leaf=leaf, budgets=Budgets(), goals=[...]))\n```\n\nBudget knobs are v1 **task**-config fields (`DecompositionTaskConfig`: `max_lemmas`,\n`leaf_attempts_per_lemma`, `max_total_leaf_attempts`, `verify_timeout_s`), so an RL run can sweep\n§5.6's hard caps from TOML (`[env.taskset.task]`) or the CLI\n(`--env.taskset.task.max-lemmas 4`) without touching code.\n\nThey are on the **task** config, not the taskset config, for a reason that only shows up under\nprime-rl: in a served run the client owns the taskset and the env server \"never `load()`s data\" —\nit rebuilds each task as `task_cls(wire_data, env.config.taskset.task)`\n(`verifiers/v1/serve/server.py::_build_task`). Budgets applied as a side effect of `load()` would\nbe set in the wrong process and the caps would silently revert to defaults in the one place they\nmatter most.\n\n### Loading works anywhere; running needs the resources\n\n`load_environment()` and `build_taskset()` take **no required arguments**, and\n`DecompositionTaskset(DecompositionConfig(id=\"rlmath-decomp\"))` constructs off a default config.\nWith nothing registered they build over `decomp_env.DEMO_GOALS` — four small true Lean props with\nids `demo-00`…`demo-03`. That is not a convenience: the Hub's integration test imports and loads a\npublished environment on infra that has **no Lean toolchain**, and `prime env info` / v1 discovery\ndo the same, so an environment that can only be constructed next to a live kernel cannot be\npublished at all. The demo set is deliberately tiny and `demo-`-labelled so no results table can\nmistake it for a run against the real bank.\n\nGoal resolution is one rule everywhere (`decomp_env.resolve_goals`): **explicit argument →\nregistered `EpisodeResources` → demo set.** The demo set is a tail, not a repair — resources\nregistered with an *empty* goal list still raise, so a misconfigured run cannot quietly become a\nfour-sample green report.\n\n**Scoring gets no fallback of any kind.** Every rollout path goes through\n`decomp_env.live_resources()`, which raises a `RuntimeError` naming `set_resources` when a backend\nor leaf is missing. A missing kernel scored as `0.0` is indistinguishable from a policy failure in\nevery downstream mean (§6, §5.7), and on a hosted eval it would read as a model that never proves\nanything. Handles are resolved per rollout, not captured at construction, so a launcher may build\nthe environment first and register the backend afterwards — the shape the CLI paths force.\n(`EpisodeResources.backend` / `.leaf` are therefore optional *fields*: a process may register the\ngoals it has without pretending to a kernel it does not. `get_resources()` answers \"what is\nregistered\", `live_resources()` answers \"can this process run an episode\" — only the second one\ngates scoring.)\n\nOne library caveat, on the **v0 surface only**: verifiers' `Rubric` catches an exception raised by a\nreward function and records `0.0` with a logged `ERROR`\n(`rubrics/rubric.py::_call_individual_reward_func`) — so a resource-less v0 eval yields an error log\nper rollout plus an all-zero column rather than a crash. That fallback is the framework's, and it is\nthe same one a dead Lean server hits today. The v1 path — the one `prime-rl` trains on — propagates\nthe failure as a `TaskError` and leaves the reward **unscored (`None`)** on the trace. Nothing in\nthis repo ever *returns* a fabricated number on either path;\n`tests/test_env.py::test_v0_bare_environment_raises_at_score_time_instead_of_scoring_zero` and its\nv1 sibling pin both halves.\n\n### Goals dataset\n\n`load_goals` takes a JSONL path or any iterable of `GoalSpec`/dict. Rows:\n\n```json\n{\"id\": \"lw_00123\", \"prop\": \"∀ n : ℕ, n + 0 = n\", \"name\": \"thm_lw_00123\"}\n```\n\n`prop` is required (a row without one raises — a silently shortened dataset makes two runs\nincomparable). `id` falls back to `source_id` / `statement_key` (the shapes\n`scripts/build_bank.py` writes) and then to the row index; `name` defaults to `goal`.\n\n### Running a local eval in-process\n\nThe CLIs (`vf-eval`, `prime eval run`, `uv run eval`) each run in their own process, so nothing can\nhand them a live `LeanBackend` — `set_resources` has to happen inside the process that scores. Until\nthe backend is a service (the Phase-3 item at the end of this file), a local eval is an in-process\nscript:\n\n```python\nenv = load_environment(goals=[...], backend=ReplPool(n_workers=1), leaf=..., budgets=Budgets(...))\nout = env.evaluate_sync(client=vf.ClientConfig(client_type=\"openai_chat_completions\",\n                                               api_base_url=\"http://localhost:11434/v1\",\n                                               api_key_var=\"OLLAMA_API_KEY\"),\n                        model=\"...\", num_examples=3, rollouts_per_example=1,\n                        state_columns=[\"rlmath\"])\n```\n\nTwo things that are easy to get wrong, both found by running it:\n\n- **`client=` wants a `vf.ClientConfig` (or a `vf.Client`), not an `AsyncOpenAI`** — `resolve_client`\n  rejects a raw OpenAI client with `Unsupported client type`.\n- **`state_columns=[\"rlmath\"]` is required for the JSON blob to reach the saved rows.** The float\n  metric columns are promoted automatically; anything parked in `state` is not. Omit it and the\n  status/`plan_stats` numbers survive but `detail` and the rest of the blob are dropped — exactly\n  the silent-diagnostic-loss failure mode this whole channel exists to prevent.\n\nThe first rollout of a run pays the REPL's cold `import Mathlib` (~30 s here, and it lands in that\nrollout's `elapsed_s`); warm episodes cost milliseconds. Budget the first one accordingly rather\nthan reading it as a per-episode cost.\n\n## The Hub package\n\n`environments/rlmath_decomp/` — built, committed, **not pushed**. Layout matches what the real v1\nscaffolder emits (`init rlmath-decomp`, a console script `verifiers` installs;\n`verifiers/v1/cli/init.py`), because the tooling's layout beats a hand-drawn sketch:\n\n```text\nenvironments/rlmath_decomp/\n├── pyproject.toml\n├── README.md                # a verbatim copy of this file (kept in sync by a test)\n└── rlmath_decomp/\n    ├── __init__.py          # re-exports from rlmath_decomp.taskset; names them in __all__\n    └── taskset.py           # re-export only — from rlmath.envs.decomp_env import ...\n```\n\nRe-export, never a copy of the wrapper: a second scoring path in the published artifact is a second\nthing to keep correct, and only one of them would have tests.\n\nBoth entry points come out of that one module:\n\n- **v1** — the loader imports `rlmath_decomp` and picks the single `vf.Taskset` subclass out of\n  `__all__` (`verifiers/v1/utils/loaders.py::_plugin_class`; **exactly one** — a second export is a\n  hard error), then constructs it as `DecompositionTaskset(config)`.\n- **v0** — `verifiers.load_environment(\"rlmath-decomp\")` imports the same module and calls\n  `load_environment(**env_args)` by name (`verifiers/utils/env_utils.py`).\n\n`pyproject.toml` uses `[tool.hatch.build.targets.wheel] packages = [\"rlmath_decomp\"]` (the v1\nscaffolder's form) rather than the older v0 `[tool.hatch.build] include = [...]`; `tags` and\n`[tool.verifiers.eval]` are kept because the Hub pipeline reads them off the pyproject. Nothing in\nthe installed `verifiers` reads either, so they are unverified locally.\n\n**Resolved 2026-08-11:** `rlmath` is not on PyPI, so the dependency is a git-dep on the public\nrepo, **pinned to a full commit SHA** (supply-chain review: a floating branch ref would let two\ninstalls of the same published env version resolve to different `rlmath` code — the environment\nmust be reproducible). `tests/test_env.py` enforces the 40-hex pin.\n\n### Publishing steps\n\n1. **Auth.** `uv tool install prime && prime login`  ✓ done 2026-08-11 (team Eumemic).\n2. **Re-pin the `rlmath` dependency to the pushed HEAD.** Before *every* `prime env push`:\n   `git push` the library first, then set the dep in `environments/rlmath_decomp/pyproject.toml`\n   to `rlmath @ git+https://github.com/eumemic/rlmath@$(git rev-parse HEAD~0)` for the commit the\n   push made public, bump the package `version`, and commit the pin. (The pin commit itself need\n   not be inside the pinned tree — installers fetch the pinned SHA, which already contains the\n   whole library.)\n3. **Verify locally first.** `prime eval run rlmath-decomp -m <model> -n 5` (or `vf-eval`, or the v1\n   `eval` script), with a live Lean backend and leaf registered via `set_resources` and a harness\n   that does not need a container:\n   `uv run eval rlmath-decomp --env.agent.harness.id null --env.agent.runtime.type subprocess -n 3`.\n   `null` is the tool-less chat loop (`EXECUTES_CODE = False`, `NEEDS_CONTAINER = False`,\n   `APPENDS_SYSTEM_PROMPT = True`) — the right shape for a single-shot plan emitter. A code-executing\n   harness would hand the policy a shell and quietly turn §5.1's restricted action space back into a\n   free REPL.\n4. **Push.** `prime env push rlmath-decomp --visibility PRIVATE|PUBLIC` (defaults to\n   `./environments/rlmath_decomp`). Visibility is **a user decision, never a default** — research\n   §7.2: \"Publishing is an external state change... Do not publish merely because local verification\n   passed.\"  ✓ **Pushed 2026-08-11, PUBLIC:**\n   https://app.primeintellect.ai/dashboard/environments/eumemic/rlmath-decomp\n5. **Hosted eval.** `prime eval run rlmath-decomp --hosted --follow` — will not produce rewards yet.\n   *Loading* the published environment works anywhere, with no Lean toolchain and nothing registered\n   (that is what the Hub's post-push integration test does, and what versions 0.1.0/0.1.1 failed\n   before the demo-goal fallback existed); *running* it needs a Lean toolchain + leaf prover, which\n   are process-local (`set_resources`), not services. A hosted rollout therefore raises at score\n   time instead of reporting zeros — deliberately, per \"Loading works anywhere\" above. Phase-3 item.\n6. **Consume elsewhere.** Because `rlmath` is a git-dependency, resolvers with transitive-URL\n   protection (uv, and pip against PyPI-style indexes) require it declared **explicitly alongside**\n   the wheel — this is the consumer-side mirror of the supply-chain pin, not a packaging bug:\n\n   ```bash\n   uv pip install rlmath_decomp \\\n     \"rlmath @ git+https://github.com/eumemic/rlmath@<pinned-sha-from-the-wheel-metadata>\" \\\n     --extra-index-url https://hub.primeintellect.ai/eumemic/simple/\n   ```\n\n   (verified 2026-08-11 in a clean venv). `prime env install eumemic/rlmath-decomp` works for\n   prime-managed contexts; `prime env info eumemic/rlmath-decomp` lists all install methods.\n\nScaffolders, if a from-scratch package is ever preferred: `init <name>` emits a **v1** taskset\npackage (`--v0` for the legacy stub); `prime env init <name>` / `vf-init <name>` emit the **v0**\nstub.\n\n### Training with prime-rl\n\nOrchestrator config sections are `[[orchestrator.train.source]]` / `[[orchestrator.eval.source]]`\n(research §6). Sketch:\n\n```toml\n[[orchestrator.train.source]]\nname = \"rlmath-decomp\"\n\n[orchestrator.train.source.env.taskset]\nid = \"rlmath-decomp\"\ngoals = \"data/goals.jsonl\"\n\n# §5.6's hard caps live on the *task* config — the subtree the env server rebuilds\n# each task from. Putting them one level up would not reach the scoring process.\n[orchestrator.train.source.env.taskset.task]\nmax_lemmas = 8\nleaf_attempts_per_lemma = 4\n\n[orchestrator.train.source.env.agent.harness]\nid = \"null\"\n\n[orchestrator.train.source.env.agent.runtime]\ntype = \"prime\"\n```\n\nOpen question for that step (§5.3 assumes a Kimina/REPL backend on a large-CPU box): the Lean\nbackend and the frozen leaf are *outside* the rollout runtime here, reached through\n`set_resources` in the **env-server** process, so a `prime`/`modal` runtime needs them exposed as\nservices rather than in-process. That is a Phase-3 integration decision, not a Phase-0 blocker.\n\n## What the research notes got wrong\n\nKept as a record, because the wrapper was written against the notes before the library was\ninstalled and both errors were load-bearing:\n\n| research/verifiers.md said | installed 0.3.0 |\n|---|---|\n| §5.1 \"only `TaskData` is stored on the trace\"; the harness owns the system slot, so a v1 task has one `prompt` field | `TaskData.system_prompt` exists. The spec is a real system message, not text inlined into the user turn. |\n| a `Trace` carries \"rewards + metrics + errors\" — no call named | `Trace.record_metric(name, float)` / `record_metrics(mapping)` write `trace.metrics`; `trace.info` is the declared task-metadata dict. Explicit methods, not attribute assignment. |\n| §5.2 \"Don't override `Taskset.__init__`\" (correct) but the constructor's arity was unstated | `Taskset(config)`, positional; `load_taskset` calls `taskset_class(config.id)(config)`. |\n\nNot wrong, but only visible in the source: the env server never calls `load()`, which is why the\nbudgets moved to the task config (above).\n","encoding":"utf-8","truncated":false,"total_bytes":24000},"status":null}