feat(orchestrator): add trace support on algorithm abstraction#2857
feat(orchestrator): add trace support on algorithm abstraction#2857willccbb wants to merge 228 commits into
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Points the submodule at the vf-nano EnvServer branch so the orchestrator can build on the env-server abstraction. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch prime-rl's env path to vf-nano: the orchestrator spawns a vf-nano EnvServer per env (it never loads an environment), dispatches rollouts by task index, and trains on the returned Trace dicts (branches + renderer tokens). - pyproject: dep verifiers -> vf-nano; drop v1/research env packages; only the vf-nano reverse-text example; override out the transitive v1 verifiers (pulled by the prime CLI) so it can't shadow vf-nano's `verifiers` package; add orjson /pandas/msgspec (were transitive via verifiers). - EnvConfig inherits vf-nano's swappable agent/runtime (+ max_turns). - envs.py: spawn EnvServer child + EnvClient, info() for num_tasks/group-scoring, dispatch by task_idx, adapt Trace -> RolloutOutput-shaped dict. - trajectories.py: trace_to_samples (one sample per Trace branch) + trace_to_output. - train_source: index sampling; client pool builds vf-nano ClientConfig; lag monitor vendored; env-server entrypoint repointed; ~14 files retyped off vf.RolloutOutput / vf.ClientConfig. - configs/debug/vf_nano_reverse_text.toml. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er config)
- trace_to_samples stitches each Trace branch's tokens into one TrainingSample
(prompt = branch start, then each turn's new context [masked] + generated
tokens [trained]); drop the RolloutOutput adapter — read the Trace's native
fields directly (reward, error{type,message}, timing generation/scoring,
num_turns, branches).
- envs returns the raw Trace; eval_sink / train_sink / dispatcher / metrics /
orchestrator read native Trace fields (no token_usage/completion/timing.total).
- client pool forwards the shared renderers.RendererConfig to the env server's
renderer client (so it uses qwen3, not the tool-less default fallback).
- debug config: tool_call_parser=hermes (vLLM accepts the agent's tools),
max_steps=20.
- bump deps/vf-nano.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…o timeout) - Env.run_rollout/run_group pass the vf-nano ClientConfig object and a SamplingConfig (built from the env's sampling args) directly — no model_dump, no per-rollout timeout forwarded to the server. - debug config: max_steps=20. - bump deps/vf-nano (typed env-server RPC).
The env server returns a Trace minus its derived fields; the orchestrator resolves
the env's Task subclass (from config.id) and validates the wire dict into a strict
Trace[EnvTask], so the whole orchestrator works with a real, typed vf.Trace —
typed task fields included (e.g. task.answer), nothing subscriptable.
- envs.py: resolve_task_type(env_id); run_rollout/run_group validate -> Trace[EnvTask].
- trajectories/types/dispatcher/train_sink/eval_sink/metrics/filters/advantage/utils
/orchestrator: attribute access on the typed Trace (reward, error{type,message},
branches, timing.<span>.duration, num_turns, ...); derived fields recompute on the
consumer.
- Task/Trace/TimeSpan stay strict (StrictBaseModel) — no extra=ignore anywhere.
- bump deps/vf-nano.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The orchestrator spawns the env server, so request the serve extra (zmq/msgpack) explicitly now that vf-nano keeps them out of core. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`from __future__ import annotations` already defers all annotations to strings, so the quotes + `# noqa: F821` on the TYPE_CHECKING-only `vf.Trace` / `TrainRollout` annotations are unnecessary (no import cycle — verifiers.nano never imports prime_rl). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The field holds a typed vf.Trace, so `trace` reads truer than `raw` (which suggested an unparsed dict). Renames the field + every `.raw` access, the `emit_rollout(trace=...)` param/kwarg, the to_dict field filter, and the dispatcher cancel-path locals. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Drop the FinishedRollout proxy properties (error/reward/is_truncated and the
example_id field); consumers now read r.trace.{reward,is_truncated,task.idx,...}
directly. The trace is the single source of truth.
- Use vf.Trace.has_error for existence checks instead of `.error is not None`.
- Replace the prime-rl trace_* token-length utils with vf.Trace.{completion_len,
total_tokens,has_response} (now on the trace); keep trace_to_samples.
- Carry task_idx end-to-end (GroupState.task_idx, env.run_rollout/run_group(task_idx),
source dict key) instead of the example/example_id dict carrier; identity comes
off trace.task.idx.
- Mark the local-package env arrangement as a temporary/experimental TODO.
- Move the debug config to configs/debug/nano/reverse_text.toml.
- Bump deps/vf-nano (Trace/Turn accessors).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- The env server binds tcp://127.0.0.1:0 and reports its concrete address back over a queue; the orchestrator connects to that. Removes _get_free_port and its TOCTOU race (the OS assigns the port atomically). - A spawned server has already bound + loaded by the time it reports its address, so the untimed info() is enough — only poll wait_for_server_startup for an external (config.address) server, which has no spawn handshake. - Bump deps/vf-nano (port report + Trace/Branch token-length accessors). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Task-subclass introspection now lives in vf-nano (vf.task_type); drop the prime-rl copy and build the typed Trace via vf.Trace[vf.task_type(env_id)]. Bump deps/vf-nano. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SFT trains on a teacher served over the chat client, which returns no token ids, so the trace's turns have tokens=None and trace_to_samples yields nothing. Restore backfill: for each tokenless turn, render its prompt + assistant response with the student chat template and split on the longest common prefix to fill TurnTokens (masks/logprobs come from trace_to_samples). train_sink.process_rollout backfills when any turn lacks tokens, before building samples. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
drop_group's error_rollout_output calls omitted the required task_idx, so an off-policy cancel (on_new_version) raised TypeError. Use the group's task_idx (or -1 when the group is already gone), mirroring handle_completed_rollout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- envs.py: EnvClient now returns Trace[WireTask]; upgrade to this env's real Task subclass via self.trace_type.model_validate(wire.to_wire()). - dispatcher.py: drop the error_rollout_output helper — inline the synthetic error Trace at each call site using vf.Error's field names (type/message/traceback); the task-exception path carries a real traceback, cancels/empty-trajectory carry none. - Bump deps/vf-nano. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nical - Spawned env servers now route their output (logging + subprocess-runtime output) to <output_dir>/logs/envs/<name>.log via a _run_env_server wrapper that redirects stdout/stderr and sets up logging in the child. Previously the orchestrator-spawned server logged nowhere. - Debug config: batch_size 16->128, group_size 8->16, eval num_examples 8->128 (interval=1), matching configs/debug/training_modes/rl.toml. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The orchestrator already passes a train/eval-split log_dir (.../logs/envs/train, .../logs/envs/eval), so _spawn must drop the file directly under it (<log_dir>/<name>.log) rather than re-adding an envs/ subdir — which had buried the train/eval split under logs/envs/<kind>/envs/<name>.log. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Instead of the orchestrator sidecar-spawning each env server as an mp child, the
rl launcher now spawns one `env-server` process per env (train + eval), each on a
free port, with output to logs/envs/{kind}/{name}.log and a crash monitor — same
model as inference/trainer. It sets env.address in the orchestrator config so the
orchestrator attaches (its existing external path) instead of spawning. Envs that
already set address (user-managed external server) are left alone; the orchestrator's
mp sidecar stays as the fallback for running `orchestrator` directly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add RLConfig.env_server_base_port (default 5000); the i-th launcher-managed env binds base_port + i. Drops the get_free_port dependency in the launcher. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Train envs bind base_port + i; eval envs bind base_port + ENV_SERVER_KIND_STRIDE + i (stride 1000), so each kind has headroom for many envs without the blocks colliding (was a single running index — train and eval sat adjacent). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- env_server entrypoint: intercept vf-nano stdlib logging so the server's own logs (EnvServer up, request failures) land in logs/envs/<kind>/<name>.log — previously only loguru output was captured, swallowing them. - envs.py: close the address-handoff mp.Queue after use (no resource_tracker leaked-semaphore warning on the sidecar path). - configs/debug/nano/reverse_text.toml: drop the eval block, mirroring examples/reverse_text/rl.toml (train-only smoke; eval path validated separately). - bump deps/vf-nano (serve/types docstring trim). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…irectly The I/O boundary (save_rollouts + monitor sample tables) now dumps the typed vf.Trace itself (r.trace.model_dump(mode="json")) instead of a Trace+metadata merge — the on-disk rollout is just the trace. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vf-nano renamed its rollout-driver abstraction Agent -> Harness. Update the
integration: EnvConfig.agent -> harness (HarnessConfig/DefaultHarnessConfig);
env.run_rollout/run_group spawn forwards harness_config; the env-server entrypoint
passes harness_config/harness_timeout; debug config uses `harness = {...}`. Bump
deps/vf-nano to the renamed branch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"advantage" was too general for a component that also fixes loss routing
and the model role, so flatten the `algo` bundle into a discriminated union
keyed on `type`: `[orchestrator.algo] type = "grpo"` instead of
`[orchestrator.algo.advantage] type = ...`.
- AlgorithmConfig is the union; each *AlgorithmConfig variant carries `type`,
its params, and the shared sampling/teacher fields (BaseAlgorithmConfig).
- `algo.teacher` shorthand preserved (folds into `model` for opd/opsd,
`sampling.source` for sft); `algo.model` is opd/opsd's direct field.
- Deleted the unused `advantage = {}` env/top-level shorthand + fold validator.
- Runtime: Algorithm takes the algo config (self.config); build_algorithm
dispatches on algo.type. Migrated 12 TOMLs, docs, and unit tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump deps/verifiers 7bb546c2 -> b8fad841: PrimeIntellect-ai/verifiers#1753 shows per-reward/ metric running means under the eval progress bar. Eval CLI dashboard + format helper only - no training/wire surface, no prime-rl edits. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…debug logs) Bump deps/verifiers b8fad841 -> cd205eee: - PrimeIntellect-ai/verifiers#1751: type v1 rollout errors by boundary (RolloutError + Provider/Harness/Toolset/User/Sandbox/Taskset/Interception types, classified via boundary()). The trace Error record (type/message/traceback) and stop_condition values are unchanged, so prime-rl's consumed surface (vf.Error, errors/stop_condition, 'Cancelled'/'prompt_too_long') is intact. - PrimeIntellect-ai/verifiers#1754: INFO interception up/down + debug-log intercepted routes. Verified: reverse-text RL smoke (2 steps) trains clean (Error 0.0%, Training finished). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Use the canonical v1 Trace.capture_error(exc) for the dispatcher's caught-exception path instead of hand-building a vf.Error record + stop_condition - same result (type=exc class name, message, traceback, stop_condition='error') but routed through the framework's recorder, so it picks up the #1751 RolloutError boundary types when one escapes. The synthetic Cancelled/ EmptyTrajectory markers stay explicit vf.Error records (not exceptions; the sink/metrics branch on those exact type strings). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…RM fix) Bump deps/verifiers fedfa570 -> 2474b054, moving off the pre-merge fix/worker-sigterm-teardown branch commit onto the feat/nano-as-v1 tip: - PrimeIntellect-ai/verifiers#1755 (merged): swallow SIGTERM KeyboardInterrupt in spawned env-server workers. - PrimeIntellect-ai/verifiers#1756: SDKs own retries; drop framework per-call model/runtime retries (removes RetryingClient/RetryingRuntime/CallRetryConfig). RetryConfig + the rollout retry (RolloutRetryConfig) are kept. prime-rl imports none of the removed retry names and never references RetryConfig/.model/.runtime, so no edits. Verified: config validates + reverse-text RL smoke (2 steps) trains clean (Error 0.0%, Training finished). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…x channel retries) Bump deps/verifiers 2474b054 -> e002e80d: - PrimeIntellect-ai/verifiers#1758: keep the interception endpoint out of agent-inheritable env. - PrimeIntellect-ai/verifiers#1759: retry in-sandbox /state + /task channel fetches; name us in the prime expose error. Internal to harnesses/runtimes/mcp - no prime-rl serve/client/trace surface, no edits. Verified: reverse-text RL smoke (2 steps, default harness + subprocess runtime) trains clean (Error 0.0%, Training finished). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump deps/verifiers e002e80d -> 8c9f284b: PrimeIntellect-ai/verifiers#1761 makes ModalRuntime.read resolve relative paths and stops retrying the tail tool logs. Modal-runtime internal only - no prime-rl surface, no edits. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
057f0087 (renderers-v0.1.8.dev40-2) fails a fresh hatch-vcs editable build
("0.1.8.dev40 can't be bumped"), so only grandfathered venvs built. dev49 is an
exact tag → builds cleanly, unblocking fresh venvs (e.g. running from /shared).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The SlurmConfig default partition "cluster" doesn't exist on this cluster (only "all"), so generated sbatches failed to schedule. Set partition=all in the scaleswe + hendrycks-sanity sources. Add scaleswe's prime-sandbox cleanup as a pre_run_command (runs on the head node before launch) instead of editing the sbatch template. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Source had num_train_nodes=1/num_infer_nodes=1 (the unconfirmed 30B-A3B starting point); the run that reached step 36 used 2+2. A single train node OOMs the 35B-A3B trainer. Restore 2 train + 2 infer nodes (32 GPUs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch the hendrycks-sanity train + eval envs from subprocess to modal (offloads scoring to modal sandboxes), cap max_steps at 200 (was 5000), rename job to hendrycks-modal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…malformed-json tool-args, container-test fix) Brings in feat/nano-as-v1 #1762 (mark swebench_verified_v1 container-only), #1763 (feed malformed tool-call JSON back to the model instead of crashing the rollout), #1765 (pin swebench_verified_v1 task workdir to /testbed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tasks) Switch the scaleswe train harness rlm -> mini-swe-agent (prime sandbox), and re-add a SWE-Bench-Verified online eval: mini-swe-agent on the prime runtime, use_prime_registry=true, first 100 tasks (num_examples=100, group_size=1), interval 20, rollout timeout 3600s. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| port = config.env_server_base_port + kind_index * ENV_SERVER_KIND_STRIDE + i | ||
| env.address = f"tcp://127.0.0.1:{port}" | ||
| env_dict = { | ||
| k: v for k, v in env.model_dump(mode="json", exclude_none=True).items() if k in EnvConfig.model_fields |
There was a problem hiding this comment.
Missing EnvConfig import
High Severity
setup_env_servers filters env fields with EnvConfig.model_fields, but EnvConfig is never imported in this module, so a local uv run rl launch raises NameError when env servers are configured.
Reviewed by Cursor Bugbot for commit 5925104. Configure here.
| if self.renderer is None: | ||
| result["renderer"] = "None" | ||
| return result | ||
| serializes.""" |
There was a problem hiding this comment.
MITO renderer lost on save
Medium Severity
Removing the wrap serializer means orchestrator.renderer = None (MITO) no longer round-trips when subconfigs are written with exclude_none=True. The renderer key disappears from TOML and reload applies the default AutoRendererConfig(), switching MITO runs back to renderer-backed tokenization after resume or control-dir dumps.
Reviewed by Cursor Bugbot for commit 9cbbb53. Configure here.
| env.num_workers = self.num_workers | ||
| if "max_retries" not in env.model_fields_set: | ||
| env.max_retries = self.max_retries | ||
| # Resolve auto num_workers now that num_examples and group_size are set |
There was a problem hiding this comment.
Breaking config lacks changelog
Low Severity
This PR removes or renames orchestrator env worker settings (num_workers → pool), drops default train/eval env entries, and requires env-server configs to specify env explicitly, but CHANGELOG.md is not updated with migration notes for those breaking config surfaces.
Additional Locations (1)
Triggered by project rule: BugBot Instructions
Reviewed by Cursor Bugbot for commit 9cbbb53. Configure here.
|
|
||
| @property | ||
| def tool_response_len(self) -> int: | ||
| return sum(len(node.token_ids) for node in self._rollout.nodes if getattr(node.message, "role", None) == "tool") |
There was a problem hiding this comment.
Length penalty ignores harness metric
Medium Severity
Token length penalty now sums raw tool-message node tokens via RolloutView.tool_response_len, but the config still documents harness metrics like *_total_tool_response_tokens. Environments that only populate the deduped metric (not per-node tool spans) get zero tool cost and incorrect advantage shaping.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 9cbbb53. Configure here.
| "env": r.raw["timing"]["env"]["duration"], | ||
| "scoring": r.raw["timing"]["scoring"]["duration"], | ||
| "overhead": r.raw["timing"]["overhead"], | ||
| "total": r.timing.scoring.end - r.timing.start if r.timing.scoring.end else 0.0, |
There was a problem hiding this comment.
Rollout timing total zeroed
Low Severity
timing_df sets total to scoring.end - timing.start only when scoring.end is truthy; otherwise it records 0.0. Rollouts without a populated scoring end timestamp report zero wall time in dashboards despite nonzero setup or generation durations.
Reviewed by Cursor Bugbot for commit 9cbbb53. Configure here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 6 total unresolved issues (including 5 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d6f7a49. Configure here.
87c38c7 to
d33b3f4
Compare


Summary
Adds the trace-native env/orchestrator/training base layer on top of
feat/algorithm-abstraction, while intentionally preserving the existing algorithm runtime shape. This is the base branch for #2842.Current scope:
EnvConfiginherit the verifiers v1 env-server config surface while prime-rl keeps orchestration-only fields such as env name, address, ratio, and retry defaultsid+argsharnesses/tasksetsplugin packages in the root prime-rl environment; the slimprime-rl-configspackage depends only on verifiers config typesDeliberately out of scope here:
deps/primesubmodule pathStack
This is PR 1 of 2:
feat/algorithm-abstractionThe branch should stay focused on trace/v1 support. Algorithm API replacement belongs in #2842.
Validation
UV_CACHE_DIR=/tmp/uv-cache uv run pytest 'tests/unit/test_configs.py::test_load_configs[configs/rlm_swe/qwen35_4b.toml]' -q-> passUV_CACHE_DIR=/tmp/uv-cache uv run pytest 'tests/unit/test_configs.py::test_load_configs[examples/glm5_pd_disag/rl.toml]' -q-> passUV_CACHE_DIR=/tmp/uv-cache uv run pytest 'tests/unit/test_configs.py::test_load_configs[configs/general_agent/rl_qwen3_30b_a3b.toml]' -q-> passUV_CACHE_DIR=/tmp/uv-cache uv lock --check-> passruff checkandruff formatpassed on cleanup commits254 passed, 1 skippedon the base branch;69 passedfor the focused algorithm/batch subset)Note
High Risk
Large orchestrator/env-server boundary refactor touching rollout lifecycle, config migration, and launcher process topology; incorrect trace→sample conversion or legacy bridge behavior would silently break training data.
Overview
Replaces v0
RolloutOutput/ trajectory-step plumbing with verifiers v1Traceobjects end-to-end, while keeping the existing algorithm hook shape (RolloutView, scoring stages) for the follow-up registry PR.EnvConfignow subclassesverifiers.v1.EnvServerConfig(taskset, pool, timeouts) with prime-rl fields (address,ratio, retries) and a v0 legacy bridge viaid+args;num_workersmigrates topool. Training/eval sampling usestask_idxover server-reportednum_tasksinstead of loading datasets in the orchestrator. Env servers run throughserve_env/EnvClient; the RL launcher can spawn oneenv-serverper env on fixed ports (env_server_base_port) and writes per-env TOMLs before subconfigs.Rollouts unify as
Rollout(vf.Trace)with train/eval metadata; the dispatcher, sinks, filters, and metrics read trace fields (has_error,num_turns,completion_len,metrics) instead ofraw["trajectory"].trace_to_samplesbuildsTrainingSamples per trace branch (mask/logprobs on flat tokens), replacing tokenizer backfill, interleaving, and image offload in the train sink. Echo and OPSD scoring paths are updated for branch/node traces; echo custom filters are rejected on v1.Deps: root pins
verifiers>=0.1.15.dev357plusharnesses/tasksets; slimprime-rl-configsadds verifiers as a pydantic-only dep (CI no longer treatsverifiersas a forbidden heavy import). Logging/docs use flatterlogs/envs/{train,eval}/{name}.log. Trainer packing gains multimodal-aware truncation when sequences exceedseq_len.Reviewed by Cursor Bugbot for commit 6d0f4da. Bugbot is set up for automated code reviews on this repo. Configure here.