feat: orchestrator owns v1 tasksets, ships task data to env servers#3043
Merged
Conversation
Companion to PrimeIntellect-ai/verifiers#2039. A v1 env's taskset is loaded once, in the orchestrator: finite tasksets become the train source's shuffled example table (real tasks, not index ranges); an infinite taskset's generator is pulled per example — no server-side materialization, no idx-addressed cache, no requirement that every pool worker regenerate the same sequence. Each dispatched rollout ships its task's data (task.data.full_dump()) and the env server pydantic-validates and runs it. The legacy (v0) bridge keeps its server-side dataset and task_idx addressing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…removed) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
samsja
previously approved these changes
Jul 16, 2026
…asksets re-land on top Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Companion to the rebased PrimeIntellect-ai/verifiers#2039. A v1 env's taskset is loaded once, in the orchestrator: finite tasksets become the train source's shuffled example table (real tasks, not index ranges); an infinite taskset's generator is pulled per example. Each dispatched env-rollout ships its task's data (task.data.model_dump()) and the env server validates and runs it. The legacy (v0) bridge keeps its server-side dataset, task_idx addressing, and the run_group route. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…039) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Finite tasksets still materialize at start() (num_tasks needs the count and the train source shuffles epochs), but the field is a single iterator either way — consumers pull it uniformly and branch on num_tasks alone. Consumed once, by TrainSource or EvalEnv.start. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mikasenghaas
marked this pull request as ready for review
July 22, 2026 20:38
hallerite
approved these changes
Jul 22, 2026
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d08a752. Configure here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Companion to PrimeIntellect-ai/verifiers#2039 (client-side tasksets / stateless env server); pins
deps/verifiersto that branch's tip and tracksmain(latest merge picked up the NIXL/ModelExpress work).Env.start()loads it once (off the event loop) instead of asking the server forinfo. Every dispatched env-rollout ships its task's data on the request (task.data.model_dump()); the env server pydantic-validates it into the taskset's declaredTaskDatatype and runs it.TrainSourceserves real tasks, not index ranges: a finite taskset is a shuffled example table of the loaded tasks (reshuffled per epoch); an infinite taskset's generator is pulled per example — no server-side materialization, no idx-addressed task cache, no requirement that every pool worker regenerate an identical sequence.EvalEnvselects its examples off the same client-side tasks (an infinite taskset's fixed eval set is pulled off the generator once and reused every epoch).GroupStatecarries the group'svf.Task; the dispatcher sendstask_datafor v1 envs. The legacy (v0) bridge keeps its server-side dataset,task_idxaddressing,infocount, and therun_grouproute.Verification
On 2 local GPUs, from this branch pair (5 steps each):
uv run rl @ examples/basic/reverse-text/rl.toml --max-steps 5— orchestrator loadsreverse-text-v1client-side (num_tasks=1000), all 5 orchestrator steps complete through the episode wire with 0% rollout errors; env-server workers logtasks=client-side(no dataset load in any worker).uv run rl @ examples/basic/alphabet-sort/rl.toml --max-steps 5—num_tasks=infinite; 115 distinct tasks (idx 0–114) streamed straight off the client-side generator, multi-turn rollouts with 0% errors, trainer completed 5/5 ("Training finished!"); the env server materializes nothing.Note: at
max_steps=5the reverse-text run's trainer tail stalls after the orchestrator exits — that is main's known ship-time lag race (RES-1077, fixed by #3050, still open): the identical command with unmodifiedmaincode in the same environment stalls the same way (and ships fewer batches, 1 vs 2). The rollout path this PR changes is unaffected.Orchestrator unit tests pass (95).
Breaking
env.address.task_datarequests); an old orchestrator cannot drive a new env server for v1 envs and vice versa.🤖 Generated with Claude Code
Note
Medium Risk
Touches core rollout scheduling and env I/O with a breaking wire-protocol dependency on verifiers; legacy paths are preserved but misconfiguration (missing taskset, version skew) would fail rollouts at runtime.
Overview
Moves v1 env task ownership to the orchestrator:
Env.start()loads each taskset once (finite sets materialized off the event loop; infinite sets keep a client-side generator) instead of relying on serverinfo()for task counts. Legacy (v0) envs still use server-side datasets andtask_idx+run_groupfor group scoring.Dispatch path:
TrainSourceandEvalEnvyield examples that include the fullvf.Taskwhere applicable;GroupStateholds that task; the dispatcher callsenv.run(..., task_data=...)for v1 andtask_idxfor legacy.Env servers become stateless for task data—workers no longer load datasets or resolve tasks by index for v1—at the cost of requiring the taskset importable in the orchestrator venv and a matching verifiers wire protocol (
task_dataon run requests).Reviewed by Cursor Bugbot for commit d08a752. Bugbot is set up for automated code reviews on this repo. Configure here.