# Debug Session: mcp-audio-stutter - **Status**: [OPEN] - **Issue**: Piccole interruzioni audio quando una risposta MCP lunga viene eseguita o reiniettata nella conversazione live. ## Reproduction Steps 1. Avvia la UI realtime. 2. Esegui una richiesta che attiva un tool MCP con output grande. 3. Ascolta la risposta audio dell'assistente. 4. Verifica se compaiono micro-interruzioni durante o subito dopo l'iniezione del risultato MCP. ## Hypotheses & Verification | ID | Hypothesis | Likelihood | Effort | Evidence | |----|------------|------------|--------|----------| | A | Inviare al modello il payload MCP quasi completo degrada la fluidità della risposta audio anche senza bloccare il main thread. | High | Low | Confirmed | | B | Esiste una race: il fallback MCP decide il turno prima che `response.created` arrivi, quindi la prima response speculativa parte comunque e viene tagliata. | High | Medium | Confirmed | | C | La chat/UI fa render pesanti quando arriva il risultato MCP grande. | Medium | Medium | Rejected | | D | I log/debug del payload MCP grande aggravano il problema sul main thread. | Medium | Low | Rejected | | E | L'attacco audio di ogni frase soffre di jitter iniziale perché il playback parte senza prebuffer sul primo chunk disponibile. | High | Medium | Confirmed | ## Log Evidence - `execRemoteMcpTool` riceve un result JSON di `5738` byte e produce un output testo di `3262` caratteri senza tempi anomali. - `built live prompt from MCP output` mostra `promptLength` ~`3400` e `buildMs: 0`. - `rendered tool result row` e `rendered MCP tool result in chat` mostrano `renderMs: 0-1`. - `sending conversation.item.create payload` mostra `payloadLength` ~`3790` e `stringifyMs: 0`. - Quindi serializzazione, render UI e stringify non spiegano i micro-tagli. - Nel run post-fix `sendUserText` mostra `responseActive: false`, `openResponses: 0`, `createInFlight: false`: non è un classico barge-in su response già attiva. - Il sintomo residuo si manifesta solo sulla prima frase speculativa; questo è coerente con una race prima di `response.created`. - Il browser può decidere il fallback MCP prima che il client abbia un `responseId` da silenziare: per questo ora viene marcata anche la **next response** come sopprimibile. - L'ultimo sintomo riferito dall'utente cambia ancora: le micro-interruzioni si sentono sulle prime parole di ogni frase, non solo su quella speculativa. - Questo è coerente con il worklet `audio-playback` che parte subito sul primo chunk ricevuto, senza accumulare buffer iniziale. ## Minimal Fix - Accodata la `sendUserText()` del payload MCP quando esiste una response attiva. - Flush del testo differito solo dopo `response.done`, prima del `response.create` già in coda. - Riassunto/troncamento del payload MCP prima di reiniettarlo nella conversazione live. - Ridotto il delay del fallback MCP a `150ms` per match forti e senza argomenti obbligatori. - Soppressione della **next speculative response** se il fallback MCP vince il turno prima che `response.created` sia arrivato. - Introdotto un piccolo prebuffer di avvio (`90ms`) nel worklet `audio-playback` prima di iniziare ogni frase. - Obiettivo: evitare sia il barge-in quando presente, sia il carico eccessivo sul modello con JSON troppo grande. - Obiettivo aggiuntivo: ridurre la finestra in cui il modello può iniziare una frase speculativa prima che parta il tool MCP. - Obiettivo finale: evitare il clipping/jitter sulle prime parole di ogni frase. ## Verification Conclusion - Pre-fix: il browser non mostrava alcun collo di bottiglia locale; il prompt inviato al modello restava però molto grande. - Post-fix: il testo MCP viene accodato se c'è una risposta attiva, il modello riceve solo un summary compatto, il fallback parte quasi subito, la next response speculativa viene silenziata se necessario e il playback aspetta un buffer iniziale prima di iniziare ogni frase.