Skip to content

[bug] "Do it" is transcribed "Duet" and "go, send" becomes "context:" — a tmux agent gets no vocabulary bias, and the divergence guard is off below four words #371

Description

@serge-ivo

The message was recorded. It just isn't the message.

Reported as "it showed as I dictated it, then never stayed on the thread — just disappeared."
Nothing was dropped. Instance cda75e28 (Heartfull tmux Operator), message 0f6c8f6f:

role: user
content: "Duet" ← what was stored, sent to the agent, and shown on the thread
dictation: "Do it" ← what the browser gate heard
audioKey: f220871a-… ← the recording, intact
createdAt: 2026-08-07T06:28:58Z

The user said "Do it". The live bubble showed "Do it". Whisper returned "Duet", the bubble
cleared, and the thread got a word the user never said — which reads exactly like the message
vanishing, because the thing on screen is not theirs.

It broke the conversation, not just the record

The turn before, the agent asked: "…shall I do that now?" — a yes/no. The user answered "Do it".
The agent received "Duet", could not resolve it, and replied by re-listing the same five tmux
sessions it had already listed twice. The task never started.

The pattern across the whole thread — 4 of 5 voice turns are wrong

whenstored + sent (Whisper)heard live (browser gate)
03:35Is tmux ready?Yes Timo is ready is the agent replying in the terminal or not
05:35Heartfull(agreed)
06:26Do you see the console output?Do you see the console output what's there right now
06:28:19context:Send
06:28:58DuetDo it

The browser gate was more accurate than Whisper on every turn where they disagreed, and the
platform always ships Whisper's reading and files the gate's into a footnote badge.

Root cause: a tmux agent is given no vocabulary at all

buildTranscribePrompt (packages/sdk/src/voice/prompt.ts:27) gates its term lists on surfaces:

if(surfaces.includes("coding")||surfaces.includes("repo"))parts.push(CODING_TERMS);if(surfaces.includes("apply"))parts.push(APPLY_TERMS);

This instance is surfaces: ["tmux"], runtime: "coding". tmux is in neither branch, so
CODING_TERMS is skipped. The call site passes the instance name as extra
(InstanceDetail.tsx:237), so the entire transcription bias for this agent is the literal string
"Heartfull (tmux)"
— its own name, and nothing else.

Meanwhile CODING_TERMS already contains the exact words this user says to a terminal agent:
console, terminal, git, npm, pnpm, commit, branch, deploy, build, lint, endpoint. They are sitting
in the file, unused, because the gate asks about the surface rather than the runtime.

That is why Do you see the console output… lost half its sentence, and why two-word utterances had
nothing at all steering them. tmux should be in the coding branch — a one-line change — and the
gate is arguably reading the wrong axis entirely, since runtime: "coding" was true here the whole
time.

Note the second-order risk this creates: the ONE term the agent is biased toward is a proper noun,
which is precisely the #332 failure mode (a proper noun in the bias list coming back verbatim on a
low-information clip). isTranscribeBiasEcho would catch Heartfull (tmux) because it is
multi-word — but it deliberately passes single-word echoes, so a one-word agent name would not
be caught.

The guard that exists is switched off for exactly these turns

dictationDiverged (packages/sdk/src/voice/machine.ts:322) is what flags a transcript as
materially short:

exportfunctiondictationDiverged(heard: string,final: string): boolean{consth=words(heard);if(h.length<4)returnfalse;// ← silent below four heard wordsreturnwords(final).length<Math.ceil(h.length*0.6);}
  • "Do it"h.length === 2false. No warning.
  • "Send"h.length === 1false. No warning.

So the two turns that came back as pure nonsense are the two the guard is contractually forbidden to
comment on. The floor is defensible in isolation — four words is "too little signal to accuse
anything"
on a volume test — but the result is that short utterances, which are the most
likely to be mistranscribed, are the only ones with no check at all.
dictationLoss still renders
a dim "2 words not in the transcript", which is not a signal anyone reads as "this is the wrong
word".

What to change

1. A short utterance needs a different test, not the volume one. When the heard capture is under
four words, compare by overlap instead: if the final transcript shares no word with what the
gate heard, that is divergence regardless of length. Do it/Duet and Send/context: both trip
it; colour/color and ordinary engine disagreement do not, because they overlap.

2. Prefer the gate's reading when the final is a bare unknown token. The gate got it right every
time here. At minimum, a one-tap "use what I heard instead" on the bubble — the data is already
stored (dictation) and already rendered behind the Show what was heard toggle
(SpokenMessage.tsx); today it is archaeology, not a correction path.

3. Re-examine the bias prompt.context: is a prompt fragment surfacing as a transcript. #332
treated the symptom (agent names); the general shape — a low-information clip echoing the prompt —
is still live and produces text the user cannot distinguish from their own words.

Related

#364 (the composer showed nothing while this happened, so the live capture was only visible in the
thread bubble), #332 (prompt-token echo), #319 (the stored live capture that made this diagnosable at
all — without it this report would have been unanswerable).

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

    bugSomething isn't workingvoiceVoice / STT / TTS

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions