feat!: standalone env servers - #3162
Merged
Merged
Conversation
… addresses The orchestrator never spawns env servers anymore — it always connects to each source's serve.address and polls until the server is up. Addresses are assigned deterministically at config validation (tcp://127.0.0.1:5000+ across train then eval sources), so the launcher (local rl + multinode sbatch template) spawns the servers in parallel with the orchestrator and both sides agree on where each server lives from the config alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… config Addresses are wiring, not intent — like the transport blocks, they are no longer configurable on sources. The orchestrator derives each server's address from the source's position in the config; the env-server entrypoint takes a required address the launcher writes per source. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mikasenghaas
commented
Jul 30, 2026
…e port EnvServerConfig composes vf's env/serve/legacy blocks verbatim, so an env-server TOML reads like a vf serve config. orchestrator.env_server_base_port offsets the derived port range, giving concurrent runs on one host (e.g. multi-run orchestrators) each their own 1:1 env server. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… eval) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, env_server_addresses -> env_addresses Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
EnvServerConfig -> configs/env.py:EnvConfig, source-level EnvConfig -> SourceConfig, entrypoint moves to entrypoints/env.py, env_server_base_port -> env_base_port, proctitle PRIME-RL::Env Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bumps deps/verifiers to PrimeIntellect-ai/verifiers#2237 (serve CLI removed, ServingConfig renamed ServeConfig); prl's source-level ServingConfig follows suit 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>
…decar # Conflicts: # packages/prime-rl-configs/src/prime_rl/configs/orchestrator.py
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mikasenghaas
marked this pull request as ready for review
August 4, 2026 16:41
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ac2cd8d. Configure here.
The env console script shadows coreutils env in activated shells — back to env-server as the sole entrypoint (still at entrypoints/, orchestrator source class stays EnvConfig as on main). EnvServerConfig regains the identity validation the nested source config used to provide (missing env, mixed v0/v1) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The multi-node srun body is one single-quoted bash -c string, so the
apostrophe in "source's" closed it early: every rendered sbatch failed to
parse with a syntax error near unexpected token '(' and the job died before
any component started. Reword the comment and note the constraint.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
samsja
approved these changes
Aug 4, 2026
eligotts
added a commit
that referenced
this pull request
Aug 5, 2026
Main's standalone env servers (#3162) moved env workers out of the orchestrator process, so the orchestrator-side apply_run_asset_env no longer reaches them. The run image-asset env now rides the launcher's env-server spawn (the process that actually renders and offloads images); the orchestrator-side apply and spawn injection are deleted as dead plumbing. Standalone env servers set VF_RENDERER_IMAGE_OFFLOAD_DIR themselves. Also imports EncodedTensor in trainer/batch.py, surfaced by main's F821 scope fix (#3194). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
Removes the env-server "sidecaring" path entirely: the orchestrator never spawns env servers via subprocess anymore — it always connects.
OrchestratorConfig.env_addressesmaps each(split, name)source totcp://127.0.0.1:<base + i>in config order (train → eval). There is noaddressfield on the source'sserveblock — the launcher and the orchestrator independently derive the same answer from the config alone. The single wiring knob isorchestrator.env_server_base_port(default 5000, likeinference.server.port): concurrent runs on one host give each run its own base, keeping orchestrator↔env-server 1:1.uv run env-serverserves one environment, and is now the only CLI for hosting an env server: the merged companion feat!: remove the serve CLI, rename ServingConfig to ServeConfig verifiers#2237 removed verifiers' unusedserveCLI (and renamedServingConfig→ServeConfig). The entrypoint moves toentrypoints/env_server.pynext to the other console scripts, and its config (EnvServerConfig) is[env]+[serve](vf.ServeConfig: pool, address, max_concurrent) +[legacy], plus prime-rl's[log]— and validates the env identity at parse time (a missing env or a v0legacy.idmixed with a v1 env id fails immediately, as it does on orchestrator sources).envs.pyloses all themultiprocessingspawn/teardown machinery; eachEnvgets its address at construction,Env.start()builds anEnvClientand polls health (600s budget), andEnvs.start()connects all sources with a singleasyncio.gather— since every address is known up front, spawn (launcher) and connect (orchestrator) happen in parallel.rllauncher spawns the servers:write_subconfigswrites oneEnvServerConfigTOML per source (configs/envs/{train,eval}/<name>.toml— the source'sEnvConfigfields plus its derivedaddress) andrl_localstarts oneenv-serverprocess per source, logged tologs/envs/{train,eval}/<name>.log(same paths as before) and monitored like inference/trainer/orchestrator.multi_node_rl.sbatch.j2launches the env servers on the orchestrator node right before the orchestrator (both regular and disaggregated layouts).serveblock is now prime-rl's own two-field config (pool,max_concurrent), consumed by theenv-serverprocess via the written per-source TOML.env_server_base_port; a resumed run keeps its port and reconnects to the same server. Docs and the monitor-run skill are updated to the new process topology.configs/debug/multi-env/rl.toml: two reverse-text train sources + one eval source — the minimal multi-source run exercising one env server per source.Breaking
uv run orchestratorstandalone no longer spawns env servers. Env servers must be running at each source's derived address — start one per source withuv run env-server(an[env]block matching the source, withserve.addressset to the source's derived address; it defaults totcp://127.0.0.1:5000, the first source's address at the default base port).serve.addressis removed from sources. Env-server locations are no longer configurable per source: previouslyNonemeant "spawn a subprocess on a free port" and a set value meant "connect to this external server"; now every source's server lives at its deterministic local address (offset byenv_server_base_port), so pointing a source at a remote env server is no longer expressible. Concurrent runs on one host must set distinctenv_server_base_ports.env-serverconfig restructured:[serve]and[legacy]are top-level blocks and[env]is the verifiers env block directly (previously everything nested under[env]as a source-shaped config).ServeConfigis prime-rl's own two-field config (pool,max_concurrent) instead of avf.ServeConfigsubclass with an optionaladdress.Verification
All runs on 2 GPUs from this branch (with
mainmerged in), viauv run rl:configs/ci/integration/reverse-text/start.toml, 5 steps): launcher spawnsPRIME-RL::EnvServerbound attcp://127.0.0.1:5000, orchestrator connects ("Train environment(s) ready" before inference is even up), reward 0.16 → 0.28, "Training finished!", clean teardown (no leftover processes, exit 0).configs/debug/multi-env/rl.toml, 10 steps): three env servers spawned in parallel at derived5000/5001/5002, per-source configs underconfigs/envs/{train,eval}/(each a vf-shaped[env]/[serve]TOML carrying its derivedserve.address), per-source logs underlogs/envs/{train,eval}/, both train sources contribute rollouts, evals run through the eval env server (reward 0.13 → 0.72 across steps 1/5/10), train reward 0.09 → 0.66, "Training finished!" with exit 0 and no leftover processes or bound ports.ValueError: Unknown split "bogus"at taskset load (~5s after it starts), the full traceback lands inorchestrator.logand the launcher output (which tails it), the launcher logsOrchestrator failed with exit code 1→Terminating all processes..., and exits 1. The env-server logs show clean startup then a gracefulEnvServer down/EnvServerPool downfrom the launcher's teardown — no orphaned processes or bound ports.tests/unitpasses (484 passed after mergingmain; thetest_qwen3_vl_e2efailure is pre-existing onmain).Note
High Risk
This is a breaking change to RL launch topology and standalone orchestrator workflows; misconfigured ports or missing env-server processes will cause rollout hangs or startup failures.
Overview
Breaking: The orchestrator no longer spawns env servers as child processes. Standalone
uv run orchestratorrequires oneuv run env-serverper source already bound at each source’s derived address.Wiring: Each train/eval source gets
tcp://127.0.0.1:<env_server_base_port + i>in config order (train then eval). Per-sourceserve.addressis removed; concurrent runs on one host use distinctorchestrator.env_server_base_portvalues.Launcher & config:
uv run rlwrites per-sourceEnvServerConfigTOMLs underconfigs/envs/{train,eval}/<name>.tomland starts monitoredenv-serverprocesses (logs atlogs/envs/{train,eval}/<name>.log).EnvServerConfigis now top-level[env],[serve], and[legacy]with parse-time validation. Sourceserveon the orchestrator side is onlypoolandmax_concurrentfor the written env-server configs.Orchestrator:
envs.pydrops multiprocessing spawn/teardown;Envtakes a fixed address,start()polls health (600s), andEnvs.start()connects all sources in parallel withasyncio.gather.Ops & tests: Multinode
multi_node_rl.sbatch.j2starts env servers on the orchestrator node before the orchestrator. Multi-run CI starts one env server per orchestrator with matching base ports. Addsconfigs/debug/multi-env/rl.tomland documents the new process topology.Reviewed by Cursor Bugbot for commit d0a79b2. Bugbot is set up for automated code reviews on this repo. Configure here.