Skip to content

fix(discord): keep voice meetings responsive - #42597

Open
joelneleber wants to merge 1 commit into
NousResearch:mainfrom
joelneleber:fix/discord-voice-meeting-timeout
Open

joelneleber wants to merge 1 commit into
NousResearch:mainfrom
joelneleber:fix/discord-voice-meeting-timeout

Conversation

@joelneleber

@joelneleber joelneleber commented Jun 9, 2026

Copy link
Copy Markdown

Summary

  • reset Discord voice-channel inactivity timers from inbound audio activity, not just join/playback
  • move the default transcript path closer to the Discord bot adapter: STT results are posted to the linked side chat directly by the adapter without invoking the agent for every utterance
  • make /voice join default to meeting mode: transcribe voice into the side chat without running a full agent/TTS turn for every utterance
  • keep legacy every-transcript agent behavior opt-in via HERMES_DISCORD_VOICE_TRANSCRIPT_AGENT_TURNS=1
  • add targeted voice command/timeout coverage

Test Plan

  • /home/j/.hermes/hermes-agent/venv/bin/python -m py_compile gateway/run.py plugins/platforms/discord/adapter.py tests/gateway/test_voice_command.py
  • custom venv smoke: adapter-side _process_voice_input default transcript-only mode and opt-in agent callback mode
  • custom venv smoke: _handle_voice_channel_input default transcript-only mode and opt-in agent-turn mode
  • custom venv smoke: _handle_voice_channel_join default meeting message/auto-TTS disabled and opt-in legacy auto-TTS enabled
  • custom venv smoke: DiscordAdapter._note_voice_activity throttles timeout resets

Notes

  • Full pytest was not available in the current venv (No module named pytest), so I ran compile + targeted smoke checks with the live Hermes venv.
  • scripts/brain-context-check.sh --staged was not present in this repo checkout.

Brain-Context: n/a (brain-context-check script unavailable in this checkout)

@joelneleber
joelneleber force-pushed the fix/discord-voice-meeting-timeout branch from 9f5074e to 20371dd Compare June 9, 2026 04:19
@liuhao1024

Copy link
Copy Markdown
Contributor

Double-posting to side chat when transcript agent turns are enabled

When HERMES_DISCORD_VOICE_TRANSCRIPT_AGENT_TURNS=1, the voice transcript is posted to the side chat twice:

  1. Adapter's _process_voice_input calls _post_voice_transcript_to_side_chat (new in this PR)
  2. Adapter then calls _voice_input_callback → gateway's _handle_voice_channel_input, which also posts via channel.send (line ~9454 in current gateway/run.py)

When agent turns are disabled (default), only the adapter posts — correct single posting. When enabled, both post — duplicate messages in the side chat.

Fix: Remove the channel.send block from _handle_voice_channel_input since the adapter now handles side-chat posting. The gateway should only handle auth, dedup, and the agent-turn pipeline.

Also note: the adapter's _post_voice_transcript_to_side_chat sanitizes @everyone/@here, but the gateway's channel.send does not — so the duplicate would be unsanitized.

Minor (code duplication): _env_flag, _voice_transcripts_trigger_agent_turns, and _is_duplicate_voice_transcript are all defined in both gateway/run.py and discord/adapter.py. The gateway versions are now largely redundant since the adapter handles dedup and posting. Consider removing the gateway copies to avoid drift.

@liuhao1024

Copy link
Copy Markdown
Contributor

Double-posting when HERMES_DISCORD_VOICE_TRANSCRIPT_AGENT_TURNS=1

When agent turns are enabled, the voice transcript gets posted to the side chat twice:

  1. Adapter (_process_voice_input_post_voice_transcript_to_side_chat) — posts **[Voice]** <@user>: transcript
  2. Gateway callback (_handle_voice_channel_input) — posts the same **[Voice]** <@user>: transcript again before dispatching to the agent

The adapter always posts via _post_voice_transcript_to_side_chat, then conditionally calls _voice_input_callback. When the callback is _handle_voice_channel_input and agent turns are enabled, it also posts — producing duplicate messages in the side chat.

Fix: Remove the posting from one side. Either:

  • (a) Remove channel.send from _handle_voice_channel_input and let the adapter own all transcript posting, or
  • (b) Remove the _post_voice_transcript_to_side_chat call from the adapter and let the callback handle posting (as it did before this PR)

Option (a) is cleaner since the adapter already owns deduplication (_is_duplicate_voice_transcript) and the new _post_voice_transcript_to_side_chat.


Duplicate _env_flag helper

_env_flag is defined identically in both gateway/run.py and plugins/platforms/discord/adapter.py. Similarly, _is_duplicate_voice_transcript and _voice_transcripts_trigger_agent_turns are defined in both the gateway and adapter. This creates drift risk — if one copy is updated (e.g., to handle "0" / "false" / "off"), the other won't be. Consider extracting to a shared utility or having the adapter call through the gateway.

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Summary

Verdict: Approved

Makes Discord voice meeting mode the default: raw transcripts are posted to the side chat without triggering a full agent turn. This prevents long meetings from becoming slow as session history grows. Operators can opt back into the old behavior with HERMES_DISCORD_VOICE_TRANSCRIPT_AGENT_TURNS=1.

  • Good documentation in docstrings explaining the meeting mode rationale
  • New test covers the default (no callback) behavior
  • Backward compatible via env flag
  • No security concerns

Reviewed by Hermes Agent (cron batch)

@liuhao1024

Copy link
Copy Markdown
Contributor

Double-posting when HERMES_DISCORD_VOICE_TRANSCRIPT_AGENT_TURNS=1

The adapter's _process_voice_input now always calls _post_voice_transcript_to_side_chat() (line ~2512 in adapter.py), posting the transcript to the side chat. Then, when agent turns are enabled (env var = 1), it also calls self._voice_input_callback(...) which resolves to GatewayRunner._handle_voice_channel_input in run.py — and that handler also posts to the side chat (the channel.send(...) block near the top of the method).

So when the opt-in env var is set, every voice transcript produces two side-chat messages:

  1. adapter._post_voice_transcript_to_side_chat()channel.send(...)
  2. run.py._handle_voice_channel_input()channel.send(...)

Fix: the adapter should skip its own _post_voice_transcript_to_side_chat call when self._voice_transcripts_trigger_agent_turns() is true, since the callback path already handles posting. Or alternatively, _handle_voice_channel_input should skip its posting when called via the callback (e.g. by checking whether the adapter already posted).


Minor: duplicated helpers across adapter and gateway

Three helpers are defined identically in both gateway/run.py and plugins/platforms/discord/adapter.py:

  • _env_flag()
  • _is_duplicate_voice_transcript()
  • _voice_transcripts_trigger_agent_turns()

The adapter should own this logic since it lives in the plugin. The gateway should either call through the adapter or import from a shared utility. Independent dedup stores also mean a transcript suppressed as duplicate by the adapter may pass the gateway's independent dedup check (or vice versa).

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

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for targeting a real Discord VC inactivity gap: current main only re-arms the timeout after check_silence() returns a completed utterance (plugins/platforms/discord/adapter.py:3162-3179).

Problems

  • The opt-in agent-turn path double-posts transcripts. The added adapter post occurs before the callback, and the callback target still posts at gateway/run.py:13061-13066. This matches @liuhao1024's review comments.
  • HERMES_DISCORD_VOICE_TRANSCRIPT_AGENT_TURNS is a new non-secret behavior setting, contrary to AGENTS.md:102-107. It also changes the documented default full-agent VC flow (website/docs/user-guide/features/voice-mode.md:244,351-365).

Suggested changes

  • Keep one transcript-post owner and add an enabled-agent-turn regression test asserting one side-chat send.
  • Split/rescope the inbound-activity timeout fix; if meeting mode remains configurable, use documented config.yaml plumbing.

Automated hermes-sweeper review.

)
return

await self._post_voice_transcript_to_side_chat(guild_id, user_id, transcript)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

When HERMES_DISCORD_VOICE_TRANSCRIPT_AGENT_TURNS=1, the callback immediately below reaches GatewayRunner._handle_voice_channel_input, which still sends the transcript at gateway/run.py:13061-13066. This post therefore creates two identical side-chat messages; make either the adapter or gateway the single posting owner and cover the enabled path.

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/discord Discord 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.

6 participants