Assertion type and feed into the same verdict rules. For the fifth mechanism — asking a language model to judge open-ended quality — see Judge.
The four assertion mechanisms
1. Value assertions
t.check(value, matcher) and t.require(value, matcher) evaluate a specific value immediately against a matcher from niceeval/expect. Use these for facts you can verify inline.2. Scoped assertions
t.succeeded(), t.calledTool(), t.messageIncludes(), and friends are registered during test(t) but evaluated after the function returns, against the complete turn data. Use these for whole-run facts.3. Project-test assertions
For Sandbox evals, run project tests, build scripts, or focused probe commands from
test(t). Use this for coding tasks where file content and build results are the ground truth.4. Efficiency assertions
t.maxTokens() and t.maxCost() turn token usage and estimated cost into scoreable dimensions. An agent that answers correctly but burns ten times the expected tokens should not score the same as one that answers efficiently.Gate vs soft severity
Every assertion carries a severity that determines how it influences the final verdict. There are exactly two severities:- gate
- soft
A gate assertion is a hard requirement. If it fails, the entire eval is immediately classified as
failed — regardless of how well every other assertion passed. Use gate for facts that must be true: “the agent called the correct tool”, “the response parsed as valid JSON”, “no shell commands errored.”Most matchers in niceeval/expect (includes, equals, matches, satisfies) default to gate. Scoped assertions like t.succeeded() and t.calledTool() also default to gate.Verdict rules
Once all assertions are collected, the runner takes the first matching rule in this fixed order and folds them into a single result:errored outranks everything else, because the execution evidence can no longer be trusted. failed outranks skipped, so that a t.skip() cannot mask a hard failure recorded earlier.
passed
No errors, all gate assertions passed, all soft assertions met their thresholds (or you did not run with
--strict).failed
At least one gate assertion did not pass, or a soft assertion fell below its threshold under
--strict. Hard failure.errored
An execution error, timeout, or author mistake — this run cannot support a trustworthy conclusion, and it is not disguised as an assertion failure.
skipped
t.skip("reason") was called. Excluded from pass-rate calculations entirely.attempts > 1), the per-eval summary becomes a pass rate (the fraction of attempts that produced passed) and an average latency, rather than a single verdict.
1. Value assertions — niceeval/expect matchers
t.check(value, assertion) evaluates the assertion immediately and records the result. t.require(value, assertion) does the same but throws immediately if the assertion fails, aborting the rest of the test function. Use t.require for preconditions: if a required fact is false, there is no point continuing.
The matchers available from niceeval/expect:
(value) => number — so you can write your own and pass them to t.check without any special registration.
2. Scoped assertions
Scoped assertions are registered duringtest(t) but evaluated after the function returns, against the complete accumulated turn data. They read from the standard event stream that t.send() produces (see Drive) and its derived facts — so as long as your adapter produces correct events, these assertions work identically for every agent.
Run / session dimension
Tool / action dimension
input argument to calledTool and notCalledTool supports a small matching language: a plain object performs deep partial matching, a RegExp matches against the serialized input, and a predicate function receives the raw input value.
Event stream dimension (low-level escape hatch)
eventsSatisfy and write an arbitrary predicate over the raw StreamEvent[].
Structured output (on turn, not t)
Workspace dimension (Sandbox agents only)
fileChanged("src/Button.tsx") matches when the Agent modified that path, fileDeleted("src/old.ts") matches when the Agent deleted it, and changedPaths([...]) matches the exact unordered set of changed paths. fileChanged(path, { before, after, status }) also matches content: before and after are read from the same change’s endpoints.
To read a file’s current content — for example, to hand it to a Judge as explicit { input, output } material — use await t.sandbox.readText(path). Attribution is decided by fileChanged, not by what you read.
Scoped assertions follow one rule everywhere: the receiver decides the scope, not the assertion name. t.* aggregates every turn of the whole eval run (including any t.newSession() sessions); session.* (from t.newSession()) scopes to that one session; turn.* (from t.send()’s return value) scopes to that single turn only. Same vocabulary, different receiver — see Drive for what each receiver is.
3. Project-test assertions (Sandbox evals)
For Sandbox coding evals, run validation commands insidetest(t) and record their result as assertions.
t.calledTool(...), t.sandbox.noFailedShellCommands(), t.eventsSatisfy(...), and diff assertions such as t.sandbox.fileChanged(...).
4. Efficiency / cost assertions
Token usage is a first-class evaluation dimension. An agent that answers correctly but burns far more tokens than expected should not be treated identically to one that answers efficiently.t.usage is available anywhere inside test(t) and exposes { inputTokens, outputTokens, cacheReadTokens?, … }. For Sandbox agents, token counts are extracted from the transcript by the adapter; for Direct Agents, they are returned in Turn.usage.
Custom scorers
A value assertion is just a function(value) => number | Promise<number>. You can write custom matchers using makeAssertion:
.gate(), .atLeast(0.7).
Related reading
- Drive —
t.send(),t.newSession(), and HITL: how you produce the Turn data these assertions read from. - Judge — the fifth assertion mechanism, for open-ended quality that can’t be expressed as a fixed rule.
- Write send — how the standard event stream is produced, and what scoped assertions depend on.
- Evals — how assertions fold into the eval lifecycle and verdict types.