Skip to content

[invariant] Mute must be reachable at every moment — ADR 0001, and the tests that keep it true #388

Description

@serge-ivo

Owner decision, recorded as an invariant

"Mute should be available at any time. I should be able to mute at any moment — and unmute
also."

Written up as ADR 0001 — Mute is available at every moment of a voice session
(docs/adr/0001-mute-is-always-available.md), which also establishes the ADR convention for this
repo (docs/adr/README.md). platform-docs/voice.md now documents the invariant, the full command
vocabulary, the settings hierarchy, the hands-free lifecycle, the guards and the tunables, and links
the ADR from the place someone would break it.

This issue is the enforcement half: the invariant currently rests on nothing but the code
happening to be right today, and it has already been one review comment away from being removed.

The four rules (full text in the ADR)

  • M1 — mute reachable by voice AND touch during listening / transcribing / processing /
    speaking, and unmute while muted. No phase is a dead zone.
  • M2 — mute silences both directions at once: mic closed, in-flight speech cancelled, queue
    dropped.
  • M3 — no condition may reduce to "not while the agent is speaking". Echo/pause guards must
    not be applied wholesale to the control path.
  • M4 — unmute is symmetric and matched by the one listener that runs while muted.

Why this needs a test and not just a document

The near-miss is on record. #386 is a real defect — the control listener has no echo guard — and
its first proposed fix was "drop control results while isEchoing". That is one line, it reuses an
existing tested helper, it matches three neighbouring call sites, and it deletes mute-during-TTS,
which is the entire reason #153 built that listener. Nothing in the type system, the tests, or the
file being edited would have objected.

Do

  • Unit tests in packages/sdk/src/voice/convo.test.tsmatchVoiceCommand returns mute
    for a configured mute phrase with ttsSpeaking: true, and unmute with muted: true. Pure,
    cheap, and the first thing to fail on a regression.
  • A dispatcher test on the control path asserting that whatever echo/precision logic ends up
    there, a deliberate mute utterance during TTS still dispatches mute. This is the test
    that would have caught [bug] The agent's own voice can issue commands — the always-on control listener is the only path with no echo guard, and a partial "stop" exits hands-free #386's first draft.
  • A comment on handleControlResult (packages/sdk/src/voice/use-voice.ts:834) pointing at
    ADR 0001 — one hop from the place the constraint would be broken.
  • Close the touch half of M1 everywhere hands-free ships. Verify the on-screen mute is
    reachable in every phase on the Assistant tab and the Coder Co-pilot, including while the
    agent is speaking and on a phone. M1 requires two channels; today the voice channel is the
    one that is documented and the touch channel is the one that is assumed.
  • Decide what to do about the no-Web-Speech hole. Where the browser has no
    SpeechRecognition, ensureControlStt returns null and voice-mute during TTS cannot exist —
    the invariant then rests entirely on the on-screen control. Options: accept it and make the
    touch control unmissable in that case; detect it and say so once in the UI ("voice commands
    are not available in this browser") rather than letting configured commands silently do
    nothing; or add a non-Web-Speech fallback. This one is a decision, not a task.

Explicitly out of scope

exit and next are not covered by ADR 0001. They end or redirect a session rather than
quieting it, and holding them to a stricter bar (final-only, whole-utterance, or confirmation) is
legitimate — see #385 and #386. Mute is the one that must never be hard to reach, because it is what
a user reaches for when something has already gone wrong.

Related: #153 (built the listener), #386 (fix the echo defect without breaking this),
#385 (blank-field semantics + phrase collisions), #387 (hands-free ending silently).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationvoiceVoice / STT / TTS

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions