check, closeQA, .gate(), and .score() methods.
This page continues NiceEval Taste. Read its four principles first; it also contains the shared checks before delivery.
Responsibilities
The Adapter reads the application’s existing protocol, execution records, and state instead of inventing another dataset. Facts passed to checks are read-only and isolated by Attempt. Keep log conversion, record correlation, and billing in the Adapter, out of the eval body.
Start with the scenario and criteria
State the business task in one sentence, then list requirements that can fail independently. For each, specify the object, whether it means “once” or “every time,” the observation window, and the evidence source. Assign weights last. Write the body in the order “what happened, what is checked, what gate and weight apply.” Comments explain why a check matters instead of repeating its method name. Separate observation prerequisites, business gates, and scoring with blank lines. Prefer another pass over the data to hiding criteria inside a general wrapper for performance. Evidence must establish the conclusion. “The recipient received the email” requires a matching send receipt and receiving record. Generating the email body only proves output exists; accepting the send request only proves processing began. Neither alone proves delivery. An abstraction should clarify criteria, not hide them. Moving thirty lines of filters into another file namedjournalMatches does not solve the problem. At the other extreme, a runEverything() helper that hides every check and weight makes the eval impossible to review.
Separate reading facts from judging them
A context Match has two parts:read(ctx) reads facts or complete material available to the current Attempt; its inner match decides how to judge them. NiceEval injects ctx when running the check. You do not pass ctx or the whole log as an argument. See the Match reference for signatures.
Use application vocabulary close to the facts: characters, speech, operations, requests, and model responses. Log filenames and storage formats should not become the main language of evals. The Adapter defines how facts are correlated, when they are complete, and which time window a measurement covers.
An application can provide checks through its Adapter’s assertions, such as t.said(match), t.systemOne(match), or t.hp(selector, collectionMatch). They read facts and pass them to the same check. These are application-defined methods, not NiceEval built-ins. Calling one registers a check; no separate scope lookup or registration step is needed.
Keep criteria in the body, not hidden in names
winnerActedIndependentlyInCombat() looks concise, but a reader must open its implementation to learn what it checks. Does it verify identity, a successful model call, and an adopted decision, or just find a log mentioning “combat”? Naming a function after the conclusion does not make its criteria visible.
Small domain criteria can use domain names, such as character, health, decision, and operation. Each should express one explainable condition. Leave equality, counting, composition, and failure diagnostics to general combinators. Put the selected subjects, required counts, and simultaneous conditions in the eval body.
The same applies to objects. “The crate arrived intact” needs three visible conditions on one object: id === crateId, durability >= minimum, and positionId === destinationId. If the crate must actually have been carried, check a separate operation record; its current position does not prove the process.
This fragment assumes the application is connected. Combinators come from niceeval/expect; t.hp, t.systemOne, t.objects, and the character, model, decision, operation, object, durability, and position Matches are application-defined. Connect the application’s Adapter first.
idsidentifies the characters in this scenario,idthe character being checked, andoperationIdsthe allowed operation types. Define them in the scenario declaration.- “All defeated” becomes a health condition plus a count; “independent action” becomes a successful model call plus an adopted decision.
scenariosupplies the object ID, map, and destination. The first argument tot.objectsselects the object; the second checks that same object’s durability and position. The weight of 50 is written directly after them.
t.objects uses public combinators such as filterWhere and countWhere, then passes the result to check. It does not call an internal evaluator and is not a built-in NiceEval object API.
The Adapter must provide correlated facts so combined conditions concern the same object. Do not build temporary data structures or expand log filters in the eval body, and do not have the Adapter return a conclusion such as “this eval passed.”
A successful model call, an adopted decision, a successfully executed operation, and an achieved goal are four distinct facts. You cannot combine A’s successful call, B’s adopted decision, and C’s operation into a pass, or interpret one successful model call as task success. An adopted decision does not prove execution succeeded; write another check when the task requires execution results.
Collection combinators
For an incomplete collection, these combinators return an indeterminate result. They do not treat a subtotal of known items as an exact count.
Check that all required characters are present separately. Three characters with zero health do not prove that every specified character was observed. An empty collection has a count of zero, which also cannot prove “everyone meets the condition.” The Adapter must preserve unknown states for missing characters or measurements instead of dropping them and claiming the collection is complete.
Specify once, every time, and never
“At least once,” “every time,” and “never” need different evidence. First limit the scope to the same Attempt, object, and observation window, then decide whether missing records affect the conclusion.
Empty-collection rules depend on the check.
closeQA scores zero, while notCalledTool passes. A partial or unavailable collection cannot prove an event never happened and must not be treated as empty.
One qualifying instance proves “at least once,” not “all qualify.” Likewise, one violation disproves “no violations” even if the remaining material is incomplete. Mark insufficient evidence honestly instead of always treating it as a business failure.
Combined conditions must concern the same object. “An action by A exists” plus “speech delivered to B exists” does not prove “A spoke to B and it was delivered.” A’s movement and C’s speech could satisfy those two conditions separately.
Complete example: A support task
The next three blocks form onesupport-review.ts module, in order. It exports a project-defined createSupportReview factory: pass the application’s existing observation function and receive an Adapter and an eval. This factory is not a NiceEval API; it keeps integration code and the eval body separate and readable.
Default-export the returned evaluation from an .eval.ts file and use the same returned adapter in the Experiment. Configure Judge before running; see Verify Judge.
Part 1: Provide application facts and preserve unknown states
observe(ctx) executes or waits for the support task and returns read-only facts for the current Attempt. It responds to cancellation through ctx.signal, registers connection cleanup callbacks as needed, and reports usage through ctx.recordUsage. Correlate records, decode protocols, and validate time here, outside the eval body.
Keep summary failures from affecting other metrics
First hand the sealed raw records toctx.attach and wait for the archive receipt, then register the physical calls obtained with ctx.recordUsage and call ctx.sealUsage. Compute derived metrics such as latency and percentiles last. When evidence or usage comes from independent sources, complete each handoff separately, and do not treat “the file exists” as “archived”.
Return unavailable material only for known summary errors that clearly belong to a specific metric:
sealUsage is a non-secret ASCII identifier of 1–128 characters: the first character is a letter or digit, and the rest may be letters, digits, dots, underscores, colons, and hyphens. For example, ai-event-stream-incomplete; do not use a sentence with spaces.
Completion status needs evidence:
- A completed task has a completion event and a verified nonnegative
businessSecondsvalue. - A task confirmed incomplete has a record showing observation fully ended.
- If neither can be confirmed, return
unavailable.
Part 2: Domain Matches select material only
Select speech only by character. Undelivered speech and speech with unknown model origin must also contribute to expression-quality assessment.read gets material from the injected ctx without registration. The read budget covers the entire collection, including nonmatching speech.
Completion status and business time are already facts: read them and calculate directly. Do not create a “completion efficiency Match” just to access a field or wrap arithmetic. Keep the formula, time scale, and weight in the eval body.
Part 3: Make every criterion and weight visible
This eval awards 5 points for participation, 45 for expression quality, and 50 for completion efficiency. It also has three gates: at least one support utterance was delivered, token usage stays within its limit, and runtime stays within its limit.said and speechDelivery are this Adapter’s own checks. said checks for at least one utterance. speechDelivery passes the complete speech collection to combinators for selection, projection, and counting, preserving incomplete states. Neither decides the eval’s pass conditions for its author.
- Two kinds of budget. The limits in
closeQAcover Judge material and audit records;captureinspeechMaterialcovers reading the whole collection. These example values do not fit tasks of every length. - Two kinds of time.
t.elapsedMsis the eval run’s wall-clock duration. Completion efficiency uses application business time. They are not interchangeable. - Unreported usage does not pass the check. Read
t.usageandt.elapsedMsdirectly as properties. Missing tokens remain unknown, not zero. - Each score proves only its own item. Completion at business second 90 earns 25 efficiency points; Judge must score expression quality separately. If Judge is unavailable or material exceeds capacity, the total score is incomplete. Delivery also does not prove model origin: add a deterministic check if the business requires independent model-generated speech.
Case 1: Ask for directions, then actually meet
A must ask B how to reach a meeting place, then walk there independently and meet B. The eval assesses A’s choices, execution, and outcome, not whether the player completed the task for A. Judge four things separately: expression quality, useful information, actual delivery, and model origin. They require different material:- Expression quality uses all of A’s speech in the observation window, including unheard or incorrect speech and speech with unverified origin. Keep questions, answers, refusals, and clarifications, with the necessary two-party context. Do not filter for delivered or verified speech first.
- Useful information uses the complete delivered conversation from both sides, together with the official map.
Case 2: Negotiate transport and actually deliver
A and B must carry an item together. Establish the business conditions first: do both already know the goal, is negotiation required, and who can refuse? Derive criteria from those conditions instead of copying the directions task.
For communication quality, retain both sides’ complete conversation and ask questions answerable from it. For delivery, correlate the item, destination, executor, and completion receipt. Score communication and delivery separately, with weights visible in the body.
Apply the same approach to operation requests and model responses: keep failures and adjustments in operation records, actual sent content in request records, and rejected outputs in model responses. Observe choice, execution, and outcome separately; one does not substitute for another.
closeQA(materialMatch, question) can assess all of a character’s speech, operations, requests, or model responses. NiceEval passes every matching item to Judge in the original order. You need not call the model once per item in the eval. The first item, a summary, or a few successful samples cannot represent overall performance.
Write a question used by only this eval directly in its closeQA call so readers see both material and criteria. Shared question text can be a reusable constant, but do not hide material selection, question, and weight together in a scenario-specific function.
A saved final position proves only the ending location. Even if the application attaches it to every operation record, it is not the location at the time of each operation. Assess a route or step-by-step trajectory from records captured at the corresponding times.
Common misleading patterns
These are business pseudocode examples. Variables represent application facts, not NiceEval fields.Separate usage from business time
Call count, tokens, and cost are different metrics. The Adapter reports usage for every real call, including failures and retries; NiceEval aggregates it. Do not maintain another model price table in the eval. Unknown cost is not zero, and a known lower bound is not a complete total.t.usage and t.elapsedMs provide general usage and wall-clock duration. Measurements such as “calls before completion” or “game time to completion” require an observation window explicitly defined by the application. Do not rename a usage snapshot “usage before completion,” or substitute game time for runtime.
Preserve complete evidence
Retain material in its original order and make it traceable to records from the same Attempt. “Complete” is relative to the declared business scope; it does not mean sending every unrelated log to Judge. Select by object, task, and observation window, not by performance. When the whole conversation matters, do not truncate text or discard refusals and clarifications. Report capacity overflow as indeterminate instead of shortening the window, sampling, or retaining only successes to obtain a score. If the business question changes, write a separate eval; its result does not answer the original task.Account for reading and Judge budgets separately
These budgets cover different things:- Read budget covers the entire candidate collection, including nonmatching records.
- Judge material budget covers the acceptance question, all matching material, and its identity information.
- Audit budget must also fit the actual Judge request, response, and references.
Calculate scores with ordinary arithmetic
Completion duration is an application fact. Do not pass the entire log into the eval. The application supplies traceable completion status and business time; the eval calculates the score directly without a new function, Match, or scoring mechanism for a field lookup or arithmetic expression. For “earlier completion within 180 business seconds earns more,” usemax(0, 1 - completionSeconds / 180) with a weight of 50:
- Complete observation proves no completion: zero points.
- Missing completion or observation-end record: indeterminate.
- A missing completion event is not a duration of zero; wall-clock runtime is not business time.
Ask Judge questions the material can answer
When an external map, rules, or reference answer is a real acceptance basis, provide it explicitly with its source. Do not put the desired conclusion into the question as a premise. Judge treats instructions inside the material as content under evaluation, not instructions to execute. When reusing a domain Match, review its name, what evidence it considers sufficient, and its failure explanations. Preserve business checkpoints and separate weights. Do not add multiple aliases or registration steps for one capability. Follow the installed version’s reference for interface shapes; design sketches are not runnable APIs.Additional checks before delivery
Complete NiceEval Taste: Checks before delivery, then verify the cases specific to business facts:- Collection states: cover complete nonempty, complete empty, missing items, and partial collections. Ensure conditions from different characters cannot combine into a pass. Treat one survivor, no winner yet, and mutual defeat as separate business cases instead of treating every non-win as a technical error.
- Real application lifecycle: for external HTTP or a continuously running application, verify the order of cancellation, waiting for ongoing operations, and saving evidence. Check late events after cancellation, writes after saving, and honest marking of incomplete observations. A passing local Fixture does not verify the whole application lifecycle.
- Does expression-quality assessment use all of the character’s speech, with delivery and model origin checked separately?
- Are collection reading, Judge material, and auditing each within budget, with overflow reported honestly?
- Can readers see characters, states, counts, gates, and combinations without opening helper implementations?
- Are reasonable refusals, silent cooperation, and adjustments after failure handled according to this scenario’s rules?
- Is final state being mistaken for a step-by-step trajectory? Are business measurement windows mixed with general usage?