Skip to content

feat: orchestrator owns v1 tasksets, ships task data to env servers#3043

Merged
mikasenghaas merged 16 commits into
mainfrom
feat/client-side-tasksets
Jul 22, 2026
Merged

feat: orchestrator owns v1 tasksets, ships task data to env servers#3043
mikasenghaas merged 16 commits into
mainfrom
feat/client-side-tasksets

Conversation

@mikasenghaas

@mikasenghaas mikasenghaas commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary

Companion to PrimeIntellect-ai/verifiers#2039 (client-side tasksets / stateless env server); pins deps/verifiers to that branch's tip and tracks main (latest merge picked up the NIXL/ModelExpress work).

  • The orchestrator now owns each v1 env's taskset: Env.start() loads it once (off the event loop) instead of asking the server for info. 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 declared TaskData type and runs it.
  • TrainSource serves 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.
  • EvalEnv selects 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).
  • GroupState carries the group's vf.Task; the dispatcher sends task_data for v1 envs. The legacy (v0) bridge keeps its server-side dataset, task_idx addressing, info count, and the run_group route.

Verification

On 2 local GPUs, from this branch pair (5 steps each):

  • v1 finite: uv run rl @ examples/basic/reverse-text/rl.toml --max-steps 5 — orchestrator loads reverse-text-v1 client-side (num_tasks=1000), all 5 orchestrator steps complete through the episode wire with 0% rollout errors; env-server workers log tasks=client-side (no dataset load in any worker).
  • v1 infinite: uv run rl @ examples/basic/alphabet-sort/rl.toml --max-steps 5num_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=5 the 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 unmodified main code 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

  • Running a v1 env now requires its taskset package importable in the orchestrator's venv (it always was in practice — config narrowing already imported it — but the data load itself now happens orchestrator-side). This also applies when pointing at an external env server via env.address.
  • Requires the verifiers wire-protocol change in feat(v1): client-side tasksets + stateless env server (v1-only) verifiers#2039 (task_data requests); 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 server info() for task counts. Legacy (v0) envs still use server-side datasets and task_idx + run_group for group scoring.

Dispatch path: TrainSource and EvalEnv yield examples that include the full vf.Task where applicable; GroupState holds that task; the dispatcher calls env.run(..., task_data=...) for v1 and task_idx for 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_data on run requests).

Reviewed by Cursor Bugbot for commit d08a752. Bugbot is set up for automated code reviews on this repo. Configure here.

mikasenghaas and others added 2 commits July 15, 2026 20:51
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>
mikasenghaas and others added 3 commits July 15, 2026 21:43
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
samsja previously approved these changes Jul 16, 2026
mikasenghaas and others added 2 commits July 22, 2026 00:27
…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>
mikasenghaas and others added 9 commits July 22, 2026 17:14
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
mikasenghaas requested review from hallerite and samsja July 22, 2026 20:37
@mikasenghaas
mikasenghaas marked this pull request as ready for review July 22, 2026 20:38

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread src/prime_rl/orchestrator/train_source.py
@mikasenghaas
mikasenghaas merged commit f5946b0 into main Jul 22, 2026
22 checks passed
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.

3 participants