Experiment fields
models
description
agent
agent and adapter must be provided.
adapter
model
reasoningEffort
judgeRuntime
flags
labels
{ line: "codex", memory: "mempal" }).
Values are string | number (validated at parse time). The line between labels and flags is “does it change what happens in the attempt”:
labels are not passed to ctx / t (the agent and eval cannot see them) and do not take part in the comparability configuration (changing them does not invalidate existing results);
they are only projected verbatim into the snapshot’s ExperimentRunInfo.labels for report dimensions (label() / numericLabel())
to group by. The default report recognizes the line key: when any Experiment in the group declares it, scatter points are grouped by line and connected.
See docs/feature/experiments/library.md “labels”.
attempts
--attempts overrides it.
earlyExit
attempts run in full to measure the complete pass rate).
Turn it on explicitly to save cost when you only want to know whether it can pass.
evals
"*" for all, a string array matched by id prefix, or a custom predicate (which receives each discovered and fanned-out
read-only EvalDescriptor, with no path / execution fields exposed); omitted is equivalent to "*". The predicate is evaluated once per
candidate eval of this invocation, and the result becomes the in-memory selectedEvalIds plan, not a filter called repeatedly at runtime
(see docs/feature/eval/library.md “EvalDescriptor” and docs/feature/experiments/library.md
“evals: iterate discovery results, choose your own”).
timeoutMs
sandbox
sandboxCache
plugins
defineExperiment().
sandboxReuse
sharedState
budget
estimatedCostUSD of finished attempts; that value is
computed from the model, token usage, and the runtime/config pricing table, independent of the
observed usage.costUSD reported by the Provider / Adapter, which does not drive the budget. Once the estimate reaches the limit, the Experiment’s remaining
attempts that have not started are skipped and run:budgetExceeded is reported once (attempts already in flight still finish).
maxConcurrency
maxConcurrency: 1 guarantees attempts run one by one in eval order),
or to slow down an Experiment that hits provider limits. A slot shares the attempt’s lifetime: it is held from before Sandbox creation until
teardown and Sandbox destruction finish, and any wait along the way (including turn retry backoff) keeps holding it.
maxConcurrency: 1 is therefore a strict critical section that the next attempt of the same Experiment cannot enter early.
It does not span Invocations; for shared external state across Invocations, declare sharedState.
classifyFailure
undefined for “unknown, hand off to the rest of the chain”. Failures in any per-attempt phase of this Experiment
are all routed to it; on the send failure chain it runs before the adapter’s classifySendFailure, because filtering by your own coordinates
is more specific than the protocol’s generic shape, so the scope axis wins when both claim the failure. The classifier must be fast, pure, and must not throw (a throw falls back to
undefined and is swallowed); it declares only the decision axis and the reason word, and retry and gating policy belong to the executor.
See docs/feature/error-classification/library.md “Experiment / eval authors: declaring the blast radius of a cause of death”.
setup
teardown and agent / sandbox hooks in the same file read them from the closure;
the runner does not mediate values. If setup throws, every Attempt of this Experiment gets an errored Verdict
(code "experiment-setup-failed", phase "experiment.setup"); other Experiments in the batch are unaffected.
The function body does not enter the fingerprint; after changing hook logic, use --rerun all to explicitly rerun everything.
See docs/feature/experiments/architecture.md “Experiment-level lifecycle”.
teardown
experiment-teardown-failed) and does not change the Verdict.