Hands-free should drop what she was not called for #89
Labels
No labels
needs-info
needs-triage
ready-for-agent
ready-for-human
v2
wayfinder:grilling
wayfinder:map
wayfinder:prototype
wayfinder:research
wayfinder:task
wayfinder:ticket
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
aiko/denpa#89
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Hands-free today means every utterance in the room is transmitted. That is why hands-free is not what anyone actually uses —
pttDown/pttUp(src/renderer/src/components/receiver/transmission.ts:41,:61) exist because holding a key is the only way to say "this one was for you". The wake phrase is the way to stop paying that price.With
#88landed the score exists and does nothing. This issue gives it consequences: an utterance the detector did not hear a wake phrase in is dropped, not sent.Where the decision goes
In Rust, in
Worker::emit_utterance(src-tauri/src/audio/engine.rs:385). Not in the renderer.That placement matters more than it looks. If the gate lived in the webview, the audio would already have crossed the IPC boundary and been assembled into a
SpeechEndpayload before anything decided it was nobody's business. Dropping it in the worker means unwanted speech never leaves the audio thread. It is also the only placement that keeps#92honest later — a speaker gate that runs after the audio has been handed around is a gate in name.The rule
Arm a window when the score crosses the threshold. An utterance emits if the window was open when it started; otherwise it is discarded and the buffer cleared.
Three cases that are easy to get wrong, and each is a test:
from_press(engine.rs:98) is an explicit human signal —#33settled that already, andhandleSpeechEnd(vad-context.tsx:358) still honours it even when the detector saw nothing. The wake phrase is an alternative to the press, not a condition on it. Someone holding the key must never be told they were not heard.The renderer has to show it
AiStateEnum(src/renderer/src/context/ai-state-context.tsx:18) hasidle,listening,thinking-speaking,interrupted,waiting,loading. None of those is "the microphone is open and she is waiting to be called", which is a different thing fromidleand a different thing fromlistening.Without a visible distinction the failure mode is silent and infuriating: you speak, nothing happens, and there is no way to tell whether the phrase missed, the mic is shut, or the connection is down.
#15is already the issue about failure states being invisible; do not add a seventh way to be invisibly stuck.handleSpeechStart(vad-context.tsx:344) currently goes straight tolisteningon any speech. It should only do that once armed.Done means
engine.rscover the three cases above.Related
#88— the score this consumes#90— the switch that turns this on, and the threshold it arms at#92— the second gate, which hangs off the same point#15— the WAITING state and failure screens#33— a press is an explicit human signalThe rule above is wrong. Correcting it before it gets built, on evidence measured in
#87.What was missed
The rule says: an utterance emits if the window was open when it started. That assumes the wake trigger arrives at or before the start of the utterance it admits. It does not.
The detection peak lands after the phrase has finished, because the score is computed over a rolling 16-embedding window and the phrase has to flush through it. Measured in
#87: padding half a second of trailing silence onto a held-out clip moved detection from 64% to 98.7% for the same model. The peak is roughly half a second late by construction.So for "Hey Denpa, what's the weather" as one unbroken utterance:
The trigger lands 1.3 seconds after the utterance started. A rule that asks "was the window open when this utterance began" answers no, and drops the one case the feature exists for. The issue already warned that arming on utterance boundaries rejects the normal case; it then specified a rule with the same flaw one level down.
The rule that works
Keep the timestamp of the last trigger. At emit time:
One comparison, and it covers all three shapes:
therefore does double duty: it is both the follow-up grace after a bare "Hey Denpa" and the expiry. A few seconds.
Consequence worth accepting deliberately
In the split case the wake phrase itself is dropped, so "she should hear her own name" does not hold there — the audio carrying it belonged to an utterance that was discarded before the trigger existed. It still holds for the single-utterance case, which is the common one. Buffering the previous utterance in case a trigger shows up shortly after would fix it and is not worth the complexity now; noted so nobody reads the difference as a bug.
Also
The trigger must NOT be consumed on use, or the second sentence of a two-part request is deaf. It expires on time, not on a single admission.
The comment above lost three expressions to shell interpolation when it was posted, including the rule itself. Restating it here; this version is the one to build against.
The rule
Keep the timestamp of the last wake trigger. At emit time:
That is, the trigger may land anywhere from
ARM_WINDOWbefore the utterance began right through to the moment it ends. One comparison, three shapes covered:ARM_WINDOW, so it is admitted.ARM_WINDOWdoes double duty: the follow-up grace after a bare "Hey Denpa", and the expiry that stops one greeting admitting everything said afterwards. A few seconds.Why the original rule fails
It asked whether the window was open when the utterance started, which assumes the trigger arrives at or before the start. It does not. The score is computed over a rolling 16-embedding window, so the phrase has to flush through before the peak appears —
#87measured this as the difference between 64% and 98.7% detection on the same model, purely from padding half a second of trailing silence onto the test clips. The peak is late by construction, roughly half a second.