Skip to content

feat: select taskset tasks per train and eval source - #3703

Merged
mikasenghaas merged 11 commits into
mainfrom
feat/filter-evals
Sep 28, 2026
Merged

mikasenghaas merged 11 commits into
mainfrom
feat/filter-evals

Conversation

@mikasenghaas

@mikasenghaas mikasenghaas commented Sep 27, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Give every train and eval source a select block (EnvConfig.select, a vf.SelectConfig): include/exclude by task idx, ids, keys or names, then shuffle, skip and limit. The orchestrator loads each source's tasks with vf.load_taskset(...).select(source.select). prime-rl no longer shuffles or slices tasksets itself, so it picks the same tasks as vf-eval for the same selection.
    [[orchestrator.train.source]]
    env.taskset.id = "gsm8k"
    select.exclude.idx = [":100"]   # hold out the first 100 tasks
    
    [[orchestrator.eval.source]]
    env.taskset.id = "gsm8k"
    select.include.idx = [":100"]
  • Every source group ([orchestrator.train], [orchestrator.eval], SFT [eval], the top level of uv run eval) takes a shared select block. The same before-validator that layers the group env block layers it under each source, field by field, and a field that the source sets wins. A source's include or exclude replaces the group's whole block. A source select that does not validate keeps its own block, so its error surfaces. For example, this evaluates 128 tasks of each taskset:
    [orchestrator.eval.select]
    limit = 128
  • Only uv run eval has the -n/-s short flags (select.limit/select.shuffle), through vf.SelectCLIConfig. The rl, orchestrator and sft CLIs have several select blocks, so they get no short flags.
  • The dashboard derives the expected episode count from select.limit, per source and for vf-shaped configs.
  • Migrate the configs, examples and docs from num_examples to select.limit. Group-level blocks use [... .select] tables.
  • Update the eval docs and skill: resume no longer checks the config (feat(eval): allow any config override on resume #3690), and a resume matches landed episodes to the new select by task.key.
  • Update the configs skill ([select], -s). Update the eval skill: the select shorthands, and common patterns (a random subset, tasks by position or name, dropping broken tasks, contiguous and random train/test splits via include/exclude and skip).
  • Pin deps/verifiers to its main, which merged feat: add taskset selection views and select config verifiers#2695 (Taskset.select, vf.SelectConfig). Raise the verifiers minimum to 0.3.2.dev152, its first release, in both pyproject.toml files, so the slim prime-rl-configs install gets it.

Companion PRs: PrimeIntellect-ai/verifiers#2695 (merged), PrimeIntellect-ai/prime-envs#852 (independent; compile_statements stops calling head()).

Breaking

  • EnvConfig.shuffle is removed. Use select.shuffle.
  • EvalSourceConfig.num_examples and the group-level num_examples are removed. Use select.limit on the source or on the eval block.
  • The shuffle seed changes from 42 to verifiers' default 0 (select.seed). Existing shuffle = true configs pick other tasks, so eval numbers before and after this change are not comparable.
  • Default task keys change for tasksets that never set idx (see verifiers#2695). An interrupted eval of such a taskset cannot resume across this change.

Verification

  • The slim-install CI check, run locally: the prime-rl-configs wheel installed outside the workspace resolves verifiers 0.3.2.dev152, and the workflow's import script passes.
  • uv run pytest tests/unit/test_configs.py tests/unit/eval tests/unit/orchestrator tests/unit/test_dashboard_timeline.py: 296 passed (2 vLLM-only tests deselected; vllm is not installed on the test box). test_load_configs loads every migrated TOML.
  • Real configs and CLI calls against a local mock OpenAI server (no inference spend). The mock counted the model calls, and the selected task idx matched an independent oracle:
    • uv run eval @ configs/debug/eval/single-turn.toml: tasks 0–7 × 2, 16 calls.
    • uv run eval @ configs/debug/eval/resume.toml: first run 16 calls. --resume --select.limit 10 restored 16 episodes and ran only tasks 8–9 (4 calls). --resume --select.include.idx 0:5 kept the landed episodes of tasks 0–4 and made no model calls.
    • uv run eval gsm8k -n 4 -s --select.exclude.idx '[0]': the oracle's 4 tasks, 4 calls.
    • A two-source TOML that splits gsm8k into [:50] and the rest under a group select default: both sources match the oracle, 5 calls.
  • Config checks: train and eval group select defaults reach every source, a source's own fields win, and the resolved OrchestratorConfig re-validates to the same values. uv run rl -h, orchestrator -h and sft -h list no -n/-s; uv run eval -h does.
  • uv run rl --dry-run on configs/debug/algo/grpo.toml, examples/basic/reverse-text/rl.toml and configs/debug/multi-env/rl.toml, and uv run sft --dry-run on examples/basic/reverse-text/sft.toml: group eval limits (128, 64, 32) resolve onto each source, a per-source select.limit = 256 is kept, and train sources default to all tasks.

🤖 Generated with Claude Code


Note

Medium Risk
Breaking config renames and verifiers-driven selection/shuffle/task-key semantics affect every train/eval run and eval resume comparability across the upgrade, though runtime logic is largely delegated to verifiers.

Overview
Replaces num_examples and per-source shuffle with verifiers' select block on every train/eval source (and group-level [select] / [orchestrator.eval.select] / [eval.select]). select.limit caps how many tasks run; include/exclude, shuffle, skip, and seed follow verifiers' ordering so prime-rl matches vf-eval for the same selection.

The orchestrator now loads tasks with vf.load_taskset(...).select(source.select) instead of client-side shuffle (seed 42) and islice. Group config merging layers shared select onto each source like env. uv run eval maps -n/-s to select.limit/select.shuffle; eval resume no longer validates config against the interrupted run—landed episodes are matched by task.key to the new select.

All example/debug/CI TOMLs, docs, and skills are migrated. verifiers minimum is raised to 0.3.2.dev152. The dashboard expects select.limit when estimating episode counts.

Breaking: rename configs from num_examples → select.limit, shuffle → select.shuffle; default shuffle seed and task keys may differ from pre-change runs (resume/comparability implications).

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

mikasenghaas and others added 4 commits September 27, 2026 19:31
Every train and eval source takes a `select` block (include/exclude
by idx, id, key or name, then shuffle, skip and limit), applied by
the taskset's own select(); prime-rl no longer shuffles or slices
tasksets itself. Eval blocks take a group-level `select` default that
each source inherits field by field; `uv run eval -n/-s` set it.

Bumps verifiers (taskset selection views) and prime-envs (take()).

Breaking: EnvConfig.shuffle and the eval `num_examples` fields are
gone (use `select.shuffle` / `select.limit`). The shuffle seed moves
from 42 to verifiers' 0, so shuffled selections pick other tasks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	deps/prime-envs
#	docs/eval.md
mikasenghaas and others added 5 commits September 27, 2026 21:39
Train and eval groups take a `select` default that each source
inherits field by field, layered in the same before-validator as the
group `env` block. Only `uv run eval` keeps -n/-s, through
vf.SelectCLIConfig, so nested select blocks no longer claim the short
flags on the rl, orchestrator and sft CLIs. Resume may now change any
`select` field, as landed episodes are matched by task key. Group
blocks use `[... .select]` tables in configs and docs, and the eval
skill documents common selection patterns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	src/prime_rl/eval/resume.py
#	tests/unit/eval/test_resume.py
A source whose `select` did not validate was silently replaced by the
group's `select`, so a typo like `limt` was dropped. The source now
keeps its block and reports its own error. The merge validates every
group block with vf.SelectCLIConfig, which drops the per-group type
lookup. The dashboard's fallback total reads `select.limit`, the
configs skill lists `[select]`, and the docs note that a source's
include/exclude replaces the group's. Bumps verifiers for slice steps
and key matching against the recorded task data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mikasenghaas added a commit to PrimeIntellect-ai/verifiers that referenced this pull request Sep 28, 2026
## Summary

- Add lazy, chainable selection views to `Taskset`: `include` and
`exclude` (by `idx`, `ids`, `keys` or `names`), `shuffle(seed)`,
`skip(n)` and `take(n)`. Each view is a shallow copy, so the base
taskset does not change.
  ```python

taskset.include(idx=["0:100"]).exclude(names=["broken"]).shuffle(seed=0).take(5)
  ```
- Add `SelectConfig` and `TaskMatchConfig`
(`verifiers/v1/configs/select.py`). `Taskset.select(config)` chains the
views in a fixed order: `include`, `exclude`, `shuffle`, `skip`,
`limit`. `idx` takes ints and Python slices `start:stop:step`, each part
optional (`"100:"`, `":50"`, `"::2"`, `"0:10,17"`); negative positions
are not supported. Within `include` or `exclude`, a task matches when
any list names it.
- Give the eval, debug, validate and GEPA configs a top-level `select`
block. The four entrypoints select only through `Taskset.select`, so the
copied "shuffle, then head" blocks are gone.
- Keep `SelectConfig` free of aliases, so that a config with several
`select` blocks (prime-rl's group defaults and sources) has no ambiguous
flags. `SelectCLIConfig` adds the short flags for the one `select` block
of an entrypoint: `-n` sets `select.limit` and `-s` sets
`select.shuffle`.
- Make `idx` a framework field: iteration sets `TaskData.idx` to the
task's position in `load()` and overwrites any value that the taskset
sets. Iteration also applies the config `system_prompt` before the
views, so a `keys` match compares the keys that traces record. The
built-in tasksets, the example environments, the `vf-init` template and
the docs no longer pass `idx`.
- Add an optional durable `TaskData.id`.
- Add `Taskset.bounded`. `take` bounds a view, and so does an `include`
that names only closed `idx` ranges. `shuffle`, an `include` by `ids`,
`keys` or `names` (which could wait forever for a match), and an
`exclude` of an open `idx` range (which drops every later task) raise on
a view that is not bounded. `select` applies `limit` after the shuffle,
so there only closed `include.idx` ranges can bound it; the error says
so. An `idx`-only `include` stops reading `load()` after its last
position.
- Log a warning when an `include` or `exclude` entry matches no task
(usually a typo).
- Document the views and the `select` block in
`docs/v1/tasksets.md#selecting-tasks`.

Companion PRs:
- prime-rl: PrimeIntellect-ai/prime-rl#3703 (consumes `select` per
train/eval source)
- prime-envs: PrimeIntellect-ai/prime-envs#852 (one script stops calling
`head()`)

## Breaking

- `EvalConfig`, `DebugConfig` and `ValidateConfig` drop `num_tasks` and
`shuffle`. Use `select.limit` and `select.shuffle` (`-n` and `-s` still
work). In TOML, change a top-level `num_tasks = 5` to `select.limit =
5`.
- `GEPAConfig` drops `shuffle`, and GEPA no longer shuffles by default.
Pass `-s` to keep the old train/val split behaviour.
- `Taskset.head(n)` becomes `take(n)`. `Taskset.view` and the `SEED`
constant are removed.
- Tasksets that never set `idx` get new default task keys, because `idx`
is part of the content hash. An interrupted eval of such a taskset
cannot resume across this change.

## Verification

- `uv run pytest tests -m "not e2e"`: 113 passed. `ruff check` and `ruff
format --check` pass. `tests/v1/test_taskset.py` has one test per
behaviour: `idx` assignment, each view, lazy reads, the bounds that
allow `shuffle`, `idx` parsing, `keys` matches under a `system_prompt`
override, the `SelectConfig` defaults and `SelectCLIConfig` aliases, and
the fixed order of `select()`.
- `vf-validate` on a dummy 20-task taskset and a dummy infinite taskset:
27 CLI combinations of `--select.*`, `-n`, `-s` and a TOML `[select]`
block matched an independent oracle. The combinations included every
`idx` form, the union inside `include`, `exclude.idx`, and the
infinite-taskset errors.
- Real configs against a local mock OpenAI server (no inference spend).
The mock counted the model calls:
- `vf-eval @ configs/gsm8k.toml`: tasks 0–4 × 3 rollouts, 15 calls. With
`-s`: the oracle's shuffled 5 of 1319.
- With `--select.include.idx 100:103 --select.exclude.idx 101`: tasks
100 and 102, 6 calls.
  - `vf-eval @ configs/wordle.toml` (infinite): tasks 0–4.
- `vf-eval @ configs/alphabet_sort.toml -s`: refused with "cannot
shuffle", 0 calls. With `--select.include.idx 0:4 -s -n 2`: runs.
- `vf-validate gsm8k …` and `vf-debug reverse-text -n 3 -s`: task order
matches the oracle.
- `vf-eval @ configs/<each>.toml --dry-run` for all 12 configs: each
resolves to its intended `select.limit`.

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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Breaking config and task-key semantics affect eval resume and all
CLIs; selection logic on infinite tasksets is easy to misconfigure but
is guarded with explicit errors.
> 
> **Overview**
> Introduces a unified **`[select]`** block (and `--select.*` / `-n` /
`-s`) for choosing which tasks run, replacing top-level **`num_tasks`**
and **`shuffle`** on eval, debug, validate, and GEPA.
**`Taskset.select(SelectConfig)`** applies lazy views in fixed order:
**`include` / `exclude`** (by `idx` slices, `ids`, `keys`, `names`),
**`shuffle`**, **`skip`**, **`limit`** (`take` in Python; **`head` is
removed**).
> 
> **`TaskData.idx`** is now set from the **`load()` stream position**
during iteration (overwriting author-set values); built-in and example
tasksets drop manual `idx`, and **`TaskData.id`** is added for durable
source IDs. **`Taskset.bounded`** gates shuffle and identity-based
filters on infinite tasksets; unmatched include/exclude entries log
warnings.
> 
> Sample **`configs/*.toml`** move limits to **`[select].limit`**. Docs
describe selection and updated task identity. **GEPA** no longer
shuffles by default—use **`select.shuffle`** (`-s`) for the old split
behavior. Task keys may change for tasksets that relied on embedded
`idx` in the hash, affecting resume across this upgrade.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
c305bde. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- 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]
> ### Add lazy taskset selection views and `select` config replacing
`num_tasks`
> - Iterating a taskset now assigns `idx` automatically, so all
environment loaders drop manually supplied `TaskData.idx`; `TaskData`
gains an optional durable `id` field and `Task` gains a `with_data` copy
helper
([taskset.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2695/files#diff-7e5e561017828ff64ab186fa98a8e394675de1971f358af769aa825f7c7997c9),
[task.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2695/files#diff-a1b4a5d6915beae41831864087ea497ed41f0b67b9091ad1ee614834cbfd9399)).
> - `Taskset` gains lazy `include`, `exclude`, `shuffle`, `skip`, and
`take` views (replacing `head`), plus a `select` view that applies
include/exclude/shuffle/skip/limit in a fixed order. Matching supports
idx ranges, `id`, `key`, and `name`, warns on unmatched selectors, and
rejects unbounded shuffles of infinite streams.
> - New `SelectConfig`/`SelectCLIConfig` models in
[select.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2695/files#diff-c2b7d88c6cfd8129800ee79349fa672c6c19af8d879cbd4a86396435fb5dd9ff)
replace top-level `num_tasks` and `shuffle` in `EvalConfig`,
`DebugConfig`, `ValidateConfig`, and `GEPAConfig`; CLI aliases map `-n`
to `select.limit` and `-s` to `select.shuffle`. Exported via
[__init__.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2695/files#diff-731a849803c53d001c41b5e0fb1bfcb392eaae7eb4fa4b3ace382768dc40066a).
> - Runners for eval, validate, debug, and GEPA now apply
`config.select` and require the resulting view to be bounded; example
TOML configs and docs updated.
> - Behavioral Change: config files using top-level
`num_tasks`/`shuffle` no longer parse — migrate to `[select]` with
`limit`/`shuffle`. `head()` on tasksets is replaced by `take()`; check
[configs/](https://github.com/PrimeIntellect-ai/verifiers/pull/2695/files#diff-175ab01246347ee3cb3ad67cd8f359e5bdd765f7751c692026b7e782a3fa53ef)
and the GEPA runner's shuffle default (now off unless `select.shuffle`
is set).
>
> <!-- Macroscope's review summary starts here -->
>
> <sup><a href="https://app.macroscope.com">Macroscope</a> summarized
c305bde.</sup>
> <!-- Macroscope's review summary ends here -->
>
<!-- Macroscope's pull request summary ends here -->

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
mikasenghaas and others added 2 commits September 28, 2026 20:11
The slim prime-rl-configs install resolves verifiers from PyPI, and
0.3.2.dev152 is the first release with vf.SelectConfig (verifiers
#2695). Pins deps/verifiers to its main, which merged #2695.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mikasenghaas
mikasenghaas marked this pull request as ready for review September 28, 2026 20:18
@mikasenghaas
mikasenghaas merged commit 2662c9a into main Sep 28, 2026
22 of 24 checks passed
@mikasenghaas
mikasenghaas deleted the feat/filter-evals branch September 28, 2026 22:39
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.

2 participants