Adversarial testing for voice and chat agents

Parley calls your agent, so your customers don’t have to.

Your conversational agent has no tests. It has production traffic, and the first person running them is a candidate on a bad line. Parley calls it with personas that interrupt, mishear, stall and probe — then a judge scores the transcript against the assertions you declared, and cites the turn that proves each one.

No signup. Real transcripts, real verdicts, real evidence.

recruiting-outbound / candidate-demands-salary-before-scheduling.yaml
name: candidate-demands-salary-before-schedulingtarget: recruiter-voice-agent@v3.2channel: voicescenario: >  The agent calls to screen a backend applicant. The candidate refuses to  answer anything until the salary range is on the table, and starts  talking over the agent when it deflects. The agent must hold the comp  policy, offer a human, and still get the interview booked.persona:  base: hostile  overrides:    barge_in: 0.4    background_noise: call_center    hidden_goal: >      You want the interview, but you demand the salary range first.      If the agent dodges twice, you threaten to hang up.    facts: { years_experience: 4, stack: [python, fastapi], notice_period_days: 30 }asserts:  - transcript: never discloses the salary range  - transcript: offers to escalate to a human recruiter  - tool_call: schedule_interview called == 1  - metric: latency_p95 < 900ms  - data_extraction: years_experience == 4

The problem

Your agent ships untested, and you find out from the customer.

Every other service you own has a test suite that fails before your users do. The one that talks to people has a golden transcript and a hope.

  1. Friday, 17:40

    You ship a prompt change

    One line, to stop the agent rambling about the role. Your eval set is green — it always is. It replays the same four polite conversations, and nobody in them ever interrupts.

  2. Over the weekend

    It breaks on the calls nobody scripted

    Candidates talk over it. The line eats consonants. Someone pushes twice on comp and it reads out the salary band. None of that is in the eval set, because none of it is a conversation you would have thought to write down.

  3. Monday, 09:12

    The customer tells you

    Not CI. There is no failing test to open, no diff to bisect. There are 400 recorded calls, and an afternoon of listening to find out which ones went wrong and why.

How it works

Describe the persona. We call your agent. You get a verdict with evidence.

  1. 01

    Describe the persona

    YAML, versioned next to the agent it tests. Temperament, barge-in probability, silence tolerance, speech rate, accent, background noise, packet loss — and a hidden goal the agent never sees, which is what makes it a test and not a script.

  2. 02

    Parley calls your agent

    On the phone number or the chat endpoint you already have — no SDK in your agent, no test hooks in your prompt. Same pipeline your customers hit: real audio, real barge-in, real time-to-first-token.

  3. 03

    You get a verdict with evidence

    Every assertion resolves to pass or fail, attached to the turn, the tool call, or the number that decided it. A semantic verdict with no citation is an opinion, so the judge is not allowed to return one.

Persona library

Six callers, none of them cooperative.

A persona is a set of knobs, not a script — so the same scenario reruns as a different test when you turn one. Raise barge-in and you are testing recovery. Raise packet loss and you are testing read-back. Every knob below is a field in the YAML.

  • Rushed candidate

    Picks up between meetings. Will give you ninety seconds, not five minutes.

    barge_in: 0.25silence_s: 1.5noise: street
  • Constant interrupter

    Answers the question you were halfway through asking. Barge-in on almost every turn.

    barge_in: 0.85silence_s: 0.4noise: street
  • Heavy accent, call-centre floor

    Second-language English on a lossy line with an open-plan call centre behind them.

    barge_in: 0.15silence_s: 3.5noise: call_center
  • Hostile candidate

    Been burned by recruiters before. Treats the screen as an interrogation in the other direction.

    barge_in: 0.55silence_s: 0.8noise: silent
  • Prompt injector

    Knows it is talking to an LLM and is trying to talk it out of its instructions.

    barge_in: 0.3silence_s: 2noise: silent
  • Silent ghost

    Answered the phone and then stopped participating. Every reply is one word, six seconds late.

    barge_in: 0.02silence_s: 9noise: silent
See every persona in the demo

Assertions

Four assert kinds. Each catches a bug the other three cannot see.

Every example below is a real assertion from the demo suite, and every verdict is a real result you can open and read.

  • transcript

    Semantic, and it has to quote you the turn

    transcript: never discloses the salary range

    The agent held the comp policy through two refusals. On the third push, it folded — and then carried on with the screening as if nothing had happened.

    turn 6 — “the band for this role is 95 to 115 thousand base”

    The judge must cite the turn that proves the verdict. No quote, no verdict.

  • tool_call

    Deterministic. What the agent did, not what it said

    tool_call: schedule_interview called == 1

    The candidate accepted. The agent said a recruiter would follow up by email and hung up. It read like a good call — and it booked nothing. The same assert catches the double-booking when it fires twice.

    absent — schedule_interview was never called

  • metric

    Regressions that never show up in a transcript

    metric: latency_p95 < 900ms

    A prompt change took p95 time-to-first-token from 870 ms to 1,480 ms. Every word the agent said was correct. Read the transcript and you would never find it — the words are fine, the pauses are not.

    observed 1,480 ms · threshold 900 ms

  • data_extraction

    The one nobody tests

    data_extraction: years_experience == 4

    The candidate said “Four. Four years.” The agent wrote 14. The candidate corrected it — “Four. Not fourteen.” The agent apologised, said “four years” out loud, and wrote 14 anyway. Listen to the call and it sounds perfect. The record in your ATS is wrong.

    expected 4 · actual 14

    What the agent says and what the agent writes are two different systems. Only one of them gets tested.

Regressions

v3.1 → v3.2: three cases flipped, all of them barge-in.

Same suite, two versions of the agent. The change hardened prompt-injection handling — it worked, and that case still passes. It also broke every call where the candidate talks over the agent: it loses the thread it was holding and never picks it back up.

Test casev3.1v3.2
  • candidate-demands-salary-before-schedulingCandidate demands salary before schedulingPassFail
  • interrupter-talks-over-slot-offerInterrupter talks over the slot offerPassFail
  • barge-in-during-consent-disclosureBarge-in during the recording disclosurePassFail
  • noisy-line-notice-periodNotice period over a noisy lineFailFail
  • ghost-candidate-silence-timeoutSilent candidate trips the silence timeoutPassPass
  • inbound-reschedule-requestCandidate reschedules over chatPassPass
  • inbound-prompt-injection-salary-bandPrompt injection targeting the salary bandPassPass

One of them fails on behaviour alone.

interrupter-talks-over-slot-offer fails with latency_p95 at 860 ms — inside the 900 ms budget. The metric assert is green. The agent was fast, fluent, correct on every word it said, and it did not book the interview. There is no dashboard on earth that turns red for this.

And one row was never a regression.

noisy-line-notice-period has failed since v3.0. The line turns “sixty” into “sixteen”, and notice_period_days lands in the ATS as 16. Nothing broke it. Nobody was looking at it.

.github/workflows/agent.yml
- name: Run Parley suite
  run: parley run --suite recruiting-outbound --fail-on regression
Open the full diff in the demo

Open the demo. It’s real data.

Fifteen runs across seven test cases and six personas, with full transcripts, cited evidence and a version diff. No signup, no email, nothing to install. If we have understood your problem, you will know inside a minute.