before and after.
Write a before / after pair
beforeruns before the first Attempt of this experiment that really executes.afterruns after all Attempts finish, and also runs when the run is interrupted.- Once
beforehas started,afteralways runs, even ifbeforethrows. Soaftermust guard against variables that were never assigned; the example above usestunnel?.stop(). - When all last results can be reused and not a single Attempt needs to really execute this time, neither runs.
before runs, the terminal’s ACTIVE area shows a line experiment before · <experiment id>, and messages from ctx.progress(...) update at the end of that line. During this time the experiment’s Attempts count as queued; nothing is stuck.
What happens when before fails
Whenbefore throws, every Attempt of this experiment is recorded as errored, with error code experiment-setup-failed. Other experiments in the same batch run as usual. An environment that did not start is never counted as a pass, and does not drag other experiments down.
Guarantee release first in after
Releasing resources must happen; observation such as health checks and metric reporting is only best effort. Wrap them intry/finally so an observation error cannot block release, and skip observation directly when the run is interrupted:
Do not put run-time addresses in flags
The tunnel gets a new URL every time it restarts.flags are experiment conditions; when a value changes, results already finished can no longer be reused. Putting a temporary address in flags means every tunnel restart forces the whole batch to rerun.
Keep the address in a module variable and let the Agent factory or Sandbox callback read it:
- Yes, such as the memory service version
0.10.39, where a different version may really behave differently: put it inflags, and rerun when it changes. - No, it is just this run’s connection coordinates: keep it in
beforeand a module variable. - An annotation only used to group reports: write it in
labels.
Prepare the environment in the Sandbox
before / after manage services on your machine. Preparation that must happen in the Sandbox before the Agent runs, such as writing configuration, installing binaries, or loading previously saved state, attaches to the sandbox’s .before(). It runs once for each Attempt that really executes, always after before, so it can read the variables before assigned:
before / after, and per-Sandbox preparation and cleanup are in .before() and ctx.onCleanup.
Fixed installations such as Agent CLIs and system packages are best baked into an image or template first. NiceEval reuses unchanged preparation steps and does not reinstall just because an eval changed. See Prebuilt Sandbox Environments · Continue building from the official baseline.
Save state across Attempts
In the example above, each Attempt loads memory at the start and saves it back at the end, so memory accumulates eval by eval. This requires Attempts to run strictly one after another:maxConcurrency: 1makes the Attempts in this run serial.sharedState.keyprevents you from running the same experiment in another terminal at the same time, with both sides interleaving reads and writes of the same state.
sharedState only guarantees that one run holds the key at a time. Storage itself, atomic write-back, and recovery after an abnormal interruption are still your responsibility. Do not put secrets in the key.
Share the same start/stop code across experiments
In comparison experiments, several experiments often target the same kind of infrastructure, for example Claude Code and Codex each connected to the same memory product. Write the start/stop as a factory function and call it once in each experiment file: the code is written once, while instances and coordinates stay separate:- When the factory function is called, it only creates a closure, with no I/O and no configuration reads.
niceeval expimports experiment files during discovery, and a throw at that point drags down unrelated experiments in the same batch. Put every operation that can fail inbefore. - Run-time coordinates live in the factory closure, not in a module-level singleton. Two parallel experiments each hold their own copy and do not overwrite each other.
after always runs in pairs with any before that started, the count always returns to 0 and never leaks.
Services that live across several runs
A service started bybefore does not outlive this one niceeval exp. For a service that should be started ahead of time and used across several runs, start and stop it with an external tool such as docker compose, then pass the address to the Adapter through an environment variable.
For the full parameters of before and after, see Experiment. For the order, caching, and cleanup of Sandbox preparation steps, see Sandbox · Prepare the Sandbox in order.