feat(orchestrator)!: make the episode the unit the orchestrator passes around - #3206
Draft
mikasenghaas wants to merge 7 commits into
Draft
feat(orchestrator)!: make the episode the unit the orchestrator passes around#3206mikasenghaas wants to merge 7 commits into
mikasenghaas wants to merge 7 commits into
Conversation
…around An episode that produced nothing — an off-policy cancel, a task that raised before reaching the env — was reported as a fabricated Rollout carrying a fake error. That trace has no real agent, so it took AgentInfo's default name and landed under a phantom agent subtree once metrics were keyed by agent. verifiers already has the shape: an episode with no traces and the reason on errors. The dispatcher records the cancellation on its own counters and emits that, so no stand-in exists. The env's episode rides through instead of being unwrapped into loose traces, which lets metrics read vf.Episode's own by_agent and token sums rather than regrouping, algorithms score episodes (hierarchical GRPO keys its solver baselines off episode.id), the sample monitors take episodes and log every branch of every agent, and traces.jsonl is written by verifiers' own writer — one episode per line, the format its read_episodes expects. Depends on PrimeIntellect-ai/verifiers#2252 for the episode's own fields (env.name, the run's metadata and policy span) and per-node advantages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
envs.py built a plain vf.WireEpisode while stamp() writes group_id, which only prime-rl's Episode has — so every emit raised inside the dispatcher's completion path and the inflight slots never cleared. The run hung with no error surfaced. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The eval summary still handed the monitor a trace container and read policy_version off traces, and the ship path still recomputed staleness onto them — all fields the episode now owns. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: upload native episodes from PrimeMonitor * fix: pass episodes to sample monitors * fix: keep sample serialization best effort
This reverts commit 9a31766.
This was referenced Aug 10, 2026
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
Follow-up to #3165. The orchestrator passes episodes end to end — the env's own
vf.Episode,extended only with what prime-rl genuinely adds — instead of flattening them into loose traces at
the boundary and rebuilding the grouping downstream.
Depends on PrimeIntellect-ai/verifiers#2252;
deps/verifiersis pinned to that branch and needs are-pin to
mainonce it merges.An episode that produced nothing needs no stand-in trace
A cancellation, or a task that raised before reaching the env, was reported as a fabricated
Rolloutcarrying a fake error. That trace has no real agent, so it tookAgentInfo's defaultname, and once #3165 keyed metrics by agent those failures landed under a phantom
agentsubtree —in a proposer-solver env, cancellations inflated a seat nobody ran. (Reported by bugbot on #3165.)
verifiers already has the shape:
run_episoderecords the reason onepisode.errorsand returnsthe episode with
okfalse. The dispatcher records the cancellation on its own counters and emitsthat same shape, so no stand-in exists and one vocabulary covers every cause.
The episode carries the dispatch
Everything else has a place on the episode already: the env it ran (
env.name) and the run itbelongs to (
run, whose metadata says whether the run trains on it or measures itself with it, andcarries the step and the policy versions generation spanned). So
kind,policy_version,off_policy_stepsandeval_stepleave the trace — they describe the episode, which is the thingthat was dispatched.
Staleness is derived, not stored. It used to be written twice: the dispatcher counted weight
updates per in-flight episode, then the main loop discarded that and recomputed
(step - 1) - policy_versionat ship. Now the dispatcher records the span generation covered andevery reading derives from it —
TrainMetadata.off_policy_stepsfor what trains,EvalMetadata.off_policy_steps(the drift) for what only measures.Algorithms score episodes
score_group/finalize_grouptake the group's episodes, so an algorithm can compare within anepisode as well as across them —
hierarchical_grpokeys its solver baselines offepisode.idinstead of a foreign key copied onto every trace.
Credit moves onto
MessageNode:Rollout.advantagesbecomes a derived read,assign_advantageswrites each node's trainable tokens, and
stamp_advantagescopiesbranch.advantagesonto thesample built from that branch rather than slicing one flat stream by offset. Unassigned stays
distinct from assigned-zero all the way to the trainer.
The sample monitors take episodes
log_samples/log_eval_samplestake episodes across all five implementations, and the W&B tablegains
agentandbranch_idxcolumns — one row per branch, so a multi-agent episode reads as itsseats rather than as one blurred cell.
traces.jsonlis written by verifiersOne episode per line, through vf's own
write_episode, so a prime-rl record reads exactly like oneits
read_episodesproduces. An episode that produced no traces is written too — itserrorsarethe record of why nothing came back.
Breaking
Rolloutno longer carrieskind,policy_version,off_policy_steps,eval_steporepisode_id; read them off the episode (run.metadata.type,run.metadata.step,run.metadata.policy,run.metadata.off_policy_steps,env.name).Rollout.advantagesis read-only (derived from the nodes) andassign_advantagestakes a scalaronly — the full-length per-token list is gone. No shipped algorithm used it.
Algorithm.score_group/finalize_grouptakelist[Episode], notlist[Rollout].Monitor.log_samples/log_eval_samplestake episodes, not traces.InflightRollout→InflightEpisode(rollout_count→episodes_owed);RolloutDispatchertakes a
run_id.traces.jsonlrows are episodes, not traces. verifiers'read_episodesreads both.{scope}/{subset}/<agent>/has_error/meanno longer counts cancellations or task failures — anepisode nobody ran belongs to no seat. Those report under
dispatcher/{cancelled,errored}/{train,eval}.Verification
uv run pytest tests/unit -m "not gpu"— 507 passed (CI's own selection).ruff check/ruff format --checkat the pinned 0.13.0.A 6-step reverse-text run with eval at steps 2/4/6, no errors. The records:
That train episode spans
v2 → v4: it outlived two weight updates mid-generation, which thestored counter could not express.
Follow-ups
apply_filtersstops at the first hit, so a rollout flagged as gibberish is never measured forrepetition — a monitoring rate that depends on filter order. Left alone here on purpose.
Rolloutstill carriesenv_name/group_idfor the consumers that work in flat trace lists(the sink's pending batch, per-agent metric bucketing).
building this reached a live run through a green suite.
🤖 Generated with Claude Code