Skip to content

tutorial

Chapter 2 of 5

Chapter 2 — Assertions: Facts About the Tracker

by Rod Rivera Published

The ground-truth half of every scenario: yes/no questions about what actually happened, exact and judge-free. Learn the assertion vocabulary, why you must test absence, and how to pin facts without pinning phrasing.

An assertion reads the conversation tracker — the engine’s own record of what happened — and answers a yes/no question: did this flow start? does this slot hold this value? did this action run? No LLM is involved in checking it, so whatever the simulator improvised and however the judge felt that day, the assertion result is a fact.

That is why assertions are the ground truth inside every scenario: the judged criteria capture quality of handling, but the non-negotiable facts — the right account, the tool actually ran, the right number was spoken — are nailed down here, where variance cannot reach them.

The shape

From eval/scenarios/balance_named_up_front.yml in the companion:

scenario:
  name: Customer names the account up front and gets its balance in one pass
  simulation_context: >
    You are a calm, logged-in customer of a retail bank. Open by asking for
    the balance of your Everyday Checking account...
  goals:
    criteria:
      - The agent does not ask which account the customer means...
    assertions:
      - flow_started: check_balance
      - slot_was_set:
          - name: check_balance.selected_account_id
            value: acc_checking
      - action_executed: get_balance

Read the assertions as a claim: however this conversation went, by the end the right skill started, the right account landed in memory, and the tool ran. Three facts, three assertions — and the criteria above them handle the “however it went” part.

The vocabulary

The scenario schema pins these assertion types (rasa/builder/copilot/mcp_server/schema/scenario_schema.yml in the installed engine — and the checking machinery is literally the classic e2e assertion classes: the scenario engine imports rasa.e2e_test.assertions directly, assertion_engine.py:9):

AssertionChecks
flow_starteda flow with this id started
flow_completed / flow_cancelleda flow finished / was cancelled
slot_was_set / slot_was_not_seta slot holds (or doesn’t hold) a value
action_executeda named action ran
bot_uttered / bot_did_not_uttera response, by name, buttons, or regex
pattern_clarification_containsthe clarification pattern offered these flows
sequencingthese facts occurred, in this order
generative_response_is_grounded / ..._is_relevantthe judge — Chapter 4’s subject

Two naming details that will otherwise cost you an afternoon: memory keys are scope-qualified — the fixture’s slot is check_balance.selected_account_id, skill id then entry, never the bare entry name — and a prose skill’s flow id is simply the skill id (check_balance); the skill__block form belongs to ordered blocks (rasa/mantle/skills/catalog.py:686-714).

Three habits in the companion’s scenarios separate a suite that catches regressions from one that merely exists:

Habit 1 — assert absence, not just presence

From eval/scenarios/smalltalk_starts_nothing.yml:

goals:
  criteria:
    - The agent responds politely and briefly without pretending the
      customer asked for something
  assertions:
    - slot_was_not_set:
        - name: check_balance.selected_account_id

The cheapest way for an agent to look competent on a positive-only suite is to start a task for everything. Your suite must contain conversations where the correct behaviour is restraint — and assert that restraint happened. An agent that treats “thanks, bye” as a balance enquiry passes every positive scenario you own.

Habit 2 — match the fact, not the phrasing

- bot_uttered:
    text_matches: '9,140'

text_matches takes a regular expression. The disambiguation scenario pins "9,140" — the number the fixture tool returns for savings — and lets the model word the sentence however it likes. Pinning whole sentences gives you a suite that fails on every prompt tweak; a suite that cries wolf on wording trains your team to ignore failures, which is worse than no suite — you keep the cost and lose the signal.

The discipline generalises: assert what must be true (the number, the slot, the action), stay silent about what is allowed to vary (the phrasing).

Habit 3 — express order as sequencing

Some behaviour is an order of events. The correction scenario (balance_correction.yml) needs “the account was chosen, then changed”:

assertions:
  - sequencing:
      - slot_was_set: check_balance.selected_account_id # set once...
      - slot_was_set: check_balance.selected_account_id # ...then re-set
  - slot_was_set:
      - name: check_balance.selected_account_id
        value: acc_savings

Each child of sequencing must occur, in this order. The same slot set twice is the correction — a judged criterion could only say the agent “seemed to handle the change of mind”; this states it as a fact.

What this instrument cannot see

Look again at the vocabulary. Every row is about structure: flows, slots, actions, responses. Nowhere is “was this answer good?” — because that is not a fact about the tracker. Ask the FAQ skill about overdraft fees and it answers in free text: an assertion can prove a response was uttered, even regex-match a fragment, but it cannot judge whether the sentences were accurate, complete, or on-topic.

For that there is no fact to assert — that is judge territory, Chapter 4. But first: who is actually talking to your agent during all of this, and why is it not you?