Skip to content

fix(gateway): dedupe Telegram voice transcript echoes - #44519

Closed
Adel7418 wants to merge 1 commit into
NousResearch:mainfrom
Adel7418:fix/telegram-voice-echo-dedupe
Closed

fix(gateway): dedupe Telegram voice transcript echoes#44519
Adel7418 wants to merge 1 commit into
NousResearch:mainfrom
Adel7418:fix/telegram-voice-echo-dedupe

Conversation

@Adel7418

Copy link
Copy Markdown

Summary

  • Prevent duplicate Telegram voice transcript echo messages when a voice note arrives during an active gateway agent run.
  • Keep interrupt handling able to transcribe the voice note for agent.interrupt(...), but stop the interrupt monitor from sending its own user-visible 🎙️ echo.
  • Add a short TTL-based echo guard so the fresh/pending echo paths suppress immediate duplicate transcript bubbles while still allowing the user to intentionally say the same phrase again later.

Why

When a voice message arrives while an agent is already working, it can flow through two paths:

  1. monitor_for_interrupt() peeks at the pending event, transcribes it, and interrupts the agent.
  2. The pending-message dequeue path later handles the same event and echoes the transcript.

That could create two identical 🎙️ "..." Telegram messages for one voice note.

Verification

  • /home/flora/.hermes/hermes-agent/venv/bin/python -m py_compile gateway/run.py
  • git diff --check
  • uv pip install --python .venv/bin/python pytest
  • uv run python -m pytest tests/gateway/test_voice_stt_echo_dedupe.py -q -o 'addopts='2 passed

@Adel7418
Adel7418 force-pushed the fix/telegram-voice-echo-dedupe branch from 7b0d1e8 to 1885f49 Compare June 11, 2026 23:09
@liuhao1024

Copy link
Copy Markdown
Contributor

Review: Swapped arguments in test

The dedup approach is solid — removing the echo from the interrupt monitor and relying on _dequeue_pending_with_transcription as the single echo owner is the right fix. The _stt_echo_keys dict with 20s TTL is a clean dedup mechanism.

However, the tests have the event and source arguments swapped:

# In test_stt_echo_dedupe_suppresses_immediate_duplicate:
assert runner._mark_stt_echo_sent_once(source, event, ...) is True

But the method signature is:

def _mark_stt_echo_sent_once(self, source, event, audio_path, transcript) -> bool:

Wait — looking again, the test passes (source, event, ...) while the production callers in run.py pass (event=event, source=source, ...). Since the production callers use keyword arguments, the runtime order is correct. But the test uses positional arguments in the wrong order: it passes source where event is expected and vice versa.

The test works by coincidence because _mark_stt_echo_sent_once reads platform, chat_id, and thread_id from the first positional parameter (which the test calls source but passes as the event parameter's value). Both SimpleNamespace objects carry these fields from the test's source object, so the dedup key is identical regardless of swap.

This means the test doesn't actually verify that the method reads from the correct parameter. If someone later adds event.message_id to the dedup key (as the type hint suggests was considered), the test would silently break.

Suggested fix: use keyword arguments in the test to match production callers:

assert runner._mark_stt_echo_sent_once(event=event, source=source, audio_path="/tmp/a.ogg", transcript="Проверка") is True

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery platform/telegram Telegram bot adapter P2 Medium — degraded but workaround exists labels Jun 11, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tracing the monitor/drain duplicate path; current main still has it: the monitor echoes at gateway/run.py:19305-19313, then the retained pending event is drained and echoed again at gateway/run.py:19684, gateway/run.py:19727-19735.

Problems

  • The proposed TTL key is (platform, chat, thread, transcript). It omits event/audio identity, so two distinct voice notes with the same transcript in the same chat within 20 seconds lose a legitimate echo. Current main keeps the pending event from monitor through drain (gateway/run.py:19270-19278), allowing event-scoped state instead.
  • tests/gateway/test_voice_stt_echo_dedupe.py tests only the helper, not the production monitor-to-drain sequence or one-transcription invariant.

Suggested changes

  • Cache transcript output and echo-sent state on the pending MessageEvent, then reuse it in both paths.
  • Add an async end-to-end regression that asserts one STT call and one 🎙️ send for one pending voice event.

Automated hermes-sweeper review.

Comment thread gateway/run.py
# different MessageEvent instances and/or cached file paths by the time
# the pending-message helper runs. The stable duplicate signal is the
# same transcript being echoed into the same chat/thread immediately.
key = (platform, chat_id, thread_id, transcript_key)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This key suppresses separate voice messages that happen to produce the same normalized transcript in this chat/thread during the 20-second window. The monitor preserves the queued MessageEvent until post-run drain, so cache/mark the event instead; that identifies the actual duplicate and avoids suppressing a legitimate second note.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Closing as fixed on main: f5d493aebf (July 19) added _echo_pending_stt_transcripts_once with the _gateway_pending_stt_echo_sent ledger, deduplicating Telegram voice transcript echoes — the same bug this PR fixes.

Thanks @Adel7418 for the fix; a residual re-echo gap across media merges is being handled separately via #67281.

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 P2 Medium — degraded but workaround exists platform/telegram Telegram bot adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants