Back to app

Scenario grammar reference

Generated by tools/gen_grammar_reference.py from callqa_spec. Do not edit by hand.

Grammar version: schema_version: 1. This page is generated from the models the server uses to validate scenarios, and every example on it is checked with callqa_spec.load_scenario.

Contents

General rules

  • YAML and JSON are equivalent. The API takes JSON, or YAML on POST /scenarios/import.
  • Unknown fields are rejected, at every level, unless their name starts with x_.
  • Strings are trimmed (leading and trailing spaces removed).
  • type selects the kind of a step or an assertion.

Scenario

The document you write. Required: name, target, persona, goal.

Field Type Required Default Constraints Description
schema_version integer no 1 Older documents are upgraded on read; a version newer than the server is refused.
name string yes
target Target yes
persona Persona yes
goal string yes
test_data map (free keys) no {}
steps list of Step no [] Optional IVR navigation hints.
assertions list of Assertion no []
limits Limits no defaults of the object
variants list of Variant no []
tags list of string no []
schedule string or null no null Cron expression, UTC unless timezone. The API also accepts every day at 09:00 UTC-style text. See the scenario guide.
timezone string or null no null IANA time zone of schedule (e.g. Europe/Paris). Default UTC. Local wall-clock time: 8:00 stays 8:00 across daylight saving changes. Checked against the IANA time zone database. A time zone may also end the schedule text; if both are given they must be the same.
call_window string or null no null 'HH:MM-HH:MM Area/City'. Only scheduled runs wait for the window; checked by the API.
environments map (free keys) or null no null Per environment plan, keyed by environment slug (staging): whether the scenario runs there, with its own schedule, timezone and call_window. The top-level fields stay the plan of the default environment.

Target

The phone number under test and what answers it.

Field Type Required Default Constraints Description
phone string yes Number to call, E.164 (e.g. +15550123456). E.164 after removing spaces, dashes, dots and parentheses (+1 (415) 555-0100 is accepted and stored as +14155550100).
kind enum: ai_agent, ivr, iva, human, unknown no unknown
platform string or null no null Free text: vapi, retell, bland, livekit, twilio, genesys...
language string no en-US BCP-47 language the call is held in.

kind values: ai_agent, ivr, iva, human, unknown.

Persona

Who the caller is and how they behave.

Field Type Required Default Constraints Description
description string yes Who the caller is and how they behave.
voice string or null no null TTS voice id; provider default if empty.
accent string or null no null
speaking_rate number no 1.0 >= 0.5, <= 2.0
patience enum: low, normal, high no normal
interrupts boolean no false If true the caller barges in while the agent speaks.

Limits

Hard limits for each call of this scenario.

Field Type Required Default Constraints Description
max_duration_s integer no 300 > 0, <= 1800
silence_timeout_s integer no 20 > 0, <= 300
ring_timeout_s integer no 45 > 0, <= 120

Variant

A test case derived from the scenario (#1..#N). Every field except title overrides the scenario for this case; assertions are appended to the scenario's assertions, test_data is merged over it.

Field Type Required Default Constraints Description
title string yes
source enum: auto, manual no manual
persona Persona or null no null
goal string or null no null
instructions string or null no null Extra guidance for this run.
test_data map (free keys) or null no null
assertions list of Assertion or null no null Appended to the base assertions.
enabled boolean no true

Steps

Optional navigation hints, followed in order. Useful for IVR menus. An AI agent target usually needs none: the caller converses from persona and goal.

steps:
  - type: expect
    text: "main menu options"
    timeout_s: 20
  - type: dtmf
    digits: "2"
  - type: wait
    seconds: 1.5
  - type: say
    text: "account balance"

say (SayStep)

The caller says this text.

Field Type Required Default Constraints Description
type "say" yes
text string yes

dtmf (DtmfStep)

The caller presses these keys. w is a 0.5 s pause, W a 1 s pause.

Field Type Required Default Constraints Description
type "dtmf" yes
digits string yes Only 0-9 * # w W.

expect (ExpectStep)

The caller waits until the target says something with this meaning.

Field Type Required Default Constraints Description
type "expect" yes
text string yes What the target should say (semantic match, not exact).
timeout_s number no 15.0 > 0, <= 300

wait (WaitStep)

The caller stays silent for this many seconds.

Field Type Required Default Constraints Description
type "wait" yes
seconds number yes > 0, <= 600

Assertions

Pass/fail checks run on the call trace after the call. Every assertion has these common fields:

Field Type Default Description
id string or null null Stable id, reused across runs for trends.
severity enum: fail, warn fail

severity: fail makes the call fail when the check fails. severity: warn only marks it warn. Semantic assertions are judged by an LLM judge that quotes the transcript as evidence; deterministic ones are computed from the trace timings.

must_include (MustInclude)

Semantic: judged on meaning by the LLM judge, not by exact words.

Field Type Required Default Constraints Description
text string yes Behaviour the agent must show, judged semantically.
assertions:
  - type: must_include
    text: "confirm date and time"

must_not (MustNot)

Semantic: the agent must never show this behaviour.

Field Type Required Default Constraints Description
text string yes
assertions:
  - type: must_not
    text: "hallucinate availability"

goal_reached (GoalReached)

Semantic: did the caller reach the scenario (or variant) goal?

No other field.

assertions:
  - type: goal_reached

llm_check (LlmCheck)

Semantic: a yes/no question the judge answers from the transcript.

Field Type Required Default Constraints Description
question string yes Yes/no question answered by the judge from the transcript.
assertions:
  - type: llm_check
    question: "Did the agent offer a callback?"

max_latency_ms (MaxLatency)

Deterministic. Latency of one turn = start of target speech minus end of caller speech. percentile picks p50, p95 or the max over the call.

Field Type Required Default Constraints Description
value integer yes > 0 Per turn, end of caller speech to start of target speech.
percentile enum: p50, p95, max no p95
assertions:
  - type: max_latency_ms
    value: 1500
    percentile: p95

max_duration_s (MaxDuration)

Deterministic. Whole call, from dialing to the last event.

Field Type Required Default Constraints Description
value integer yes > 0
assertions:
  - type: max_duration_s
    value: 180
    severity: warn

max_interruptions (MaxInterruptions)

Deterministic. Counts the times the target talked over the caller.

Field Type Required Default Constraints Description
value integer yes >= 0 Times the target talked over the caller.
assertions:
  - type: max_interruptions
    value: 1

transcript_regex (TranscriptRegex)

Deterministic. Python regular expression, case insensitive, searched in each turn of speaker. expect_match: false asserts that nothing matches.

Field Type Required Default Constraints Description
pattern string yes Must compile as a Python regular expression.
speaker enum: target, caller, any no target
expect_match boolean no true
assertions:
  - type: transcript_regex
    pattern: "booking (reference|number)"
    speaker: target

Short form

The format shown on callqa.dev is accepted as is. On import it is folded into the typed grammar:

Short form Becomes
phone: "+1..." target.phone
persona: "text" persona.description
must_include: [..] one must_include assertion per item
must_not: [..] one must_not assertion per item
max_latency_ms: N a max_latency_ms assertion (p95)

This short document:

name: "Book appointment - edge case"
phone: "+14155550100"
persona: "Impatient customer, speaks fast, has accent"
goal: "Book a haircut for tomorrow 2pm"
must_include:
  - "confirm date and time"
must_not:
  - "hallucinate availability"
max_latency_ms: 2000

is stored as (with every default filled in):

schema_version: 1
name: Book appointment - edge case
target:
  phone: '+14155550100'
  kind: unknown
  language: en-US
persona:
  description: Impatient customer, speaks fast, has accent
  speaking_rate: 1.0
  patience: normal
  interrupts: false
goal: Book a haircut for tomorrow 2pm
test_data: {}
steps: []
assertions:
- severity: fail
  type: must_include
  text: confirm date and time
- severity: fail
  type: must_not
  text: hallucinate availability
- severity: fail
  type: max_latency_ms
  value: 2000
  percentile: p95
limits:
  max_duration_s: 300
  silence_timeout_s: 20
  ring_timeout_s: 45
variants: []
tags: []

Extension fields x_*

Any key that starts with x_ is accepted at any level and kept verbatim (stored, exported, returned by the API). CallQA never interprets them. Use them for your own metadata (owner, ticket, region) or to try a field before it joins the grammar.

name: "Checkout flow"
target:
  phone: "+14155550100"
  x_region: "us-west"
persona:
  description: "Returning customer"
goal: "Buy the blue plan"
x_owner: "voice-team"
x_jira: "VOICE-142"

Complete examples

Real-world scenarios from examples/scenarios/. Each one is validated by the test suite.