Scripted
A fixed conversation with per-turn expectations. Answers “at this point, did the agent make the right next decision?”
Simulation
A persona, a goal and success criteria — an LLM plays the caller and improvises. Answers “by the end, did it reach the right outcome?”
Your first scenario
A scenario’sdefinition is a conversation and what you expect back.
202 Accepted with a batch_id: a dedicated worker executes runs, never the API process, so eval load can never become dead air on a live call.
Targets: what you run against
agent_name follows the publish. inline runs a configuration that was never published — which is how you check a prompt change before shipping it, and how you point a scenario at sandbox tools.
Tool mocking, and why it’s on by default
A scenario that exercises “book the appointment” against an agent with a real webhook books a real appointment, on every iteration. So scenarios carry mocks:mock_only, the default, a tool call with no mock is refused and the run ends as errored naming the tool, rather than executing. live is the opt-in for agents whose tools are read-only lookups.
Mocks belong to the scenario, not the run: “the booking succeeds, does it report the confirmation correctly?” and “the booking fails, does it avoid claiming success?” are two different tests.
Text or audio
modality: "text" skips STT and TTS — fast, silent, cheap, and what you run on every pull request. modality: "audio" synthesizes the caller’s turns into the agent’s real STT and judges a transcription of the agent’s real TTS. Audio is what covers recognition, synthesis and turn timing; text cannot see any of it.
Audio runs use local models for the caller’s voice and the judge’s transcription, downloaded on first use. Budget for that the first time.
Simulations
Give a persona and a goal instead of turns, and an LLM improvises the call:success once over the whole conversation; metrics are scored per agent turn, and one below its min_score fails the iteration. A persona never says the same thing twice, so use iterations — one run is an anecdote.
In CI
1; an error — a judge outage, a pipeline that died — exits 2, so a broken checker never reads as a broken agent.
turncall eval show <run_id> prints a run’s transcript with its verdicts. Scenario files are the API request body unchanged — turncall eval run scenarios/*.json --agent-name support runs them without storing anything.
Start from a real conversation
Evals only find what someone thought to test. Turn a conversation that already happened into a draft — a voice call:save: false is the default and returns the draft without storing it; true stores it.
A session names its agent directly, so from-session takes its default_target from the session. A call may have run an inline agent with no agent row at all, so from-call reads the call record instead.
To convert a WebRTC call the browser just made, read the call id from the signalling answer — POST /v1/webrtc/connect returns it beside sdp and type. See WebRTC.
Results
call.ended has no equivalent here — subscribe to eval.run.started and eval.run.completed instead. The completed event is comprehensive: status, counts, every iteration’s transcript and failures, and the configuration snapshots. The envelope carries eval_run_id beside call_id and session_id.
Aggregate a whole batch without pulling transcripts:
passed_count/failed_count out of iterations — there is no score. errored is neither: it means the harness could not complete, so it counts toward no rate at all.
What evals cannot see
Worth knowing before you trust a green suite. An eval swaps only the transport, so everything inside it is invisible:
Everything the pipeline builds is covered, which is where the regressions that shipped have actually lived: STT and TTS construction, provider model names, VAD and turn-detection wiring, the tool bridge, knowledge retrieval.
The judge is an LLM and is not deterministic. Use binary criteria, use iterations, and treat a single flip as a signal to look rather than a verdict.