Skip to content

fix(telegram): queue voice follow-ups instead of interrupting in-flight reply (#31328) - #31342

Closed
alexzhu0 wants to merge 1 commit into
NousResearch:mainfrom
alexzhu0:fix/telegram-voice-followup-no-interrupt-31328
Closed

fix(telegram): queue voice follow-ups instead of interrupting in-flight reply (#31328)#31342
alexzhu0 wants to merge 1 commit into
NousResearch:mainfrom
alexzhu0:fix/telegram-voice-followup-no-interrupt-31328

Conversation

@alexzhu0

Copy link
Copy Markdown
Contributor

What

Extends the existing PHOTO queue-don't-interrupt special case in both BasePlatformAdapter.handle_message and GatewayRunner._handle_message to also cover MessageType.VOICE.

Why

When a user sends voice notes in rapid succession, voice 2 arriving mid-processing of voice 1 currently:

  1. Hits the active-session branch in BasePlatformAdapter.handle_message
  2. Falls past the PHOTO-only exception → executes the default "interrupt" path
  3. Sets the per-session interrupt_event and queues into _pending_messages
  4. Voice 1's _process_message_background task finishes, generates a reply, then the stale-response check at base.py:3255-3265 — interrupt_event.is_set() and session_key in self._pending_messages — silently sets response = None
  5. Voice 1 is dropped from the conversation: no response ready log, no Sending response, no JSONL entry. The user sees no reply for voice 1, only the merged voices 2+3 batch turn arrives.

This matches the symptom the reporter observed — voice 2 had a conversation turn: history=6 log but no response ready line and no session JSONL entry, while voices 3+4 went on to produce a normal batched response.

Photo bursts already had this protection (added long ago for albums). Rapid voice notes have the same UX shape — the user is providing more inputs, not requesting an interrupt — so they follow the same contract.

After the fix:

  • Voice 1's reply is delivered normally
  • Voice 2 (and any further voices that arrive while voice 1 is still in flight) accumulate in _pending_messages via merge_pending_message_event, which already extends media_urls for events that both have media — so multiple late-arriving voices land in a single merged event
  • When voice 1 finishes, the existing pending-drain logic in _process_message_background spawns a fresh turn for the merged batch — exactly the behavior the reporter described as expected ("merge late-arriving voices" path)

This is option (a) from the issue ("wait for in-flight turns to finish before starting a new batch") implemented as the same queue-don't-interrupt pattern as photo bursts, rather than inventing new mid-flight merge semantics.

Scope kept narrow: only MessageType.VOICE is added (not AUDIO/VIDEO/STICKER/DOCUMENT) to match the reported issue precisely. The same shape likely applies to AUDIO/VIDEO and could be extended in a follow-up; happy to broaden in this PR if reviewers prefer.

Test

  • tests/gateway/test_interrupt_key_match.py::test_voice_followup_is_queued_without_interrupt — adapter-level: VOICE event during active session must queue into _pending_messages without setting interrupt_event
  • tests/gateway/test_telegram_photo_interrupts.py::test_handle_message_does_not_priority_interrupt_voice_followup — runner-level: VOICE event arriving with a registered running agent must queue without calling running_agent.interrupt()

Both new tests are direct mirrors of the existing photo-followup tests, so they enforce the same contract for voice that has been working for photos.

Verification:

  • 9/9 tests pass in the two modified files
  • Both new tests fail without the production change and pass with it (regression guard confirmed)
  • 74/74 surrounding gateway tests remain green (test_session_race_guard, test_active_session_text_merge, test_queue_consumption, test_telegram_text_batching, test_busy_session_ack, test_duplicate_reply_suppression)

Platform

telegram (the path is shared by all platforms that route through BasePlatformAdapter.handle_message, but the bug surfaces most clearly on Telegram where voice notes are common)

Issue

Closes #31328

…ht reply (#31328)

When a user sends voice notes in rapid succession, voice 2 arriving
mid-processing of voice 1 currently sets the per-session interrupt
Event.  The stale-response check in ``_process_message_background``
then suppresses voice 1's reply, and voice 1 is silently dropped from
the session — no ``response ready`` line, no JSONL entry, no message
to the user.  Photo bursts already had a queue-don't-interrupt
exception for exactly this UX shape; voice bursts have the same shape
so they follow the same contract.

Extends the existing PHOTO special case to also cover VOICE in both
the adapter-level (``BasePlatformAdapter.handle_message``) and
runner-level (``GatewayRunner._handle_message``) priority intercepts.
After the fix, voice 1's reply is delivered, voices 2+ accumulate in
``_pending_messages`` (already merged via ``merge_pending_message_event``
since both have media_urls), and the existing pending-drain logic
spawns a fresh turn for the merged batch when voice 1 completes.

Test: tests/gateway/test_interrupt_key_match.py +
tests/gateway/test_telegram_photo_interrupts.py — adds two regression
tests mirroring the existing photo-followup ones.  Both new tests fail
without this change and pass with it; 74 surrounding gateway tests
remain green.

Platform: telegram
Issue: #31328
@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/gateway Gateway runner, session dispatch, delivery platform/telegram Telegram bot adapter tool/tts Text-to-speech and transcription labels May 24, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Closely related to #8434 (queue voice/audio messages instead of interrupting with empty text) which addresses the same core issue. Also related to #19825 (preserve full MessageEvent in interrupt path). Fixes #31328.

@alexzhu0

Copy link
Copy Markdown
Contributor Author

Thanks for the cluster pointers @alt-glitch. Quick diff for a reviewer collapsing the three:

vs #8434 — same direction (queue voice without interrupt), but #8434 only patches GatewayRunner._handle_message in gateway/run.py. The photo-burst precedent and the active-session interrupt_event both live in BasePlatformAdapter.handle_message (gateway/platforms/base.py:3116), so a run.py-only fix leaves the adapter call site firing the interrupt before run.py ever sees the event. #31342 patches both sites and ships a test pair that mirrors the existing photo tests (test_voice_followup_is_queued_without_interrupt and test_handle_message_does_not_priority_interrupt_voice_followup). Happy to credit @chaizijun1 as co-author if a maintainer prefers preserving authorship from the earlier PR.

vs #19825 — different mechanism. #19825 keeps the interrupt and stores the full MessageEvent so media survives the handoff. The voice-1 drop reported in #31328 is the stale-response check at gateway/platforms/base.py:3255-3265 swallowing voice-1's reply once voice-2 sets interrupt_event — that check fires regardless of whether the pending event carries media, so #19825 alone wouldn't address #31328. Skip-interrupt is the lighter fix for media bursts; the two are only complementary if interrupt semantics are kept for other message types.

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

Labels

comp/gateway Gateway runner, session dispatch, delivery P1 High — major feature broken, no workaround platform/telegram Telegram bot adapter tool/tts Text-to-speech and transcription type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Telegram voice batching can drop the in-flight response when subsequent voices arrive mid-processing

2 participants