Skip to content

feat: consolidate eval cli - #3471

Merged
mikasenghaas merged 127 commits into
mainfrom
feat/evals-consolidation
Sep 16, 2026
Merged

mikasenghaas merged 127 commits into
mainfrom
feat/evals-consolidation

Conversation

@mikasenghaas

@mikasenghaas mikasenghaas commented Sep 2, 2026

Copy link
Copy Markdown
Member

Summary

Migrates the eval CLI from verifiers into the prime-rl ecosystem: uv run eval is prime-rl's entrypoint for evaluating any model on any environment, standalone or as the online evals of a training run.

Why:

  • consolidation of features: adaptive concurrency, the local dashboard, resume from the trace stream, live rollouts
  • easier code sharing between the CLI entrypoints (rl, sft, eval)
  • direct access to the pre-installed environments (prime-envs submodule)
  • one workspace for post-training: training and evals

Important:

  • the verifiers eval CLI stays for a transition period as the quick in-process path, renamed uv run vf-eval so prime-rl owns eval (feat: remove the eval CLI verifiers#2610 removes it later); env servers and their streamed traces (feat: stream served episodes turn by turn verifiers#2512, merged; the submodule points at verifiers main) are consumed here only
  • evals run through env servers, for scalability; the launcher (eval, sft, rl) owns them
  • --resume continues an interrupted run from its trace stream: landed episodes rejoin the epoch, only the owed rollouts run; num_examples/group_size may change, the model, sampling and env config may not
  • multi-env capable ([[source]] blocks); the single-env command shorthands are preserved
  • adaptive concurrency only against local deployments (it reads vLLM /metrics); against an API, concurrency is pinned via min_inflight = max_inflight (the default pins 128)
  • prime-rl's core dependencies are plain CPU; install them and the environments with uv sync --all-packages
  • Prime runs and traces integrate through the single monitor abstraction (--monitors.prime, --monitors.wandb, the file monitor)

Examples:

uv run eval gsm8k                                            # default model on Prime Inference
uv run eval gsm8k -n 32 -r 4 -m openai/gpt-5.6-luna           # 32 tasks × 4 rollouts, another model
uv run eval terminal-bench-2 --env.taskset.tasks '["fix-git"]' --env.agent.harness.id bash -n 1 -r 2
uv run eval @ configs/debug/eval/single-turn.toml            # gsm8k, null harness
uv run eval @ configs/debug/eval/multi-turn.toml             # 16 terminal-bench-2 tasks, bash harness
uv run eval @ configs/debug/eval/multi-env.toml              # terminal-bench-2 under bash and rlm side by side
uv run eval @ configs/debug/eval/aime2026.toml               # AIME 2026, avg@16
uv run eval @ configs/debug/eval/tb2.toml                    # Terminal-Bench 2, avg@4
uv run eval @ configs/debug/eval/resume.toml --run.name x    # then: --resume
uv run eval gsm8k --monitors.prime                           # stream the evaluation to the platform

Breaking

  • uv run evals is uv run eval (EvalsConfigEvalConfig); config shape: [eval.client][client], [eval.concurrency][concurrency], [[eval.source]][[source]], eval.num_examples/eval.group_size → top level. [online] is gone: online evals are spawned by sft.
  • eval has no [ckpt] block; --resume is a switch that reads the run's trace stream.
  • SFT online evals: no public entrypoint (uv run sft with an [eval] block spawns it); logs/attempt_N/evals.logeval.log, evals.jsoneval.json, W&B label online-eval; EvalsEvalConfigSFTOnlineEvalConfig.
  • ratio is accepted on training sources only, interval on online eval sources only; a standalone eval's [[source]] takes neither.
  • orchestrator.env_server_base_port and the eval entrypoint's env_server_base_port are removed: launcher-managed env servers bind an OS-assigned port and publish it (configs/attempt_N/resolved/envs/<split>/<name>.address); serve.address pins one. uv run env-server without serve.address binds an OS-assigned port (address_file publishes it).
  • configs/evals/swe.toml is removed (uv run eval swebench-verified --env.agent.harness.id bash).

Verification

  • Unit: uv run pytest tests/unit green (configs, orchestrator, eval, monitors incl. the live reader).
  • Debug configs: single-turn, multi-turn, multi-env and aime2026 run to completion against Prime Inference. Resume: single-env and multi-env runs interrupted with SIGTERM and resumed hold exactly examples × group_size unique episodes with the plan and epoch metrics over the full set; resuming with a larger group_size runs only the extra rollouts; resuming with another model is refused.
  • Core install (uv sync without extras) runs uv run eval; an SFT run with online evals starts one env server per source.
  • Live streaming, on the final code: gsm8k 16 single-turn episodes, terminal-bench-2 fix-git with bash (13-14 turns, the pending preview alternating with committed turns in the live files), kuhn-poker 4 two-seat episodes (8 traces), a 5-step reverse-text RL run (768 train episodes with token ids, logprobs and masks); no assembly mismatch.
  • Platform: --monitors.prime on a two-rollout fix-git eval opened the evaluation 9 s after launch, streamed both rollouts and closed it; the dashboard linked to it throughout.
  • Dashboard behaviour checked in headless Chromium: live rows and viewer follow, handover in place, metrics pane per env, filter menus, platform button states.

🤖 Generated with Claude Code


Note

Medium Risk
Breaking CLI and config renames affect existing scripts; env-server address discovery replaces fixed ports across rl, sft, and eval launch paths.

Overview
Replaces uv run evals with uv run eval as the standalone evaluation entrypoint, sharing the orchestrator eval pipeline (per-source env servers, adaptive or pinned concurrency, file/W&B/Prime monitors). Config is flattened ([[source]], [client], [concurrency] at top level); --resume continues from the trace stream instead of a [ckpt] cursor; SFT online eval is renamed and typed as SFTOnlineEvalConfig (no public [online] block).

Env server wiring changes: fixed env_server_base_port ranges are removed—launcher-managed servers bind OS-assigned loopback ports and publish addresses via address_file / configs/.../envs/<split>/<name>.address. Training ratio and online-eval interval are scoped to the right source types only.

Docs and examples add docs/eval.md, per-example eval.toml files, and configs/debug/eval/* smokes; configs/evals/swe.toml is deleted. CI adds a gsm8k eval integration test on VM runners.

Dashboard gains live rollout APIs and eval-centric metrics (plan-driven progress, platform links, eval run status), renames evals.logeval.log, and tightens static asset revalidation.

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

mikasenghaas and others added 2 commits September 2, 2026 20:07
Make `uv run evals` the one eval entrypoint: a flat config
([[source]], [client], [concurrency], num_examples, group_size), the
single-source shorthands `<taskset> --env.<field> <value> -n -r -c -m -o`,
a run directory under output_dir/run.name with clean/dry-run/resume,
cursor checkpoints on by default (keep_last prunes old cursors), a log
file for standalone runs, dashboard auto-start, and platform upload via
monitors.prime (one evaluation per source, uploaded when its epoch ends).

The SFT online-eval sidecar becomes its own `online-evals` entrypoint
with OnlineEvalsConfig (the sft [eval] block plus the run-level fields);
both share EvalRunner. The orchestrator EvalConfig splits into
EvalSourcesConfig + EvalConfig so the interval knobs stay training-only.

The shorthands rewrite argv into one JSON --source flag because
pydantic-config has no list-index paths (--source.0.x does not parse).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mikasenghaas and others added 6 commits September 2, 2026 20:30
`uv run eval` replaces `uv run evals` so verifiers users keep their
command; the sidecar is `online-eval`. Modules, configs (EvalConfig,
OnlineEvalConfig, SFTEvalConfig; the orchestrator's interval block is
ScheduledEvalConfig), log files and resolved-config names follow.

examples/eval/ gains TOMLs for the CLI usage verifiers used to document
(best-of-n, wiki-search, terminal-bench-2 with retries, rlm in docker
with sampling) and a README. A GPU integration test runs `uv run eval`
against a local vLLM server from configs/ci/integration.

The verifiers submodule points at the branch that removes its `eval`
console script, so the shared venv has one `eval`; re-pin to main once
PrimeIntellect-ai/verifiers#2512 lands.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The platform sample format and credentials move from verifiers into
prime_rl.monitors.platform, shared by the train and eval monitors (the
next step is one prime monitor for both). Credentials come from the
environment, then the prime CLI config.

`uv run eval` loses the `-o` short flag; `--output-dir` stays.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The orchestrator's inference metrics parse vLLM's /metrics with
prometheus_client, which only came in through vllm (the gpu extra). The
eval entrypoint imports that module, so a slim install (macOS, no gpu
extra) failed on it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`uv run eval gsm8k -n 2 -r 2 -c 2` works out of the box: the default
client is Prime Inference with deepseek/deepseek-v4-flash, and a
PRIME_API_KEY-keyed client falls back to the prime CLI config for the
key, as the verifiers client does.

The launcher prints the start line, log paths and dashboard URL, then
raises the console to results and warnings; logs/latest/eval.log keeps
the full log.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Main moved the prime monitor onto prime-runs (#3459) and renamed the
admin clients to AdminPlane (#3472). The eval monitor now opens one
prime-runs eval run per finished epoch instead of posting to
/evaluations by hand, which retires prime_rl.monitors.platform: the
SDK's projection and metrics modules own the sample format and the run
aggregates. The eval runner takes the AdminPlane rename and the
error-rate line from #3462. The swe example stays under examples/eval
(main had moved it to configs/evals for `uv run evals`), and the
verifiers submodule follows feat/remove-eval-cli's merge of main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread docs/configuration.md Outdated
Comment thread docs/training.md Outdated
Comment thread docs/training.md Outdated
Comment thread examples/eval/agentic-judge.toml Outdated
Comment thread examples/eval/best-of-n.toml Outdated
Comment thread packages/prime-rl-configs/src/prime_rl/configs/orchestrator.py
Comment thread skills/training/monitor-run/SKILL.md Outdated
Comment thread skills/training/start-run/SKILL.md Outdated
Comment thread pyproject.toml Outdated
Comment thread tests/integration/test_reverse_text_eval.py Outdated
mikasenghaas and others added 6 commits September 14, 2026 22:28
Default the eval model to deepseek/deepseek-v4.1-flash.

The SFT online-eval sidecar is no longer a console script: the sft
launcher spawns `python -m prime_rl.eval.online`, and its config is the
sft [eval] block itself (SFTOnlineEvalConfig, run-level fields filled by
the launcher). Both eval processes log to eval.log. The orchestrator's
[orchestrator.eval] block is RLOnlineEvalConfig.

Monitor configs split by what they log: TrainMonitorsConfig with
PrimeTrainMonitorConfig, EvalMonitorsConfig with PrimeEvalMonitorConfig.

examples/eval/ is gone; the basic examples ship an eval.toml next to
sft.toml/rl.toml, and Prime Inference smoke configs live in
configs/debug/eval/. New docs/eval.md and skills/eval; the training docs
and skills point at them. The eval integration test runs against Prime
Inference in the CPU workflow instead of a local vLLM server on the GPU
matrix. The unrelated docs/configuration.md hunk is reverted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Software components of a training run's evals keep the "online" prefix
(config class, module, process name, W&B label). Artifacts do not: the
resolved config sits next to eval.log as configs/attempt_N/resolved/eval.json.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
External inference APIs expose no vLLM /metrics, so the adaptive band
had nothing to follow and the startup probe refused to run without -c.
EvalConfig now pins min_inflight = max_inflight = 128; -c N repins, and
min_inflight < max_inflight in [concurrency] restores the adaptive band
against a vLLM server. Training-side evals keep the adaptive default.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
single-turn (gsm8k, null harness), multi-turn (terminal-bench-2 fix-git
in a sandbox), resume (low pinned concurrency to interrupt and --resume),
and multi-env (both), all against Prime Inference.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Like the rl/sft launchers, `uv run eval` prints the start block and
dashboard URL, stays quiet while the eval runs (only errors surface),
and ends with the success line and the dashboard URL again; results
live in the dashboard and eval.log.

The multi-turn debug config runs 16 fix-git rollouts against a local
dp=2 vLLM deployment with the adaptive band starting at 2 in flight.
Admin traffic targets the engine: the router's /v1/models fails with
two workers on one URL.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An eval ships metrics at epoch end and its first episode can take
minutes, so the run summary had no start and went stale while running.
The launch time is the start until a metrics row says otherwise, and
the attempt's log files count as liveness.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mikasenghaas mikasenghaas changed the title feat: consolidate evals and split online-evals feat: consolidate evals + streamed traces Sep 15, 2026
mikasenghaas and others added 13 commits September 15, 2026 01:16
Env servers now stream each trace turn by turn (verifiers 40b00b24c).
The dispatcher keeps every in-flight episode's streamed assembly and
publishes a live view twice a second through a new monitor hook: the
file monitor writes monitors/file/inflight.json, one row per live
trace with its phase, turns, tokens, cost, elapsed time and last
message. The eval progress line counts in-flight rollouts by phase
and the dashboard's metrics tab renders the rows as a live table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
One primitive for a rollout's progress: the env server's delta
(verifiers b963fb2b6). Env.run(on_delta) relays each delta to the
dispatcher, which forwards it to the monitors stamped with the
dispatch identity and keeps only a phase and turn count per live trace
for its progress line. The file monitor appends the deltas to
traces/live/<trace_id>.jsonl and deletes the file when the episode
finishes, is cancelled or dropped, so the directory is exactly the
in-flight set and finished traces stay in the stream.

The dashboard folds the same files: the traces tab lists live traces
above the finished ones with a phase badge and opens them in the
viewer as they grow. `python -m prime_rl.monitors.file.traces
<run_dir> [<trace_id>]` prints the live rows or one assembled trace.
The inflight.json snapshot is gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The newest node of a tool-using rollout is a tool result, which read as
an empty last message.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every entrypoint derived env-server addresses as tcp://127.0.0.1:5000
+ index, so two runs on one host raced for the same ports: the second
either failed to bind or answered the wrong run's tasks. A launcher-
managed env server now binds an OS-assigned port and writes it to
configs/attempt_N/resolved/envs/<split>/<name>.address; the env clients
wait for that file when a source has no explicit serve.address.
env_server_base_port is gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every launcher (rl, sft, eval) now prints its "Starting ..." line
first, then where the launch's configs are - the command as typed, the
launch TOML when one was given, and each component's resolved JSON -
then the Logs block and the dashboard URL. The eval console no longer
repeats the dashboard banner after the finish line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The env, model and provider were an arbitrary subset of the config;
the Configs block and the dashboard carry all of it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A rollout whose first trace has not streamed yet was invisible; the
dispatcher now publishes a pending placeholder at dispatch (removed by
the first delta or by retirement) and the live rows carry the dispatch
time, shown as a "dispatched" column. A live trace open in the viewer
keeps its scroll position across refreshes.

verifiers f321f896: replayed OpenRouter reasoning details dedup onto
their committed node, so a DeepSeek terminal-bench-2 rollout is one
branch again instead of one per turn.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The traces tab shows in-flight rollouts as tinted rows of the episode
table: dispatch time where finished episodes show arrival, a phase
badge, turn/token/branch counts that grow with the stream, reward n/a,
a status filter (all / in flight / finished). Reward moves to the last
column. The logs tab's components menu lists every env server under
"all envs" so one env's log can be toggled into its own pane. The eval
console lists env servers individually in its Configs and Logs blocks.

CI: the eval smoke is gsm8k with the bash harness in a subprocess
runtime, asserting reward and one branch per rollout; the reverse-text
eval test is gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The eval.log of an sft run's online evals and of a standalone eval are
the same artifact; the Logs block names it the same way.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The env servers' logs live under envs/<split>/; the formatter takes
that mapping instead of one parameter per split, and cuts long names in
the Configs block the way the Logs block does.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A run whose first episode has not landed has no stream, and the traces
tab's error path wiped the in-flight rows it had just drawn. The page
and its assets now send Cache-Control: no-cache so a reload revalidates
them, and the traces tab polls the live rows once a second.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The viewer's sidebar lists in-flight rollouts ahead of the finished
episodes with their phase and turn count; prev/next and the arrow keys
step across both. An open live trace pins its transcript to the newest
turn on every refresh unless the reader scrolled up.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mikasenghaas and others added 8 commits September 16, 2026 00:12
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The total cost is a summary tile; a per-episode cost plot below the
token strips repeated it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Deleting the file monitor's directory before the restored episodes were
back in the fresh stream let a resume that died during setup lose the
whole epoch. The directory is now renamed to file.previous, a later
resume reads both streams and keeps each episode once, and it goes only
after the epoch has the restored episodes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@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 db514fd. Configure here.

Comment thread src/prime_rl/eval/resume.py
mikasenghaas and others added 4 commits September 16, 2026 00:56
The file monitor directory a resume finds becomes
monitors/file.attempt_N and stays; the resumed attempt writes a fresh
one and a later resume reads every attempt's stream, each episode once.
Nothing is deleted and no release step is needed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A team's internal models are served only under its X-Prime-Team-ID.
Like the verifiers client, a Prime Inference client without one gets
the team from $PRIME_TEAM_ID or the prime CLI config.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
It reaches Prime Inference like the other integration tests, from the
runners that hold the team credentials. A model listing that fails
now raises with the status and body instead of a KeyError.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
samsja
samsja previously approved these changes Sep 16, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mikasenghaas mikasenghaas changed the title feat: migrate the eval CLI from verifiers into prime-rl feat: consolidate eval cli Sep 16, 2026

@hallerite hallerite left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for addressing the first round. I checked the updated stack: the eval runner now imports without torch, server ownership is fixed, and resume restores landed episodes and the full epoch count. I found two remaining resume correctness issues and a concurrency issue in the live dashboard cache, detailed inline.

Validation: 27 targeted prime-rl tests and 99 verifiers non-live tests passed, plus local reproductions of the issues below.

Comment thread src/prime_rl/eval/resume.py Outdated
Comment thread src/prime_rl/eval/resume.py
Comment thread src/prime_rl/monitors/file/traces/live.py Outdated
A resume validated against the latest attempt's resolved config, which
a rejected or dry attempt also writes, so repeating a rejected command
passed. Each attempt now stamps its config beside the episodes it
produces (monitors/file/eval.json) and a resume validates against that.

Owed rollouts of a task opened a fresh group next to the landed one, so
pass@k and the dashboard saw two groups for one task; they now join the
group the landed rollouts carry.

The dashboard's list and single-trace endpoints fold live files from one
cache on a thread pool; the fold now runs under a lock and callers get a
snapshot, so an appended delta folds exactly once.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
hallerite
hallerite previously approved these changes Sep 16, 2026

@hallerite hallerite left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, great work

mikasenghaas added a commit to PrimeIntellect-ai/verifiers that referenced this pull request Sep 16, 2026
## Summary

- Served episodes stream turn by turn over the env-serve wire
(`verifiers/v1/serve/delta.py`). `Trace.notify` fires at every phase
change and after every recorded turn; the worker's `DeltaStreamer` diffs
the run's live traces against what it already sent and ships the new
part (header once, appended nodes/calls/errors, semantic links, changed
scalars), so every byte crosses the wire once. A cursor advances only
after its delta is on the wire. `EnvClient.run` assembles the episode
from the deltas (`EpisodeAssembly`, which copies the nodes it keeps so a
later link never mutates a delta the caller holds), validates it once,
and takes `on_delta` so a caller can relay or persist the stream; the
client's receive loop survives a malformed frame.
- `pending` preview: the interception server hands each model request's
uncommitted tail (tool results, user turns) to `Trace.preview`, so a
watcher sees a tool result before the model has answered; the committed
turn replaces it and the finished record never carries it.
- The eval CLI is `uv run vf-eval` and runs in-process only. Serving an
env to many consumers is prime-rl's job (its `uv run eval` runs env
servers and consumes this stream), so the CLI keeps the quick local path
and never assembles deltas; its TUI footer and console log point at
prime-rl's `eval`. The env-server e2e fixture drives a worker pool
through `EnvClient` directly.

Based on `main`; the eval CLI stays for a transition period
(#2610 removes it later). prime-rl
PrimeIntellect-ai/prime-rl#3471 consumes the stream for its live traces.

## Breaking

- Env-serve wire: replies are 4 frames (`client_id, request_id, kind,
data`) with `kind` = `delta` | `reply`; `RunResponse` carries the
episode head and per-trace counts instead of the traces. A client and
server must be on the same side of this change.
- Every console script is prefixed: `vf-eval`, `vf-validate`,
`vf-debug`, `vf-replay`, `vf-init`, `vf-gepa`. Generic names collide
with the scripts of a workspace that installs verifiers as a dependency
(prime-rl's `eval`), and the winner is whichever package was installed
last. `vf-eval` has no `[serve]` / `--serve.*` / `--no-serve`: rollouts
always run in-process; use prime-rl's `uv run eval` for env-server runs.
Existing venvs: `uv sync --reinstall-package verifiers
--reinstall-package prime-rl` regenerates the scripts.
- `Env.run_slot` takes `on_trace`; `Trace` gains
`watch`/`notify`/`preview`/`clear_preview`.

## Verification

- `tests/v1/test_serve_delta.py`: turns, a link onto an earlier node, a
retried attempt and the final reward round-trip through `DeltaStreamer`
→ `EpisodeAssembly` → `WireEpisode`; a refused send is diffed again; the
pending preview streams, is replaced by the committed turn and never
reaches the record; assembling never mutates a delta; the field lists
cover every serialized `Trace` field.
- `uv run pytest tests/v1 -m "not e2e"` green apart from the
config-parse cases of tasksets not installed in the venv; ruff clean.
- End to end through prime-rl: gsm8k single turn, terminal-bench-2
fix-git with bash and with the rlm harness delegating to a sub-agent
(semantic links `subagent_call`/`subagent_return`), kuhn-poker two-seat
episodes, 5-step reverse-text RL runs with token ids and logprobs,
including router replay plus sampling replay on a MoE; no assembly
mismatch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- Macroscope's pull request summary starts here -->
<!-- Macroscope will only edit the content between these invisible
markers, and the markers themselves will not be visible in the GitHub
rendered markdown. -->
<!-- If you delete either of the start / end markers from your PR's
description, Macroscope will append its summary at the bottom of the
description. -->
> [!NOTE]
> ### Stream served episodes turn by turn with delta frames
> - Adds `DeltaStreamer` in
[delta.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2512/files#diff-106a565d15f6f5c86f7357854d512768b3f8a4775026a0eae32c69b828f5da31)
that watches trace changes during `run_slot` and sends incremental
deltas; the client `EpisodeAssembly` reconstructs a `WireEpisode` from
those deltas plus a final episode head and `TraceSummary` counts.
> - Changes the wire protocol to three-frame messages (request ID, kind,
data). Delta frames are relayed by the pool and client without closing
the request; reply frames close it. Updated in
[server.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2512/files#diff-64422f6a8749fcc024415b2756129bc98000e830e74b3b5245de50a555480a22),
[pool.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2512/files#diff-0c9c59578214be2580d340bc524321e352f48038207a68945637d00fc399600c),
and
[client.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2512/files#diff-ef98a21052536967b8f8723e36022f69e539f4e8b13794791e8254434ac4696e).
> - Adds a trace watcher/preview API in
[trace.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2512/files#diff-834820c3aa80d87ee1c1c57fb54c74f09c66ac727036d964e6005c8613c00b5e).
`Env.run_slot` gains an `on_trace` callback; `Rollout.open`/`close` and
`InterceptionServer` notify the trace on phase changes and pending turn
previews.
> - `PendingTurn.commit`/`commit_prompt` now clear the trace's pending
preview after committing.
> - Risk: `RunResponse` in
[types.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2512/files#diff-8e290c0dfc583c92b20ee9f20fa2ecd0df82459ad63f8e819e87aa856de97cf4)
removes the serialized `WireEpisode` field and replaces it with an
episode head and `TraceSummary` list — any out-of-tree consumer
expecting the full episode in the run response will break.
>
> <!-- Macroscope's changelog starts here -->
> #### Changes since #2512 opened
>
> - Renamed console script entry points in `pyproject.toml` and updated
all documentation and usage strings to use `vf-` prefixed command names
[02a6eff]
> - Refactored trace preview system from unkeyed to keyed architecture
[55500a3]
> - Updated all trace preview and clear operations to use keyed API with
PendingTurn instances as keys [55500a3]
> - Added abandon mechanism for cleaning up previews when turns fail or
are cancelled [55500a3]
> - Changed `verifiers.v1.trace.Trace` to store preview entries by
`id()` of the key object rather than by the key object itself [73d602d]
> - Updated `PRIME_RL_HINT` constant in the `hint.py` module [fc7e45b]
> <!-- Macroscope's changelog ends here -->
>
> <!-- Macroscope's review summary starts here -->
>
> <sup><a href="https://app.macroscope.com">Macroscope</a> summarized
fe771d8.</sup>
> <!-- Macroscope's review summary ends here -->
>
<!-- Macroscope's pull request summary ends here -->

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **High Risk**
> Breaking env-serve wire protocol and `RunResponse` shape require
matching client/server versions; CLI renames and removal of eval’s serve
path change how users run evaluations.
> 
> **Overview**
> This PR **streams env-server episodes incrementally** instead of
returning one fat `RunResponse`. Workers use **`DeltaStreamer`** to diff
live traces on `Trace.notify()` and send **`delta`** frames; clients
**`EpisodeAssembly`** rebuilds traces and validate once against a final
**head + `TraceSummary` counts**. The wire format is now **`[request_id,
kind, data]`** with `kind` = `delta` | `reply`; the pool relays deltas
without closing the request.
> 
> **`Trace`** gains **`watch` / `notify` / keyed `preview` /
`clear_preview`**; rollouts and the interception server fire
notifications (including **pending tool/user tails** before commit).
**`Env.run_slot`** adds **`on_trace`** so the server can subscribe
traces to the streamer.
> 
> **`vf-eval` is renamed and narrowed**: console scripts are **`vf-*`**
to avoid clashing with prime-rl’s **`eval`**, docs/config comments
follow suit, and **`vf-eval` always runs in-process**—**`[serve]` /
`--no-serve` are removed** from eval config—with dashboard/log hints to
use **prime-rl** for env-server scale and live dashboards.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
fc7e45b. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mikasenghaas
mikasenghaas merged commit c394c2e into main Sep 16, 2026
19 of 20 checks passed
@mikasenghaas
mikasenghaas deleted the feat/evals-consolidation branch September 16, 2026 18:04
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.

4 participants