experiments/. CLI positional arguments only select which evals to run; they do not temporarily change the agent or run configuration.
Minimal experiment
agent is an already configured agent instance. URL, auth, and protocol details for the system under test normally go into the adapter factory; the runner does not keep a separate agentConfig field.
Evaluate how different system prompts affect the agent
Use the flag mechanism: configure different flags across two experiments to compare different prompts.model is passed to the adapter as ctx.model; if your agent supports model selection, build the request yourself.
flags is passed to the adapter as ctx.flags, and also appears in the eval as t.flags.
The semantics match a feature flag in product A/B testing: write the adapter so it forwards the flag to your agent, and the agent switches between different system prompts or behavior based on the flag.
Write a group of experiments
One experiment file is one configuration cell. To compare multiple models, agents, or flag values, put several files in the same folder:group/filename, no extension) for an exact match to a single file:
Common fields
Start experiment-shared services
Some resources are “one per experiment, shared by all attempts”: a tunnel to an internal memory service, an experiment-specific mock server, a license lease. Resources like these go into a pair of experiment-level hooks,setup and teardown, each running at most once for the whole run. setup runs before the first attempt this experiment is about to dispatch; teardown runs after all attempts finish (it also runs if the run is interrupted), and it fires if and only if setup’s point has already been reached — setup throwing still leads to teardown running, so the teardown code must defend against variables that may not have been assigned. When every previous result gets reused and this experiment doesn’t need to actually run a single attempt, neither setup nor teardown runs:
setup is running, the terminal’s ACTIVE area shows a line experiment setup · <experiment id>, and ctx.progress(...) messages update at the end of that line; attempts waiting on it count toward the queued total — this is not a hang. In CI or agent output, setup and teardown each append one line for their start and one for their end.
When setup throws, every attempt in this experiment is recorded as errored (error code experiment-setup-failed) and listed individually in the report; other experiments in the same batch run normally — an environment that fails to come up should not masquerade as green, and it should not drag down anyone else.
Releasing resources in teardown is the non-negotiable floor: wrap it in try/finally so it runs regardless of whether the observation code before it failed. Observation actions (health probes, metric reporting) are only best-effort — give them their own short timeout, don’t let a failure block the release, and skip them outright when ctx.signal.aborted; on the interrupted path, an observation call that might hang must not stand in front of “tear down the tunnel, release the lease”:
setup / teardown only handle services that are “one per experiment, on your machine.” To prepare the environment inside the Sandbox per experiment before the agent runs — installing binaries, warming up, loading and storing state across attempts — attach hooks to the spec in the sandbox field:
.setup() should not rebuild the same environment on every attempt. For the steps to derive a prebuilt environment from an official Docker image, E2B template, or Vercel runtime, see Sandbox providers.
For when hooks run, how multiple hooks order, and what failure means, see Sandbox providers.
Working together with Sandbox hooks
Experiment-level hooks start host-side services; Sandbox hooks write the coordinates into each Sandbox and store state back at teardown — the two layers connect through module-level variables in the same file, and the runner guarantees the ordering: the experiment-levelsetup runs before any Sandbox hook in this experiment, so a Sandbox hook is guaranteed to read a variable that has already been assigned:
sandbox chained hooks, reading the experiment-level artifacts; how the agent connects to itself and the eval’s task fixtures each live in the agent definition and the EvalDef, not in the experiment file.
Multiple experiments sharing the same lifecycle code
A comparison group often has several experiments pointed at the same kind of infrastructure — the same memory product, with claude and codex each as one comparison cell, using identical start/stop mechanics. Write the start/stop logic as a factory function that returns a complete kit sharing one closure: the experiment-level hook pair, a getter that lets the agent/MCP factory read the coordinates, and a sandbox hook that writes the coordinates into the Sandbox. Each experiment file calls the factory once, sharing the same code while each gets its own instance and coordinates:- The factory only creates the closure at import time — it does no I/O and reads no config. Experiment files are imported during
niceeval exp’s discovery phase, and an import that throws drags down unrelated experiments in the same batch; leave all hard failures tosetup. - Runtime coordinates live in the factory closure, not in a module-level singleton — two experiments running in parallel in the same batch each hold their own copy and never overwrite each other’s; the coordinates exist only after
setupruns.
teardown fires if and only if the same-layer setup has reached its point, and setup throwing still pairs with teardown firing — refs never leaks.
The boundary is the run’s lifecycle: a service shared within a batch does not outlive this run. A service that needs to exist across runs (started ahead of time, run against repeatedly with multiple niceeval exp invocations) is still started and stopped by external orchestration (such as docker compose), with its URL passed in through an environment variable.
Let different evals use different prebuilt environments
A batch of real-world tasks may need different runtime and dependency versions. Put the concrete template-bearing factory on each Eval so the task and its execution environment remain one declaration:ctx.model and ctx.flags.