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 /classifywith 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, andincomplete"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-noover the textAssistant 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" andwhen_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 /classifywhose labels are the regex candidates plusnone, 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.