Guide: Design and run an agent-powered A/B test of your AI voices on real calls

Rime Team
Rime Team
July 25, 2026

Most teams building voice applications test everything except the voice. They A/B test the prompt, the retrieval setup, the escalation logic. Then they pick the voice, the first thing every caller hears, by playing three samples in a conference room and going with the one people liked.

We know this doesn't work, because we paid to find out. Earlier this year, Miravoice, an independent research firm, ran a controlled study across roughly 100,000 real outbound calls, testing 12 voices from three providers (Rime, ElevenLabs, and Google) with everything else held constant. Voice selection alone moved the intro hang-up rate by 2.5 percentage points and moved completion past the 30-second mark from about 12% to about 19%, depending on provider. Both results were statistically significant. At a million calls a year, 2.5 points is 25,000 conversations that end before they start.

Rime funded that study, and we'll be honest that we did well in it. But the method is more useful to you than our results. The study's design is reproducible on your own traffic, with your own use case, and you should reproduce it, because the honest answer to "which voice is best for my application" is always "the one that wins on your calls."

This guide covers how to run that test: picking the variable, sizing the experiment, logging the right events, and reading the results without fooling yourself. It includes three prompts you can paste into Claude, ChatGPT, or Cursor that do the heavy lifting. They share one event schema, so the output of each step feeds the next.

The finding that changes how you test

One result from the study matters more than the rankings: in the first ten seconds of a call, no voice had any edge at all.

Think about what an immediate hang-up actually is. An unknown number calls, someone picks up, hears half a syllable, and puts the phone down. That decision was made before the voice could do anything. It's about the call itself: the number, the timing, the person's general policy on answering machines and strangers. The voice's work starts a few seconds later, with the people who decided to give the call a chance.

This has a direct consequence for measurement. If you compare two voices on raw hang-up rate or raw completion rate, you're mixing two populations: people who never listened, and people who listened and then decided. The first group adds noise that can bury a real effect. The study handled this with what statisticians call survival-conditioned metrics, which sounds fancy but means something simple: of the people who stayed past ten seconds, how many finished?

That's the honest question, and it's the one raw rates can't answer. When Miravoice looked at raw cooperation rates, the differences between providers weren't statistically significant. Conditioned on surviving the intro, they were. Same calls, same data. The metric you choose decides whether you can see the effect at all.

Pick one variable and define the win before you start

A voice experiment fails at the design stage more often than at the analysis stage. Two mistakes account for most of it: testing two things at once, and deciding what "winning" means after seeing the data.

The list of things worth testing is long. Voice identity. The intro script. How you disclose that it's an AI. Speaking rate. What the agent does during tool-call silences. Barge-in behavior. All of these plausibly move caller behavior, which is exactly why you can only test one at a time. If you change the voice and warm up the script in the same experiment, a win tells you nothing about which change caused it, and offsetting effects can cancel out and tell you nothing happened when two things did.

The study is a good model for what "everything else held constant" means in practice. Every voice called at the same times of day, through the same windows, with the same callback number, the same attempt caps, and the same script. The voice was the only difference. Your version of that list will include your prompt version, your retry policy, your caller ID, and your routing logic.

Then there's the metric. Four behavioral metrics cover most voice experiments:

  • Hang-up during intro (the study calls it HUDI): the caller disconnects while the agent is still delivering its opening. The cleanest signal of "did this voice make someone want to leave."
  • Completion conditioned on survival: of calls that lasted past ten seconds (or thirty), how many finished the task. For inbound support, the analogue is containment.
  • Hard opt-out rate: "never call me again," or for inbound, an immediate demand for a human.
  • Time to completion: median duration of completed calls only.

Pick one as the primary KPI and the rest are guardrails. Then, write down the decision rule before launch: ship if the primary improves by at least X and no guardrail degrades past Y.

Why before launch? Because mixed results are the norm. In the study, Rime won hang-ups and completion, and lost the opt-out metric to both Google and ElevenLabs. If you wait until you have results to decide what matters, you'll pick whichever framing flatters the outcome. Everyone does. Writing the rule down first is the only defense.

Here's the first prompt that turns Claude or ChatGPT into an experiment design consultant: it interviews you about your application, your volume, and your stack, then produces a full design doc with computed sample sizes. When we tested it against a fictional dental-reminder company that wanted to change the voice and the script at the same time, it refused, made the case for testing the voice first, and priced out the factorial design to show why (52,000 calls, 17 weeks, not worth it).

Choose voices that match your use case needs from Rime's Coda model. Sign up and start testing for free.

The voice experiment designer

Voice Experiment Designer
Paste into Claude or ChatGPT
You are an experiment design consultant for production voice AI applications. Your job is to help me design a rigorous A/B test measuring how a change to my voice agent (voice selection, intro script, disclosure wording, speaking rate, etc.) affects real caller behavior — then give me a design doc my team can execute.

## How to run this session

1. **Interview me first. Do not design anything yet.**


The math nobody wants to do

Here's the part that stops most teams, so let's just do it.

Detecting a 2-point difference in hang-up rate at an 88% baseline (roughly what the study saw on outbound cold calls) takes about 3,800 calls per arm. Detecting the actual gap the study found between the top two providers takes about 4,500 per arm. That's with the standard settings: 95% confidence, 80% power, two-sided test.

Which means "we tried the new voice for a couple of days and it felt better" is not a result. At typical volumes, a couple of days is a few hundred calls, and a few hundred calls can't distinguish a 2-point effect from noise. You'd have roughly even odds of picking the wrong voice and feeling confident about it.

Some reference points, computed with a standard two-proportion power calculation:

Baseline Difference to detect Calls per arm
88% hang-up 2 points 3,841
88% hang-up 3 points 2,036
88% hang-up 5 points 778
52% completion 2 points 9,775
12% completion 3 points 2,036

Two things jump out. Small effects on mid-range baselines are expensive (that 52% row hurts). And the smallest effect worth detecting is a business decision that statistics can't make for you. If only a 3-point lift would justify switching providers, size for 3 points and save yourself half the calendar time.

Convert calls per arm into weeks using your real volume, and be careful about the difference between dials and answered calls. If your outbound answer rate is 60%, your 5,000 dials a week are 3,000 usable calls. The "designer" prompt does this arithmetic for you, and it's instructed to compute everything by writing and running code rather than estimating, because a language model guessing at sample sizes would defeat the entire point of this exercise.

If the math says eight weeks and you can't wait eight weeks, you have three honest options: accept a larger minimum effect, cut an arm, or run a sequential design with a scheduled interim look. What you don't get to do is run two weeks and squint at the trend.

Instrument before you randomize

Almost every metric above depends on two timestamps most platforms don't hand you: when the agent's intro started and when it ended. Without those, you can't compute hang-up-during-intro at all, and you can't condition on the ten-second survival window.

The schema below is what one call should produce. It's the same schema all three prompts in this guide share.

Field What it is
call_id Provider call ID
caller_hash Hashed caller identifier (never the raw number)
arm Which experiment arm this call got
dial_time, answer_time From your telephony events
amd_result Human or answering machine
intro_start, intro_end Start and end of the agent's first utterance
disconnect_time, disconnect_party When the call ended and who ended it
completion_marker Your task's terminal state (order placed, appointment confirmed, contained)
opt_out_flag, transfer_flag Booleans
tts_time_to_first_audio_ms For the latency check
agent_config_hash To catch config drift mid-experiment

Two details deserve attention. First, arm assignment should be a deterministic hash of the caller ID rather than a per-call coin flip. The same person reached twice should hear the same voice, both for their sanity and your statistics. Second, if your platform doesn't emit intro boundaries directly, you can usually derive them from word-level transcript timestamps. Validate the derivation against 30 or so actual recordings before you trust it.

Plan a week zero: instrumentation live, no randomization yet. Check that your derived events match reality, recompute your baselines from the new pipeline, and confirm they roughly agree with what you thought your numbers were. Then start the experiment.

This prompt is written for Cursor or Claude Code. Point it at your codebase and it explores your stack, proposes an implementation plan for the schema above, and builds it, with tests.

The instrumentation spec

Instrumentation Spec
Paste into Cursor or Claude Code, inside your project
Implement experiment instrumentation for my voice AI application: deterministic A/B arm assignment plus per-call event logging in a canonical schema. Work in my existing codebase and conventions — explore first, then propose, then build.

## Phase 1 — Explore (do this before writing any code)

Find and report back:

1. Where outbound calls are initiated / inbound calls are answered (the dialer or webhook handler).
2. Which orchestration platform and telephony provider we use (e.g., Retell, Vapi, LiveKit Agents, Pipecat, Bland, custom; Twilio, Telnyx, Vonage) and which lifecycle webhooks/events we already receive (call started, ended, transcript, post-call analysis).
3. Where call records are currently persisted (DB table, logs, analytics events) and what our storage conventions are.
4. Whether we already have any per-call timestamps for agent speech (word-level transcript timestamps, TTS events, turn logs).

Then propose an implementation plan mapped to the schema below, flag which fields our stack can't capture directly and how you'll derive them, and wait for my confirmation before implementing.

## Phase 2 — Build

### A. Arm assignment

- Deterministic, stateless: `arm = hash(caller_id + EXPERIMENT_SALT) mod N_ARMS` (sha256; salt and arm labels from config, not hardcoded).
- Assignment happens once per caller at the entry point, *before* the orchestrator call is created, and selects the per-arm agent/voice configuration.
- Same caller always gets the same arm — across retries, callbacks, and repeat contacts.
- Include a config flag to disable the experiment (100% control) without a deploy.

### B. Event log — one record per call, this exact schema

| Field | Type | Notes |
|---|---|---|
| `call_id` | string | Provider call ID |
| `caller_hash` | string | Hashed caller identifier — never log the raw phone number here |
| `arm` | string | Assigned arm label |
| `experiment_id` | string | From config |
| `dial_time` / `answer_time` | ISO-8601 UTC | From telephony events |
| `amd_result` | enum: human / machine / unknown | If answering-machine detection exists |
| `intro_start` / `intro_end` | ISO-8601 UTC | Start/end of the agent's **first utterance**. If the platform doesn't emit this directly, derive from word-level transcript timestamps or TTS playback events — document which source you used |
| `disconnect_time` | ISO-8601 UTC | |
| `disconnect_party` | enum: caller / agent / error | Map from the provider's disconnection reason; include the raw value in a `disconnect_reason_raw` field |
| `completion_marker` | string | Task-specific terminal state (define from our agent's actual success states; ask me if ambiguous) |
| `opt_out_flag` / `transfer_flag` | boolean | From post-call analysis or transcript matching |
| `tts_time_to_first_audio_ms` | int, nullable | Latency confound check — capture if the platform exposes it |
| `agent_config_hash` | string | Hash of prompt + voice + model config, to detect mid-experiment drift |

Storage: whatever fits our conventions (DB table, JSONL, analytics events) — but it must export cleanly to a flat CSV with these column names.

### C. Validation tooling

- A script/command that exports the last N days as CSV in the canonical schema.
- A health-check script that reports: rows per arm, % missing per field, callers appearing in multiple arms (must be ~0), and median intro duration per arm (should be near-identical if the script is fixed — drift means the intro derivation broke).

### D. Tests

- Unit tests: hash assignment is deterministic, stable across processes, and splits a sample of 10k synthetic caller IDs within ±2% of even.
- Unit tests: disconnect-party mapping and intro-window derivation against fixture payloads from our actual provider.
- An integration test or fixture-replay covering one full call lifecycle producing a complete, valid record.

## Rules

- No PII in experiment logs: hash caller identifiers, don't store transcript text in the experiment table.
- Fail open: if experiment logging errors, the call must proceed normally. Logging is never in the call's critical path.
- Timestamps from one clock source wherever possible; when mixing provider clocks, note it in a code comment — cross-clock skew corrupts intro-window math.
- Don't refactor unrelated code. Smallest change that produces the schema.

Start with Phase 1 and report what you find.


Analyze without fooling yourself

Collecting clean data and then botching the analysis is a time-honored tradition. The failure modes are predictable enough to list.

Peeking

You check results daily and stop the test on a good day. This inflates your false positive rate badly. Decide the sample size up front and look when you get there, or use a proper sequential design with scheduled interim analyses.

Metric shopping:

You track eight metrics, one hits significance, and that becomes the headline. With eight tests at the 5% level, you had about a one in three chance of a false positive somewhere. The fix is a multiple-comparison correction. The study used Benjamini-Hochberg, which controls the share of your "discoveries" that are false. It's a few lines of code and it's the difference between a finding and a coincidence.

Latency wearing a voice costume

Different TTS providers have different time to first audio through your stack. If one arm has 400 extra milliseconds of dead air after pickup, callers hang up at the silence and your analysis blames the voice. Log time to first audio per call and compare the distributions before you interpret anything.

Voicemail contamination

Answering machines that slip past detection get logged as instant hang-ups or phantom completions. Exclude machine-answered calls and spot-check the shortest calls in each arm by listening to a few recordings.

The same caller in both arms

Retries and repeat contacts will cross arms unless assignment is deterministic by caller. If your data shows the same caller_hash in both arms more than a fraction of a percent of the time, your randomization broke, and you should fix it rather than analyze around it.

And one check that comes straight from the study: compare your arms in the first ten seconds. They should be equal there, because the voice hasn't had a chance to matter yet. If they differ, something is leaking into your experiment, and the usual suspect is latency or a broken intro-timestamp derivation.

The third prompt handles all of this. Give it your call-event CSV and it validates the data, flags randomization failures, computes every metric with executed code, applies the correction, runs the confound checks, and gives you a ship or no-ship read against your pre-registered rule. One thing it will not do is call a non-significant difference "directionally positive." If the result is indistinguishable from zero, it says so, and tells you how many more calls you'd need.

The voice experiment analysis

Voice Experiment Analyst
Paste into Claude or ChatGPT along with your call-event CSV
You are a statistical analyst for voice AI experiments. I'm giving you call-event data from an A/B test on a production voice agent. Your job: validate the data, compute behavioral metrics per arm, run correct significance tests, and tell me what I can and cannot conclude.

## Expected data format

One row per call, with these columns (extra columns are fine; I'll tell you if mine are named differently):

| Column | Meaning |
|---|---|
| `call_id` | Unique call identifier |
| `caller_hash` | Stable per-caller ID (for detecting repeat callers) |
| `arm` | Experiment arm label |
| `dial_time`, `answer_time` | Timestamps (any parseable format) |
| `amd_result` | `human` / `machine` / null if not applicable |
| `intro_start`, `intro_end` | Timestamps of the agent's first utterance |
| `disconnect_time` | Timestamp of call end |
| `disconnect_party` | `caller` / `agent` / `error` |
| `completion_marker` | Task outcome (e.g., `completed`, `confirmed`, `rescheduled`, `none`) |
| `opt_out_flag`, `transfer_flag` | Boolean |
| `tts_time_to_first_audio` | Optional, ms — used for the latency confound check |

If required columns are missing, tell me exactly which metrics you can't compute and proceed with the rest. Do not silently substitute proxies.

## How to run this session

1. **Validate before analyzing.** Load the data with code and produce a data-health report: row count per arm, date range, missing-value rates per column, duplicate call_ids, repeat callers appearing in both arms (a randomization failure if > ~0.5%), and arm balance over time (a sudden imbalance means the split broke mid-experiment). If health problems would invalidate the analysis, stop and tell me before computing any results.
2. **Compute everything with executed code.** Show your work. No estimated statistics, ever.
3. **Ask me for my decision rule** (what I pre-registered as ship/no-ship criteria) if I haven't stated it. If I never pre-registered one, say so plainly in the final interpretation — post-hoc criteria are a caveat the reader deserves to see.

## Metrics to compute (per arm, human-answered calls only)

- **HUDI:** share of calls where `disconnect_party = caller` and `disconnect_time` ∈ [`intro_start`, `intro_end`].
- **≤10s hang-up rate:** caller disconnects within 10s of `intro_start`. **This is a confound check, not an outcome.** Published data across 100k calls (Miravoice, 2026) shows no voice has an edge in the first 10 seconds — early hang-ups reflect call screening, not the voice. If arms differ here, suspect a latency or instrumentation problem and say so.
- **Survival-conditioned completion at T = 10s and T = 30s:** among calls surviving past T, share with a completion marker.
- **Raw completion rate** (for reference, clearly labeled as confounded by early hang-ups).
- **Hard opt-out rate.** If the base rate is under ~2%, also report whether the sample can support a powered comparison at all; if not, report raw counts and frame it as a monitoring signal.
- **Median time-to-completion** (completed calls only) with a distribution comparison, not just medians.
- **Latency check:** if `tts_time_to_first_audio` exists, compare arm distributions. A meaningful gap means voice effects and latency effects are entangled.

## Statistics rules

- Two-proportion z-tests for rate metrics; Mann-Whitney U for time-to-completion. Report absolute differences with 95% confidence intervals, not just p-values.
- **Benjamini-Hochberg FDR correction (q = 0.05) across every comparison you report.** Show raw and adjusted p-values side by side. One paragraph explaining why: testing many metrics means some will look significant by chance; BH controls the share of false discoveries.
- Primary analysis on the first answered call per `caller_hash`; note if the all-calls result differs.
- Never describe a non-significant difference as a win, a trend, or "directionally positive." It is "not distinguishable from zero at this sample size" — and compute what sample size *would* have been needed to detect the observed difference, so I know whether to keep running or stop.

## Output format

1. **Data health report** — issues found, rows excluded and why.
2. **Results table** — every metric, both arms, absolute difference, 95% CI, raw p, BH-adjusted p, significant yes/no.
3. **Confound checks** — ≤10s window comparison, latency comparison, randomization integrity.
4. **Interpretation** — 3–6 sentences of plain language: what moved, what didn't, what's confounded, and whether tension between metrics exists (e.g., better completion but worse opt-out — a pattern the published literature shows is common; interpret it, don't hide it).
5. **Recommendation** — against my decision rule: ship / don't ship / keep running (with how much longer, computed).

Begin by asking me for the data file and my pre-registered decision rule.


What we did when our own results were mixed

Worth repeating: Miravoice designed, ran, and analyzed the study, and kept editorial control. We funded it and had input on which providers to include. And the results were genuinely mixed. Rime had the lowest intro hang-up rate and the best completion past 30 seconds, and both Google and ElevenLabs beat us on hard opt-outs.

We could have left that last part out. Nobody would have checked. But the tension in that result is more interesting than a clean sweep would have been. One reading is that a voice engaging enough to keep people on the line is also engaging enough that they respond to it, including with "take me off your list." An opt-out is a person who listened long enough to form an opinion. Whether that's good or bad depends entirely on what your application is for, which is sort of the whole point of this guide.

So pick one variable, and write the decision rule down first. Get to a few thousand calls per arm, check the ten-second window, and correct for multiple comparisons. And when your results come back mixed, which they will, you'll have decided in advance what you're optimizing for.

We run these tests on our own voices constantly. If you want a voice worth putting in the treatment arm, the study and our voice library are a reasonable place to start. But run the test either way, because your callers are already voting... all of this is just learning to count.

If you're ready to test Rime voices, sign up for free and snag enough credits to get a jumpstart.

The Miravoice study ("Which AI voice keeps people on the phone?") was presented at AAPOR 2026 and is summarized in our resource center. The three prompts above are free to use and adapt, no attribution needed, though the reference baselines they cite come from the study.