● Open sourceVoice-Agent Latency Harness

dialtone.

Measure the audio, not the API

Every voice-AI vendor publishes a latency number. All measured differently, none including the phone network. dialtone records what it sent and what came back and recovers the timing from the audio itself.

Pythonnumpy33 testsSeeded
Language Python 3.9+Tests 33License MIT
dialtone — compare
$ python examples/compare_agents.py
› snappy 680ms p50 · 41 cut off
› balanced 1209ms p50 · 23 cut off
› patient 1708ms p50 · 0 cut off
✗ phone-tree 0% barge-in
01 The problem

The number the vendor reports is not what the caller heard.

An SDK will tell you it emitted its first audio chunk 340ms after it decided the caller stopped talking. That is real, and it excludes the inbound jitter buffer, the endpointer's own decision delay — usually the largest term — loss concealment, carrier codec delay, and the playout buffer.

02 The hard part

Where the work actually was.

So do not ask. Recovering true end-of-speech to first-audio latency means normalised cross-correlation on the recorded audio, normalised per window rather than globally — otherwise a loud unrelated burst outscores a quiet true match and the alignment lands on whatever was loudest.

1

Timing from audio

FFT numerator, prefix-sum denominator. O(n log n) rather than O(n·m).

2

Ambiguity detection

A periodic signal matches at every multiple of its period. Peak sharpness catches it instead of reporting confident nonsense.

3

Real failure modes

"Hello?… hello?", a phone number in chunks, an address over a compressor, an answering machine.

4

Bursty loss

A codec conceals one lost packet well and five in a row not at all. Independent loss at the same rate is far too forgiving.

5

Concealment, not silence

Silence-filling would make every dropout look like end-of-turn, and the harness would measure its own naivety.

6

Control tests

Inject a known latency, assert the harness recovers it. Without that, nothing it reports means anything.

03 Measured

What it actually does.

The control tests — the harness measuring a latency it injected on purpose:

injected 700 + 350 ms → measured 1050.4 ms injected 300 + 200 ms → measured 500.3 ms injected 1200 + 600 ms → measured 1800.5 ms barge-in stop, injected → recovered 80ms · 150ms · 400ms all within 80ms network profiles perfect · lte · poor-mobile bluetooth-car (180ms, codec not distance)
04 Engineering

How it's built.

LanguagePython 3.9+, numpy only
Alignmentnormalised cross-correlation
Signalssynthesised, ground truth by construction
Networkseeded, per-packet, bursty loss
Agent boundaryone method: audio in, audio out
Tests33, no telephony required
05 Honestly

What it doesn't do.

No real telephony leg yet — everything here is a simulated media path. The timing recovery is designed for real recordings, but placing actual PSTN calls costs money per minute and is not wired up. That is the obvious next piece. No ASR scoring either: this measures timing and turn-taking, not comprehension.

Want a closer look?

The README goes deeper, and the tests are the honest documentation. Happy to walk through the trade-offs.