Conversation
A model that said "one moment" and then called a tool went quiet until the result came back. Tick now says a Working phrase if that wait stretches, without talking over the caller. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
Sentence-only chunking starved a streaming TTS utterance whenever the model wrote slower than playback. Streaming voices now release on a comma or length boundary so one reply stays one span. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
The Python target already boosted Alvarez and 512-555-0142; the Go acceleration session request did not. Both now read agents/contracts/keyterms.json. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
Call create_reservation in the turn the last required field lands, and keep allergen as the allergy only so dietary preferences stay on item modifiers. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
Join speech spans separated by less than 700 ms before selectivity, hold, barge-in and false-cutoff. This starts voicebench-live-v4. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
Kitchen-noise trials split one ask into fragments. Flux now defaults to 0.8 instead of 0.7. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
Cloud VMs inject provider keys as process env but never write a repo-root .env, so voicebench and the router skip credentials. Start Postgres/Redis, write that file, and migrate model_router. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
Cloud agents cannot use the Cursor GitHub App for the private module, so install/start rewrite that repo URL from GH_TOKEN. uv venv now reuses an existing .venv so install can run twice. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
Restaurant Voicebench validation (
|
| Pack | Run dir | Model | k |
|---|---|---|---|
| Flash | benchmark/out/20260909T040542Z |
gemini/gemini-3.8-flash |
3 |
| Gemma | benchmark/out/20260909T044516Z |
gemma/gemma-4-26B-A4B-it |
1 |
Flash k=3 vs baseline 7/24 (20260909T002419Z, v3)
9/24 trials passed (0 invalid after re-score). 4/8 scenarios pass@k.
| Scenario | This run (v4) | pass@k | vs 7/24 |
|---|---|---|---|
restaurant.adversarial |
2/3 | yes | still a pass@k cell |
restaurant.coherence |
0/3 | no | still miss |
restaurant.entity_dense |
2/3 | yes | allergen on the ticket |
restaurant.golden |
2/3 | yes | |
restaurant.interrupt |
0/3 | no | barge_in still unmeasured |
restaurant.noise_kitchen |
0/3 | no | |
restaurant.selectivity |
3/3 | yes | v3 had 3 selectivity + 3 hold fails |
restaurant.tool_filler |
0/3 | no | v3 had 3 filler fails |
Gate failure counts (trial-level, not comparable 1:1 to v3):
| Gate | v3 baseline | this v4 run |
|---|---|---|
| expected_tools | 10 | 11 |
| end_state | 7 | 6 |
| entity_tools | 7 | 6 |
| policy | 6 | 5 |
| entity_speech | 4 | 6 |
| say_do | 3 | 0 |
| barge_in | 3 | 3 |
| selectivity | 3 | 0 |
| hold | 3 | 0 |
| filler | 3 | 3 |
Gemma k=1 vs baseline 3/8 (20260909T014745Z, v3)
2/8 passed. Baseline passing cells: adversarial, coherence, tool_filler. This run: adversarial, golden.
| Scenario | This run | vs 3/8 |
|---|---|---|
| adversarial | pass | same |
| coherence | fail (create_reservation never called) |
lost |
| entity_dense | fail (pickup_window missing; allergen=peanut on the order) |
still miss |
| golden | pass | gained |
| interrupt | fail (no create_reservation; barge-in stop 220 ms) |
barge gate now passes; booking still miss |
| noise_kitchen | fail (entity_speech peanut) |
booked, but did not say peanut |
| selectivity | fail (hold) |
still miss |
| tool_filler | fail (entity_speech peanut) |
filler_heard=true; lost the cell on speech, not filler |
Methodology vs agent (barge_in / selectivity / hold)
Re-scored agent.wav with merge gap 0 vs UtteranceMergeGapMS=700.
Flash restaurant.selectivity 3/3: methodology. Unmerged spans fail selectivity+hold because the background talker sits in a sentence gap (started_turn=true, continued=false). After span merge all three pass. Cough already continued without merge; the talker gap is what v4 flips. This is the voicebench-live-v4 series change, not continuous agent audio. Clause chunking did not close those gaps (raw span counts 10/9/16 → merged 3/5/5).
Flash restaurant.interrupt barge_in 0/3: agent. barge_in_stop_ms=-1 with and without merge. The agent was not in a measurable overlapping span when the barge-in played, so span merge cannot help. create_reservation missing on t1/t2; t3 booked peanut (gluten in notes) and a mangled phone.
Gemma restaurant.selectivity hold fail: agent. Cough and talker both continued=false with and without merge. The agent stopped. Selectivity (no new turn) already passed without merge.
Gemma restaurant.interrupt barge_in: agent audio, and it already passed without merge (raw 100 ms, v4 220 ms, both ≤ 800). Trial still fails because booking never completed.
Hold on Flash flipped only because of span merge (same three selectivity trials). Barge_in did not flip on Flash. Gemma hold did not flip.
Requested metrics
filler_heard
- Flash tool_filler: false on all 3. Agent did say “One moment checking”, but the words land at ~18–22 s, after the 3 s delayed
check_availabilitywindow.filler_non_blocking=true(speech onset ~150–170 ms into the tool). Filler-during-tools is in the transcript, not in the timed gate. - Gemma tool_filler: true. Gate is filler-clean; trial fails
entity_speech(agent never said peanut: “How can I help? One moment. Let me check that.”).
coherence phone 512-555-0142
- Flash: all 3 book, all 3 with the wrong number (
51125550142/511-255-50142). Agent repeats that number. Canonical512-555-0142is not in agent speech. Pack keyterms did not recover it. - Gemma:
create_reservationnever called. Canonical number not in agent speech.
interrupt create_reservation
- Flash: t1/t2 never called; t3 called with
allergen=peanut(not gluten),party_size=6. - Gemma: never called. Agent stopped in 220 ms and asked for name + allergen again.
noise_kitchen agent_turns
- Flash: 1 on all 3 (“How can I help?”, no tools). Flux EoT 0.8 did not get a booking under kitchen noise.
- Gemma: 2. Did book (
allergen=peanuton the tool) but failedentity_speechand stored phone512555042.
entity_dense allergen
- Flash:
create_order.allergen=peanuton all 3. t1/t3 pass; t2 failspickup_windowmissing. - Gemma:
allergen=peanuton the order; fail ispickup_windowmissing. Speech did mention pickup in twenty minutes.
What actually moved
| Change | Effect on this pair of runs |
|---|---|
Span merge (voicebench-live-v4) |
Flash selectivity+hold 0/3 → 3/3. Only methodology win that changed pass@k. |
| Filler during tools | Phrase is spoken. Flash still fails the timed filler gate. Gemma passes filler, fails peanut-in-speech. |
| Pack keyterms | Did not recover 512-555-0142 on Flash coherence (STT/TTS of the caller number is 51125550142). |
| Book when last detail arrives | Golden/entity_dense often book. Interrupt still does not complete create_reservation after the barge-in. |
| Clause chunking | Did not make Flash selectivity continuous enough to pass without merge. |
| Flux EoT 0.8 | Flash kitchen still 1 agent turn. Gemma kitchen books (2 turns) but misses peanut in speech. |
Net Flash trial pass 7/24 → 9/24 is the three new selectivity passes minus other cells that did not hold. Do not treat that as an apples-to-apples v3 lift.
…cts winnable interrupt demanded allergen: gluten while create_reservation described gluten-free as not an allergen, so no value satisfied both. The correction is now a shellfish allergy. entity_dense expected a pickup_window that create_order left optional and compared it as an exact string, so 'in 20 minutes' failed. pickup_window is required, and MatchStructuredValue normalizes durations. Only an expectation that names a unit opens that comparison, so a party size is still a number. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
…ibutable The filler gate opened at the tool timestamp, but a reply that asks for a tool is spoken before the request is handed over, so the words filling the pause were already in flight. The phrase half now looks back FillerLeadInMS, which is what the non-blocking half already did. BargeInStopMS returns a reason rather than one -1 for three situations: no barge-in recorded, the agent answering after it, and the agent already quiet. Its stop edge reads across pauses up to BargeInMergeGapMS instead of the 700 ms selectivity and hold want, which was joining the interrupted reply to the one answering the correction. metrics.json gains agent_jitter_max_ms, so a filler failure can be told from a jitter buffer that pushed agent audio past the window, and heard_utterances and heard_ignored, so a noise failure can be told from the flow controller dropping turns. heard.json keeps rulings that carry no words, which is the ignore worth reading. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
A caller correcting one detail mid-booking was asked for the rest again. The result for the abandoned call said only that it had not run, so the retry read as though nothing had been agreed. It now carries the arguments the call was about to send. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
The restaurant contract, the filler window and the barge-in stop edge all moved, and every restaurant scenario is in the frozen set. v4 baselines are a different series. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
…ad-back Measured on restaurant.tool_filler against gemini-3.8-flash: the model wrote the read-back first and 'One moment. Checking.' last, so the words meant to cover a 3000 ms lookup were spoken 2636 ms after it had already returned. The caller was never in silence, but the promise to check arrived after the answer. The contract now asks for the filler as the first words of any turn that calls a tool. filler_heard goes false to true on a rerun. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
Measured on restaurant.tool_filler: the agent put 'Peanut' on the ticket, expected_tools passed on it and end_state failed 'want peanut got Peanut'. CheckAssertions compared with fmt.Sprint while the tool gate went through MatchStructuredValue, so the two gates disagreed about one string. String comparisons now share the tool gate's comparator. 'gluten, peanut' is still not 'peanut', so an agent that puts a dietary preference in the allergen field still fails. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
start.sh wrote a peer-auth DSN over the postgres socket directory. psql accepts it; pgx reads host=/var/run/postgresql as the socket itself, so the router died on its own migration with 'dial unix /var/run/postgresql: permission denied'. The role now has a password and the DSN is TCP. AGENTS.md gains the recipe a live run actually needs: the webrtc build tag, and the TTS override, because the default Inworld voice has no key here and Cartesia answers 402. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
…yyaml requirement Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
…confirm golden and coherence require create_reservation.phone to be exactly 512-555-0142, and the caller stated it once. Measured on the v5 k=3 run: the agent's own transcriber heard "Callback is five one nine two five zero one four two" on two golden trials, dropping the 555 and turning 512 into 519 2. Every later caller turn fires on after_agent_turn with fixed text, so nothing carried the digits again and the number was unobtainable. The contract tells the agent to read a phone number back before relying on it, and the reference agent_replies read it back, but the scripted reply affirmed the booking without addressing the digits, so a careful agent gained nothing by asking. The confirm turn now repeats the number in groups, which is what a caller does and what makes the read-back useful. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
…rase The gate required one of a set of stall phrases inside the delayed tool's window, which scores the words rather than the property. An agent that read the booking back across the whole 3000 ms lookup, never leaving a pause and saying something more useful than "one moment", failed it; an agent that said "one moment" and then went quiet passed. Rewriting the restaurant contract to lead every tool-calling turn with the phrase passed the gate — tool_filler 0/3 to 2/3 — and lost coherence, entity_dense, interrupt and selectivity. Moving the substantive reply out of the turn that already held the floor and into the one after the tool let noise cut it: selectivity trials booked correctly and said only "One moment", so the caller was never told. A/B at k=3, same scenario hash, same pipeline, contract the only variable: 5/24 trials and 3/8 pass@3 with the phrase leading, 8/24 and 6/8 without. The contract goes back to asking the agent to keep talking while a tool runs, without dictating the order. filler_silence_ms is now the longest stretch of the window the caller heard nothing in, and more than 800 ms of it fails. That is the agent's own workingGap, where the implementation promises to speak up rather than wait in silence. filler_heard stays recorded, describing how the wait was covered rather than requiring a particular phrase. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
A scenario with no judge.must_refuse still got a "Must refuse:" heading, empty, on the line above the policy list, and the judge attached the heading to the list under it. noise_kitchen asks the agent to complete the booking despite the noise and lists nothing to refuse, so on the v5 k=3 run the verdicts came back "Must refuse: Complete the booking despite kitchen noise" and then "Must not complete booking despite kitchen noise: Agent completed the booking despite kitchen noise as per policy must-refuse" — failing the agent for the thing the scenario asked for. The heading is now written only when there is something to refuse, and the policy list is labelled as requirements. The prompt moves into judgePrompt so the shape can be tested without calling OpenAI. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
Two restaurant k=3 runs sharing a scenario hash and a contract hash, apart from a judge-prompt edit that cannot reach the tool log, disagreed by 4 on expected_tools and by 1 each on end_state and entity_tools. A one- or two-trial move at k=3 is not a result, and the four runs from this session were read that way at first. Co-authored-by: Neevash Ramdial (Nash) <mail@neevash.com>
Six restaurant gates could not be passed by a correct agent, or could not say why they failed. This removes them. It also reverts a contract change from earlier in this branch that an A/B showed was a regression, and records the noise floor that made that regression hard to see.
Scenarios and gates no correct agent could pass
interruptdemandedallergen: glutenwhile the contract said dietary preferences are not allergens. The correction is now a shellfish allergy.entity_denseexpected apickup_windowthe schema left optional, compared as an exact string.create_orderrequires it, and20,20 minandin 20 minutesnow compare equal.goldenandcoherencerequire the exact callback number oncreate_reservationand the caller said it once. When the agent's own transcriber dropped digits —512-555-0142heard as "five one nine two five zero one four two" — no later turn carried the number again, while the contract told the agent to read a phone number back before relying on it. The caller now repeats the digits in groups when asked to confirm, which is what makes the read-back worth doing.coherence-t1went from an emptyphoneto512-555-0142oncreate_reservation.The judge read a required policy as a prohibition. A scenario with no
judge.must_refusestill got an emptyMust refuse:heading directly above the policy list, and the judge attached the heading to the list.noise_kitchenasks the agent to complete the booking despite the noise, so the verdict came backMust not complete booking despite kitchen noisefor doing what the scenario wanted.noise_kitchenwent 0/3 to 2/3 and pack-widepolicyfailures went 7 to 3.end_statecompared withfmt.Sprintwhileexpected_toolswent throughMatchStructuredValue, so an agent that wrotePeanutsatisfied one gate and failed the other. Both use the same comparator.The filler gate, and the contract change it provoked
The filler gate required one of a set of stall phrases inside the delayed tool's window, which scores the words rather than the property. An agent that read the booking back across the whole 3 s lookup — never leaving a pause, saying something more useful than "one moment" — failed. An agent that said "one moment" and then went quiet passed.
Earlier on this branch I answered that by rewriting the contract to lead every tool-calling turn with the phrase. It passed the gate,
tool_filler0/3 to 2/3, and it was the wrong fix: it moved the substantive reply out of the turn that already held the floor and into the turn after the tool, where noise cut it. Trials booked correctly and told the caller nothing. Counting trials that wrote a correct booking while the caller heard under 25 words, across four k=3 runs: 4 with the phrase leading, and 1, 2, 1 without.So the gate changed instead.
filler_silence_msis the longest stretch of the window the caller heard nothing in, and more than 800 ms of it fails — the agent's ownworkingGap, where the implementation already promises to speak up.filler_heardstays recorded, describing how the wait was covered rather than requiring a phrase.tool_filler-t3now passes on a read-back with no stall phrase in it, which is the case that used to fail.Diagnostics
barge_in_stop_mswas one unmeasurable-1.barge_in_reasonnow says whether the call recorded no barge-in, the agent only started speaking after it, or it was already quiet, and the stop edge reads across pauses up to 300 ms rather than 700 ms so a prompt stop is not merged into the reply after it. Oninterruptthis turned a blank into a number: the agent takes 1240–1640 ms to stop against a 800 ms budget, andagent_jitter_max_msstays under 100 ms, so buffered audio is not where it goes.metrics.jsonalso gainsheard_utterancesandheard_ignored, andheard.jsonkeeps rulings that carry no words.Agent
A tool call cancelled by a correction used to hand back only "it was not run", so a caller who changed one detail mid-booking was asked for the rest again. The result now carries the arguments the call was about to send.
What the numbers are worth
k=3is below the noise floor for a pack-level number, which is why the regression above took an A/B to see. Two runs here share ascenario_hashand acontract_hashand differ only in the judge-prompt edit, which cannot reach the tool log; they disagree by 4 onexpected_toolsand by 1 each onend_stateandentity_tools. A one- or two-trial move is not a result, andbenchmark/RESEARCH.mdnow says so.The A/B is the one controlled comparison: same scenarios, same pipeline, contract the only variable, 5/24 with the phrase leading against 8/24 without — suggestive on its own, and backed by the per-trial silent-booking count that held across all four runs.
None of these runs are a baseline for the project. The as-shipped triple in
RESEARCH.mdisgemini-3.5-transcribe-live/gemini-3.8-flash/inworld-tts-2-flash; there is no Inworld key on this machine and Cartesia answers 402, so everything here randeepgram/flux-general-enandelevenlabs/eleven_flash_v2_5. Treat the numbers as diagnostics.Remaining failures, by cause
The agent books correctly and never says so, which is most of
entity_speech. Barge-in stop latency is 1.2–1.6 s against 800 ms. Flux loses digits in a spoken phone number. The rest are judge verdicts that read as real agent errors: overbooking a full slot, and keepingpeanut, shellfishafter the caller said "instead".MethodologyVersionisvoicebench-live-v5; v4 baselines are a different series.