All demosVoice turn: answer before the silence

Voice turn: answer before the silence

A voice assistant that routes every partial transcript in about 40 ms and answers the moment the request is actionable, instead of waiting out a fixed silence timeout, then stops talking when the speaker interrupts.

/classify/yes-no

How it works

The original macOS experiment asked its judge three things on every partial transcript: turn_complete (has the speaker finished a complete request?), intent (what do they want?) and, while the assistant spoke, is_barge_in (is the speaker interrupting?). Three more questions picked a slot value — the timer duration, the room, the message recipient — out of candidates a regex had already found in the transcript.

This page keeps the script, the timings, the policy and the panels, and sends two questions instead of six.

  • Every coalesced partial, while listening: one POST /classify with the raw transcript so far and the original eight intent rubrics as described labels — set_timer "Start a timer, alarm or countdown for a duration", weather, play_music, lights, send_message, question, chit_chat, and incomplete "Too little said so far to tell what the speaker wants". The answer gives the live intent and its probability.
  • Every coalesced partial, while the assistant speaks: one POST /yes-no over the text Assistant is saying: "…" Speaker says: "…" with the statement The speaker cuts the assistant off., when_true "the speaker tells the assistant to stop, wait, cancel or be quiet" and when_false "the speaker lets the assistant finish: a filler sound, a thanks, or nothing at all". Measured on this script: "stop stop" 0.92, "ok thanks" 0.09.
  • At fire time only, and only when the intent takes a slot: one POST /classify whose labels are the regex candidates plus none, each with a one-line description ("This phrase is the duration the speaker wants the timer set for" / "None of the phrases is the timer duration"). The model selects; the code copies the label verbatim. Nothing is generated.

Batching and cadence. At most one call is in flight and no two calls start closer than 420 ms apart, so a burst of fast words coalesces into one question about the newest transcript; answers that arrive out of order are dropped by sequence number. That is about one call per second while someone speaks, plus one at the end of a turn — roughly 55 calls for the ten-utterance script. The shared demo key allows 2 calls per second across all visitors, so the session also stops itself after 60 seconds or 80 calls.

Code owns every threshold. The model returns a label and a probability, never a decision. The rules, ported from TurnPolicy.swift: answer when the intent probability is at least 0.85, the detail that intent needs has been said, and the speaker has paused for 250 ms; stretch the timeout to 2,500 ms while a needed detail is still missing; use the plain 1,000 ms silence timeout otherwise; stop the reply when barge-in reaches 0.80.

What changed from the original, and why. The original's turn_complete question does not survive the port. decision-machine-1 scores meaning, not grammar: asked whether a transcript is a finished sentence, it answered about 0.5 for "set a timer for" and for "set a timer for ten minutes" alike, under every statement and hint pair we measured. So the completeness signal here is the pair the model does answer well — what the speaker wants, and whether the detail that request needs has been said — and the timeline plots that probability instead. It produces the same behaviour on this script: the assistant answers about 250 ms after the last word, and it waits through the 1.3 second hesitation in "send a message to … Sarah", where the fixed timeout cuts the speaker off.

The microphone is simulated: a scripted transcript with per-word timings from the original, played by the same clock that runs the policy. Nothing is recorded, and only transcript text leaves the page.

Ported from the jev-voice-turn Jev experiment. Every request here is live; the shared demo key allows about 2 requests per second across all visitors.