Voice and the confirm gate
Speech never becomes a bid by itself. The pipeline, the gate and where the accuracy gate stands.
In plain words
A bidder can say a number instead of tapping it. Three things stay true however well that goes:
- Speech never bids by itself. A spoken bid is a suggestion until the room has checked it, and by default the bidder is asked "did you mean $150?" first.
- The engine decides, not the microphone. Whatever speech produces goes through the same rules as a tap: price, increment, ceiling.
- Speech can fail without stopping the auction. Manual entry and tap to bid never depend on it.
Whether speech is accurate enough to skip the question is the open part. The results so far are on synthetic, noise-free audio only, so bids still ask first; the numbers are further down.
The path a spoken bid takes
Voice pipeline and the confirm gate
Scroll the figure sideways, or open it full size.
packages/voice/src/decide/policy.ts, decide.ts, score.ts, packages/server/src/voice/bridge.ts and room/confirm.ts. The dashed red path is what the code allows, not what the default build does: with one parser the score tops out at 0.82, so a spoken bid confirms. Accuracy numbers are in the table below the figure, not drawn here.Text version of this diagram
- Microphone audio is cut into segments.
- Speech to text turns a segment into a transcript. The default build plays cached transcripts; a live provider (Groq or Deepgram) runs only when VOICE_STT is set with its key.
- The parser reads amount, paddle and intent from the transcript.
- The decision step merges parser opinions, scores confidence and validates the bid against the room. The result is accept, confirm or drop.
- Confirm: the bidder is asked, for example "Did you mean $150 for paddle 12?". Yes places the bid after it is checked against the current price. No or expiry places nothing and is logged.
- Drop: nothing is placed, the decision is logged.
- Accept (dashed): a clean parse with confidence of 0.85 or more that both parsers agree on skips the gate and goes to the engine.
- Manual entry by the auctioneer and tap to bid skip speech and enter at the engine.
- The engine accepts or rejects the bid and an accepted bid is appended to the hash-chained ledger.
Gate G5 is PARTIAL: wrong-accept 0.000 to 0.000 on 24 synthetic, noise-free clips per set (limit 0.05), after fixes chosen on those same clips. No real-room or noisy-room test exists, and spoken bids always confirm.
The pipeline
- Segment. Audio is cut into speech segments.
- Speech to text. The default build plays cached transcripts. A live provider (Groq or Deepgram) runs only when
VOICE_STTis set and its key exists. - Parse. A rule parser reads amount, paddle and intent.
- Decide. The step merges parser opinions, scores confidence and validates the bid against the room. The result is
accept,confirmordrop. - Confirm. A question goes to the bidder (for example "Did you mean $150 for paddle 12?"). A yes is checked again against the live price before a bid is placed. A no or an expiry places nothing and is logged.
Some situations always confirm, whatever the confidence: a correction, a hedged phrase, several amounts or bidders, an unregistered paddle, a large jump, an implausible amount.
The pipeline in detail
Step through it, or hover and focus a box to see what it does and which file implements it.
Voice pipeline in detail: capture to engine
Scroll the figure sideways, or open it full size.
Answer. Yes is checked again by the engine at the time of the answer. No, or 15 seconds of silence, places nothing and is audited.
Hover or focus a box for what it does and which file implements it.
packages/voice/src/browser (mute, queue, drop monitor), packages/server/src/host/audio-ws.ts, packages/server/src/voice/stt.ts, packages/voice/src/stt/failover.ts, packages/voice/src/decide/policy.ts, packages/server/src/voice/bridge.ts and packages/server/src/room/confirm.ts. The default build plays cached transcripts; a live provider needs VOICE_STT and its key. Speech accuracy gate G5 is PARTIAL: wrong-accept 0.000 to 0.000 on 24 synthetic, noise-free clips per set (limit 0.05), after fixes chosen on those same clips; there is no real-room or noisy-room test. A clean bid at 0.85 or more is not held by the code today, so spoken bids go through the confirm prompt by decision in docs/GATES.md, not by a code switch.Text version of this diagram
- Browser: the microphone feeds a mute controller. Muted means no audio, a keepalive every 5000 ms, and the utterance is ended.
- A send queue holds 50 batches and drops the oldest when full. The drop monitor raises a degraded state when more than 2000 ms were dropped in 10 s; the person is told to use manual entry. It recovers after 10 s without drops.
- The /audio socket needs the auctioneer token as its first message within 5 s. Limits: 64 KiB per second, 100 frames per second, 30 minutes, 2 streams per token, 4 per room.
- Speech to text: Deepgram, then Groq, then cached replay when VOICE_STT is deepgram and a Groq key exists. Switch triggers: silent 3000 ms, late final 4000 ms, two errors in a row, fatal, closed. Deepgram is tried again after 30 s.
- The segmenter removes auctioneer talk and duplicates. The rule parser reads amount, paddle and intent. Decide classifies: hard flags confirm, below 0.50 drop, below 0.85 confirm, else accept.
- The room holds a voice bid below 0.85 or flagged ambiguous for 15 seconds and asks the bidder. A drop is stored without its transcript. A clean bid at 0.85 or more goes to the engine.
- A yes is checked again by the engine at the server time of the answer. A no or an expiry places nothing and is audited.
Gate G5: wrong-accept 0.000 to 0.000 on 24 synthetic, noise-free clips per set (limit 0.05), fixes chosen on the same clips. Status PARTIAL.
Which speech provider runs
With VOICE_STT unset (or replay) the cached provider plays recorded transcripts. No key is needed and nothing leaves the host.
AUTO or CONFIRM
The decision step classifies the segment. Confidence at or above 0.85 with no hard flag is accept. From 0.50 up to 0.85 is confirm. Below 0.50, or nothing that looks like a bid, is drop. Hard flags (correction, hedged phrase, several amounts or bidders, homophone, unsupported currency, fractional amount, large jump, unregistered paddle, implausible amount, model disagreement) always confirm.
The bridge hands it to the room. An accept becomes a voice bid carrying its confidence. A confirm becomes a voice bid flagged ambiguous. A drop is written to the audit trail without the transcript.
The room gate applies. A voice bid below 0.85, or flagged ambiguous, is held for 15 seconds and its bidder is asked. A clean bid at 0.85 or more goes straight to the engine, which still checks price, increment and ceiling.
The answer is checked again. A yes runs the normal bid pipeline at the server time of the answer. A no, or no answer within 15 seconds, places nothing and is audited.
Two timers, two layers
The decision step keeps its own prompt for 8 seconds (CONFIRM_TTL_MS in packages/voice/src/decide/decide.ts). The room holds a bid for 15 seconds (CONFIRM_TTL_MS in packages/server/src/room/confirm.ts). The bridge submits a confirm decision to the room, so the room's 15 second hold is the one a bidder meets.
Mute and the drop monitor
Muting is a safety control as well as a convenience. While muted, no audio is sent: the browser sends an empty keepalive every 5 seconds so the stream stays open, and muting tells the provider the utterance has ended. Push to talk starts muted and opens the microphone only while the talk key is held. If the microphone track ends (device unplugged) one automatic restart is tried before the state becomes an error.
The drop monitor watches the browser's send queue. The queue holds 50 batches and drops the oldest when it is full. If more than 2 seconds of audio are dropped inside 10 seconds, capture moves to a degraded state and the person sees "Connection is slow, voice bids may be missed. Use manual entry." Ten seconds without a new drop returns it to running. Manual entry and tap to bid are never affected.
The upgrade checks the origin against the allowed list. The first message must be an auth frame with the auctioneer token within 5 seconds; the token is never in the URL. After that the voice session enforces 64 KiB per second, 100 frames per second, 30 minutes per stream, 2 streams per token and 4 per room. The microphone is the room's: the bidder of a spoken bid comes from the paddle that was heard, never from the token.
No. The voice path ends at the room, and the room calls the engine like any other bid. Holds and capture are the payments package's job and run on the settlement path described in the money flow page.
How accurate is it?
G5 PARTIALWhere this lives in the code
- packages/voice/src/decide/policy.tsaccept, confirm or drop; the hard flags
- packages/voice/src/decide/decide.tsthe decision
- packages/voice/src/decide/score.tsconfidence scoring
- packages/voice/src/decide/answer.tsanswering a confirm prompt
- packages/voice/src/pipeline/pipeline.tsthe pipeline
- packages/server/src/voice/bridge.tsaudio socket to room
- packages/server/src/room/confirm.tsheld voice bids
- packages/server/src/host/audio-ws.tsthe /audio socket and its auth message
- packages/server/src/voice/stt.tsprovider choice and the Deepgram, Groq, cached chain
- packages/voice/src/stt/failover.tsswitch triggers and the 30 second circuit
- packages/voice/src/browser/mute.tsmute, push to talk, keepalive
- packages/voice/src/browser/degraded.tsthe drop monitor
- packages/voice/src/audio/queue.tsthe bounded send queue
Last updated on