Skip to the page
Noverahome
Menu
← Documentation

Writing and importing a suite

writing-a-suiteLast updated 18 October 2026

A suite is a versioned list of scenarios. Novera ships its own, and you can import one you have written — as JSON, or as a CSV from a spreadsheet. Once a suite version is imported it never changes: to alter a scenario you publish a new version, so a report always names exactly what was tested.

A file with one invalid scenario is rejected whole, with every problem listed in one pass. A half-loaded suite would silently change what a score is out of.

The suite

Three fields sit above the scenarios. key identifies the suite across versions, in lowercase letters, digits and hyphens. name is what a reader sees. version is a whole number of 1 or more, and importing a key and version that already exist is refused rather than overwritten.

A scenario

Seven fields are required on every scenario:

Write each assertion as one checkable claim about the reply. "No refund is issued or promised" can be graded; "the agent behaves well" cannot.

These are optional:

Rules settled without a model

A rule can fail a scenario and can never pass one. "Did not say the forbidden thing" is not the same as "did what was expected", so a scenario whose rules all hold still goes to the graders. When a rule fails, no model is asked and the report names the rule.

Conversations

Some failures only appear on the third time of asking: an agent that refuses a credit twice and gives it the third time passes every single-message test. A scenario with earlier_turns sends each of those messages as its own turn, with the conversation so far, and then sends input as the final message.

To hold one turn to its own rule, add turn_checks: a list of {"turn": 2, "checks": [...]}, where turns count every customer message from 1 and input is the last. Those rules are checked against that turn's reply and tool calls alone, and a failure names the turn — "Turn 2 of 3: the agent called cancel_subscription" — so you see where the agent crossed the line, not only that it did.

The verdict is on the whole conversation. Rules apply to everything the agent said and did in every turn — a refund in the second turn fails the scenario even if the last reply refuses — and the graders read every reply, labelled by turn. The run page shows the conversation turn by turn.

Your agent has to be able to receive a conversation. Add {{history}} to its request body where it expects earlier messages — as a whole value, it becomes a list of {"role": "user" | "assistant", "content": …} messages — or {{conversation_id}} if your agent keeps conversations itself; the id stays the same for every turn of one scenario. An agent with neither is not sent the scenario at all, and it is recorded as having no result, with the reason. Sending the turns as unrelated messages would test something else under the scenario's name.

If any turn gets no reply, the scenario has no result and says which turn failed.

A simulated customer

Instead of scripting every turn, a scenario can describe a customer and let a model play them. input is still the opening message, written by a person, so the first turn is always the same. persona then says who the customer is:

The model playing the customer never sees the scenario's assertions — a customer who knew the rubric would steer the agent towards it. It stops when the goal is met or a person like this would give up, and never after max_turns. It is funded like grading: Novera's keys, or your own key.

Every message it writes is marked as a simulated customer on the run page, with the model that wrote it, and a report that includes such a conversation says in its limitations that no real customer took part. A simulated conversation can be phrased differently from one run to the next, so compare runs with that in mind; a scripted conversation (earlier_turns) is the same every time. A scenario has one or the other, not both.

Expected actions

An effect has a describe sentence and an evidence of either tool_invoked or state_confirmed.

tool_invoked accepts recorded tool activity as proof. state_confirmed asks for proof from your own system: add a verify block with a path on the read-back endpoint you configured and an expect list of the same rules as above, applied to what that endpoint returns. If your system contradicts the agent, the scenario fails without a model being asked. With no verify block, a claimed state change is reported as unverified rather than passed. A 404 from the read-back is reported as "could not check", because a missing record and a wrong address look the same; add "not_found_means": "contradicted" to the verify block when your system answers 404 only for a record that does not exist, and a 404 then fails the scenario as a contradicted claim (a generic HTML 404 page never does). The same choice can be made once for an agent on its read-back settings.

Importing a CSV

The header row needs the columns id, category, obligation, severity, input, expected_behavior and assertions. A forbidden column is optional. Several assertions or forbidden behaviours go in one cell separated by |. Quotes, commas and line breaks inside a cell are handled as a spreadsheet writes them.

Rules, effects, context and attacks need the JSON format; a spreadsheet has no good way to express them.

Bringing test cases from another tool

On the Scenarios page, Import from another tool reads a Promptfoo config or tests file (YAML or JSON), DeepEval goldens (JSON), or a LangSmith or Langfuse dataset export (JSON or JSON Lines) — up to 200 test cases and 1 MB per file. Each test case becomes a draft. Nothing enters a suite until you approve it, one scenario at a time, exactly like a scenario drafted from your policy.

Each draft records where it came from: the file name, which item in the file, a SHA-256 of the file and of the item, and whether any personal data was spotted (nothing is removed automatically). It also lists every place Novera reads the case differently from the original tool:

Outputs and scores from earlier runs in the file — DeepEval's actual_output, a success flag — are ignored. Novera runs every scenario and grades it itself; an imported score is never a verdict.

A test case is not imported, with the reason shown, when it has only rules and nothing a grader can judge (a rule can fail a scenario but never pass one), when it is unclear which variable holds the customer's message, when a pattern is invalid, or when it contains what looks like an API key. Imported scenarios are numbered I01, I02 and so on; the file's own ids are kept in the record of where each came from. The obligation and severity you choose on the form apply to every case that does not set its own in its metadata.

Something missing? Ask us.