Repository navigation
feat: add taskset selection views and select config - #2695
Merged
Merged
Conversation
Tasksets gain lazy, chainable views: include and exclude (by idx, id, key or name), shuffle, skip and take. select(SelectConfig) chains them in a fixed order and is the one selection path for the eval, debug, validate and GEPA entrypoints, which now take a top-level `select` block (`-n` sets select.limit, `-s` sets select.shuffle). Iteration now sets TaskData.idx to the task's position in load(), overwriting any value the taskset sets, so the built-in tasksets and examples stop passing idx. TaskData gains an optional durable `id`. Breaking: the eval, debug and validate configs drop `num_tasks` and `shuffle` (use `select.limit` / `select.shuffle`), GEPA drops `shuffle` and no longer shuffles by default, and Taskset.head() is replaced by take(). Tasksets that never set idx get new default task keys, as idx is part of the content hash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Sep 27, 2026
One test per behaviour: idx assignment, each view, lazy reads, the bounds that allow shuffle, idx parsing, the select config defaults and aliases, and the fixed order that select() applies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the short flags on SelectConfig itself, every nested select block (a group default, each source) claimed -n and -s, so a CLI with several of them had ambiguous flags. SelectConfig is now alias-free, and SelectCLIConfig adds -n/-s for the one select block of an entrypoint: the eval, debug, validate and GEPA configs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mikasenghaas
commented
Sep 27, 2026
`idx` entries now take the full `start:stop:step` slice syntax (no negative positions, which would need the stream's length). The configs use `[select]` tables, and the docs say idx is set automatically. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Iteration applied the config system prompt after the views, so an include/exclude by key compared keys without the prompt while traces record them with it; both now apply before the views. A zero step spelled "00" passed validation and divided by zero. idx entries parse into plain ranges, and the shuffle error names the bound that works through select. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mikasenghaas
marked this pull request as ready for review
September 27, 2026 22:36
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d664149. Configure here.
Contributor
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR adds a framework-wide task-selection capability and changes the eval/debug/validate/GEPA execution paths, task identity semantics, and public configuration APIs. The breaking migration and altered selection behavior have a broad enough runtime impact to warrant human review. You can add or adjust custom eligibility rules. Learn more. |
mikasenghaas
added a commit
to PrimeIntellect-ai/prime-envs
that referenced
this pull request
Sep 28, 2026
## Summary - `lean_common/scripts/compile_statements.py`: take the first `limit` tasks with `itertools.islice` instead of `Taskset.head(limit)`. PrimeIntellect-ai/verifiers#2695 removes `head()` (it becomes `take()`). `islice` works with verifiers both before and after that change, so this PR can merge on its own. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Single-line behavioral change in a dev script; slicing semantics should match the old head/limit behavior for taskset iteration. > > **Overview** > Updates **`compile_statements.py`** so `--limit` takes the first N tasks via **`itertools.islice(taskset, limit or None)`** instead of **`taskset.head(limit)`** (or iterating the full taskset when no limit). > > This keeps the compile script working when upstream **verifiers** drops **`head()`** in favor of **`take()`**, without requiring a coordinated dependency bump. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ad833f2. 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] > ### Replace `taskset.head()` with `itertools.islice` in `statements` > The `statements` function in [compile_statements.py](https://github.com/PrimeIntellect-ai/prime-envs/pull/852/files#diff-99d68b7a3d3eedfe5da62e6d44b91ea9a797b6153420172eec83411fe947372a) now slices the taskset with `itertools.islice(taskset, limit or None)`. A positive limit takes at most that many tasks; a falsy limit takes the whole iterable. Risk: a negative `limit` now raises `ValueError` from `islice` instead of being handled by `head()`. > > <!-- Macroscope's review summary starts here --> > > <sup><a href="https://app.macroscope.com">Macroscope</a> summarized ad833f2.</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
commented
Sep 28, 2026
On an infinite taskset, include by ids, keys or names followed by take(n) read forever when fewer than n tasks matched (e.g. a typo), so include now raises on an unbounded view for identity matches. An idx slice like ":0" passed validation, selected nothing and still built task 0; an omitted start now counts as 0 in the empty-range check. The docs use the long flag forms and name the shorthands in comments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On an infinite taskset, exclude(idx="5:") drops every task past position 5, so a later take(n) waited forever. exclude now raises on an unbounded view when an idx slice covers the whole tail; a slice with a step above 1 still leaves tasks and passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hallerite
approved these changes
Sep 28, 2026
mikasenghaas
added a commit
to PrimeIntellect-ai/prime-rl
that referenced
this pull request
Sep 28, 2026
## 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. ```toml [[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: ```toml [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 (#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 PrimeIntellect-ai/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](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!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). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f30798a. 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 Opus 5.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
Taskset:includeandexclude(byidx,ids,keysornames),shuffle(seed),skip(n)andtake(n). Each view is a shallow copy, so the base taskset does not change.SelectConfigandTaskMatchConfig(verifiers/v1/configs/select.py).Taskset.select(config)chains the views in a fixed order:include,exclude,shuffle,skip,limit.idxtakes ints and Python slicesstart:stop:step, each part optional ("100:",":50","::2","0:10,17"); negative positions are not supported. Withinincludeorexclude, a task matches when any list names it.selectblock. The four entrypoints select only throughTaskset.select, so the copied "shuffle, then head" blocks are gone.SelectConfigfree of aliases, so that a config with severalselectblocks (prime-rl's group defaults and sources) has no ambiguous flags.SelectCLIConfigadds the short flags for the oneselectblock of an entrypoint:-nsetsselect.limitand-ssetsselect.shuffle.idxa framework field: iteration setsTaskData.idxto the task's position inload()and overwrites any value that the taskset sets. Iteration also applies the configsystem_promptbefore the views, so akeysmatch compares the keys that traces record. The built-in tasksets, the example environments, thevf-inittemplate and the docs no longer passidx.TaskData.id.Taskset.bounded.takebounds a view, and so does anincludethat names only closedidxranges.shuffle, anincludebyids,keysornames(which could wait forever for a match), and anexcludeof an openidxrange (which drops every later task) raise on a view that is not bounded.selectapplieslimitafter the shuffle, so there only closedinclude.idxranges can bound it; the error says so. Anidx-onlyincludestops readingload()after its last position.includeorexcludeentry matches no task (usually a typo).selectblock indocs/v1/tasksets.md#selecting-tasks.Companion PRs:
selectper train/eval source)head())Breaking
EvalConfig,DebugConfigandValidateConfigdropnum_tasksandshuffle. Useselect.limitandselect.shuffle(-nand-sstill work). In TOML, change a top-levelnum_tasks = 5toselect.limit = 5.GEPAConfigdropsshuffle, and GEPA no longer shuffles by default. Pass-sto keep the old train/val split behaviour.Taskset.head(n)becomestake(n).Taskset.viewand theSEEDconstant are removed.idxget new default task keys, becauseidxis 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 checkandruff format --checkpass.tests/v1/test_taskset.pyhas one test per behaviour:idxassignment, each view, lazy reads, the bounds that allowshuffle,idxparsing,keysmatches under asystem_promptoverride, theSelectConfigdefaults andSelectCLIConfigaliases, and the fixed order ofselect().vf-validateon a dummy 20-task taskset and a dummy infinite taskset: 27 CLI combinations of--select.*,-n,-sand a TOML[select]block matched an independent oracle. The combinations included everyidxform, the union insideinclude,exclude.idx, and the infinite-taskset errors.vf-eval @ configs/gsm8k.toml: tasks 0–4 × 3 rollouts, 15 calls. With-s: the oracle's shuffled 5 of 1319.--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 …andvf-debug reverse-text -n 3 -s: task order matches the oracle.vf-eval @ configs/<each>.toml --dry-runfor all 12 configs: each resolves to its intendedselect.limit.🤖 Generated with Claude Code
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-levelnum_tasksandshuffleon eval, debug, validate, and GEPA.Taskset.select(SelectConfig)applies lazy views in fixed order:include/exclude(byidxslices,ids,keys,names),shuffle,skip,limit(takein Python;headis removed).TaskData.idxis now set from theload()stream position during iteration (overwriting author-set values); built-in and example tasksets drop manualidx, andTaskData.idis added for durable source IDs.Taskset.boundedgates shuffle and identity-based filters on infinite tasksets; unmatched include/exclude entries log warnings.Sample
configs/*.tomlmove limits to[select].limit. Docs describe selection and updated task identity. GEPA no longer shuffles by default—useselect.shuffle(-s) for the old split behavior. Task keys may change for tasksets that relied on embeddedidxin the hash, affecting resume across this upgrade.Reviewed by Cursor Bugbot for commit c305bde. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add lazy taskset selection views and
selectconfig replacingnum_tasksidxautomatically, so all environment loaders drop manually suppliedTaskData.idx;TaskDatagains an optional durableidfield andTaskgains awith_datacopy helper (taskset.py, task.py).Tasksetgains lazyinclude,exclude,shuffle,skip, andtakeviews (replacinghead), plus aselectview that applies include/exclude/shuffle/skip/limit in a fixed order. Matching supports idx ranges,id,key, andname, warns on unmatched selectors, and rejects unbounded shuffles of infinite streams.SelectConfig/SelectCLIConfigmodels in select.py replace top-levelnum_tasksandshuffleinEvalConfig,DebugConfig,ValidateConfig, andGEPAConfig; CLI aliases map-ntoselect.limitand-stoselect.shuffle. Exported via init.py.config.selectand require the resulting view to be bounded; example TOML configs and docs updated.num_tasks/shuffleno longer parse — migrate to[select]withlimit/shuffle.head()on tasksets is replaced bytake(); check configs/ and the GEPA runner's shuffle default (now off unlessselect.shuffleis set).Macroscope summarized c305bde.