Skip to content

fix: prevent Discord NO_REPLY bot loops - #29932

Open
JeffreyDavidson wants to merge 1 commit into
NousResearch:mainfrom
JeffreyDavidson:fix/no-reply-bot-loop
Open

fix: prevent Discord NO_REPLY bot loops#29932
JeffreyDavidson wants to merge 1 commit into
NousResearch:mainfrom
JeffreyDavidson:fix/no-reply-bot-loop

Conversation

@JeffreyDavidson

Copy link
Copy Markdown

Summary

  • Drop Discord bot messages whose content is exactly NO_REPLY before they can enter the agent loop
  • Exclude bot NO_REPLY sentinel messages from Discord channel history backfill
  • Suppress literal NO_REPLY platform responses at delivery time
  • Add regression coverage for the Discord bot filter behavior

Why

When bot-to-bot routing is enabled with DISCORD_ALLOW_BOTS=mentions, a bot NO_REPLY sentinel can be treated as a real prompt. If the receiving agent then intentionally stays silent, the gateway can surface empty-response retries and create noisy bot-to-bot loops. NO_REPLY should behave as a control/silence token, not a user prompt.

Verification

  • ./venv/bin/python -m pytest tests/gateway/test_discord_bot_filter.py tests/gateway/test_discord_bot_auth_bypass.py -q

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery platform/discord Discord bot adapter P2 Medium — degraded but workaround exists labels May 21, 2026
zombi3butt pushed a commit to zombi3butt/hermes-agent that referenced this pull request May 21, 2026
Three-layer fix for Discord bot-to-bot silence token handling:

1. Entry filter (_handle_message): Drop Discord bot messages with content
   exactly "NO_REPLY" before they enter the agent loop. When
   DISCORD_ALLOW_BOTS=mentions, other bots' NO_REPLY must be ignored.

2. Backfill exclusion (_fetch_channel_context): Exclude NO_REPLY sentinel
   messages from channel history backfill so they don't contaminate session
   context.

3. Delivery suppression (send): Suppress literal NO_REPLY responses from
   being sent to Discord channels — it's a control/silence token, not
   user-facing content.

Fixes noisy bot-to-bot loops caused by agent silence being surfaced as
empty-response retries.
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused three-layer investigation. Current main has since centralized the outbound part of this behavior: gateway/response_filters.py:56-79 recognizes successful exact NO_REPLY turns, gateway/run.py:12146-12156 suppresses their delivery after persistence, and gateway/stream_consumer.py:606-620 covers streaming output.

Problems

  • The PR edits the removed gateway/platforms/discord.py; the active Discord implementation is now plugins/platforms/discord/adapter.py, and GitHub reports this branch as conflicting.
  • The added test duplicates the proposed conditional instead of exercising the adapter, so it cannot verify the live callback after the platform migration.

Suggested changes

  • If the remaining inbound/history hygiene is desired, port it narrowly to plugins/platforms/discord/adapter.py:1130 and :5050, with adapter-level tests.
  • Please account for the documented constraint at website/docs/user-guide/messaging/discord.md:304-307: Hermes-to-Hermes Discord reply loops are an unsupported topology; the remaining change should be justified as trusted-external-bot hygiene rather than a circuit breaker.

Automated hermes-sweeper review.

@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-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 13, 2026

@GottZ GottZ 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.

This was generated by AI during triage.

Summary

Two PRs address the Discord NO_REPLY loop through inbound filtering, history exclusion, and outbound suppression. Their Discord changes substantially overlap, but #29932 is focused while #29942 also bundles multiple unrelated fixes; moreover, current main already centralizes outbound NO_REPLY suppression, and both diffs target the removed pre-migration Discord module.

Related pull requests

  • #29932 related — (+35/-0) — keep open for a narrow rework, not merge as-is: the diff directly filters bot-authored exact NO_REPLY messages, excludes them from history, and suppresses outbound delivery, but it edits the removed gateway/platforms/discord.py and its test reproduces the condition rather than exercising the active adapter. Consistent with the contributor keep_open review on #29932, port only the still-needed inbound/history hygiene to plugins/platforms/discord/adapter.py with adapter-level tests; drop the now-duplicated outbound change.
  • #29942 [closed] duplicate — (+399/-16) — closed, relevant as an overlapping reference implementation but not a merge candidate: its Discord portion implements the same three layers as #29932, while most of the diff contains unrelated tool-result serialization, provider, compression, runtime-provider, and skill-sync changes. The contributor review explicitly required these concerns to be split, so its focused Discord logic may inform #29932 but the closed PR should not be reopened or merged.

Duplicates

#29932 and the Discord-specific portion of #29942 are substantially duplicate implementations of inbound NO_REPLY filtering, history exclusion, and outbound suppression; #29942 additionally contains extensive unrelated changes.

Suggested consolidation

Do not merge either branch as-is. Consolidate the remaining work in #29932 by retaining it open, rebasing onto current main, porting only justified inbound/history filtering to the active Discord adapter with live-path tests, and omitting outbound suppression already centralized on main; keep #29942 closed as the superseded, unfocused duplicate reference.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    subgraph Dup29932 ["PRs duplicating each other"]
        P29932["PR #29932 (open)"]
        P29942["PR #29942 (closed)"]
    end
    class P29932 open
    class P29942 closed
    class P29932 target
    click P29932 "https://github.com/NousResearch/hermes-agent/pull/29932"
    click P29942 "https://github.com/NousResearch/hermes-agent/pull/29942"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed or no verify verdict yet (state tag in the node label).

Cross-PR triage: Reviewed 2 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 31 kB of PR diffs, 2 kB of issue/PR text, 2 kB of discussion (3 comments), 1 verify verdict. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

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-broad Sweeper blast radius: broad — a core path most sessions hit 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