Skip to main content
Your eval set has 20 Rust tasks and 30 Python tasks. Every Rust task needs an environment with cargo and dependencies installed, which takes five minutes to set up. Installing it for every task is too slow; chaining all 50 tasks with sandboxReuse is also too slow, since the Python tasks have no reason to wait for the Rust tasks. Eval groups solve this. Use defineEvalGroup() to put the 20 Rust tasks into one group: the group shares one Sandbox and runs its evals in ID order, while the Python tasks and other groups run in parallel as usual. An eval group only schedules the Attempts that really need to run this time. Evals that reuse last results do not enter the group Sandbox; the rules for --rerun, attempts, and early exit are the same as without groups.

Choose the right kind of reuse first

The same Experiment cannot both select eval groups and declare sandboxReuse: true. Both decide which evals share a Sandbox; when both appear, the run reports eval-group-sandbox-reuse-conflict before creating any Sandbox.

Step 1: Put the group file next to its members

Create a directory under evals/ and put the members and eval-group.ts in it. The directory’s path is the group ID:
NiceEval only recognizes the file name evals/**/eval-group.ts. Naming it *.eval-group.ts means it is not discovered; putting it at evals/eval-group.ts does not work either, because it has no enclosing directory to serve as the group ID.

Step 2: List the members in the group file

Each member default-exports the return value of defineEval() or defineScoreEval() as usual. The group file imports these return values and lists them in evals:
When writing evals:
  • It cannot be empty, and only accepts imported return values. Eval IDs, directory prefixes, glob, tag, and selector are not allowed.
  • NiceEval does not collect files from the same directory automatically. Adding or removing members always shows up in the diff of eval-group.ts.
  • Members must be either all defineEval() or all defineScoreEval(). When they are mixed, the run reports an error before planning Sandboxes and lists the eval IDs of both kinds, so you can split them into two groups.
  • An eval belongs to at most one group, and cannot be listed twice in the same group.
Array order does not decide execution order. Within a group, evals always run sorted by eval ID, and moving array positions does not change scheduling. When you need a dependency such as “build first, then query”, write both steps in the same eval. Ordering members by business sequence is not supported yet. onUnavailable is required. It decides what happens when the group Sandbox cannot be created, reset, or prepared:
  • "stop-group": the rest of this group’s evals are no longer dispatched.
  • "replace-sandbox": discard the current Sandbox and try creating a new one for the next Attempt.
Without it, loading eval-group.ts reports an error. Whether to spend more money rebuilding or to stop after a failure is your decision; NiceEval does not guess for you. Which evals run is still decided by the Experiment and the command line. When only workflow/02-query is selected, workflow/01-index is not pulled into this run automatically.

Step 3: Have the Experiment provide the Sandbox

In most projects, the Experiment chooses the Provider and template, and the eval group only decides “which evals share a Sandbox”:
Do not write sandboxReuse: true here. maxConcurrency: 4 is the cap for the whole Experiment and does not make the same group run four at once; the remaining concurrency capacity goes to other groups and ungrouped evals. Eval groups only work with Agents that run in a Sandbox and Providers that support reuse. An Agent without a Sandbox cannot run eval groups.

Step 4: Confirm the grouping before running

First look at the plan with --dry; it creates no Sandbox and calls no model:
Confirm that both member evals appear in the plan, then check whether they belong to the same group. In the machine-readable plan, each item carries evalGroupId:
Both evals’ evalGroupId should be workflow. When the field is missing, first check that the group file’s name and location are evals/workflow/eval-group.ts. After confirming, run:
Within the group, evals queue by eval ID. When attempts is greater than 1, the Attempts of one eval run back to back before the next eval’s turn. Positions that do not need to run because of early exit or result reuse are skipped directly. This is only a stable queueing rule; it cannot be used to pass data between evals. After the run, view results in the terminal with pnpm exec niceeval show, or in the browser with pnpm exec niceeval view.

Put shared preparation on the eval group

Sandbox preparation can be written in three places: the Experiment, the eval group, and the eval. Only one of the three can choose template. A common split: Write the eval group’s preparation steps as sandboxLayer():
Members cannot choose template, but they can add their own .before() / .after() commands. The preparation steps of the Experiment, eval group, members, and Agent are placed in one order: dependencies first, then ascending changeFrequency. So a rarely changing step in the eval group can come before a frequently changing step in the Experiment. Lifecycle callbacks and Agent Context within a group can read ctx.evalGroup.id and ctx.evalGroup.definitionHash. The same function may also serve ungrouped evals, so evalGroup is an optional field. When you need to isolate caches or service names by group outside workdir, first check that the field exists, then build names from the group ID.

Do not rely on group order to pass results

After each Attempt, NiceEval restores workdir to its state when shared preparation finished. Files an earlier eval wrote into workdir are not visible to a later one; $HOME, /tmp, global installs, and background processes may stay behind. When you cannot accept those leftovers, do not reuse Sandboxes. An earlier eval may not even run. Result reuse, command-line filtering, an exhausted budget, and interruption can all keep a member out of the Sandbox this time. When a later step must read a file from an earlier step, write both steps in the same eval. Only put evals in the same group when what they share already lives outside workdir and each eval can reach a verdict on its own.

Common errors