Skip to content

fix(discord): continuously recover missed gateway messages - #87700

Open
maxibotstef wants to merge 2 commits into
NousResearch:mainfrom
maxibotstef:task/oc-20260816-1155-discord-reliability-standalone
Open

fix(discord): continuously recover missed gateway messages#87700
maxibotstef wants to merge 2 commits into
NousResearch:mainfrom
maxibotstef:task/oc-20260816-1155-discord-reliability-standalone

Conversation

@maxibotstef

Copy link
Copy Markdown

Summary

Hermes can remain nominally connected to Discord while silently missing a gateway event. The existing recovery scan runs only at startup/ready time, so a missed human message can remain unanswered until the next restart.

This change:

  • runs the existing durable Discord missed-message scan periodically, with one adapter-owned loop and serialized scans;
  • reuses the existing authorization, ignored-channel, mention, bot-loop, deduplication, response-ledger, and dispatch paths;
  • atomically claims recovered message IDs so a live event racing recovery cannot dispatch twice;
  • admits a human reply to another bot only in configured free-response scope while retaining the direct-mention and bot-loop gates;
  • adds bounded configuration, lifecycle, race, durable-completion, and incident-shaped regression coverage.

Incident reproduced

An untagged human ? appeared in a configured free-response Discord channel after gateway startup. The WebSocket remained connected, but the live event was absent from gateway dispatch logs and startup-only recovery did not revisit it. This is distinct from a reconnect-only outage.

Implementation

  • discord.missed_message_backfill.interval_seconds defaults to 60.
  • 0 keeps the ready-time scan while disabling periodic polling.
  • Invalid, negative, and non-finite intervals fail closed with a warning; intentional 0 is quiet.
  • Repeated ready events coalesce onto one periodic task.
  • The periodic owner awaits the existing one-shot scan task, preventing overlap.
  • disconnect() cancels the periodic owner before the active scan.
  • Per-pass limit and max_dispatches bounds remain in force.
  • Recovered admission uses claim=True; failure/cancellation paths retain the existing claim release behavior.

Verification

Exact reviewed head: 6b3111e8f14da7f9ddf1cca09d091f4032a9c7ca

133 passed, 2 existing AsyncMock warnings in 11.19s
python3 -m py_compile: passed
git diff --check: passed
changed paths: exactly 5

Focused suites:

python -m pytest -q \
  tests/gateway/test_discord_free_response.py \
  tests/gateway/test_discord_missed_message_backfill.py \
  tests/gateway/test_discord_liveness.py \
  tests/gateway/test_voice_command.py

Independent Opus review: GO_WITH_CAVEATS, no blockers. The review specifically verified the recovered/live-event race, consecutive-pass durable completion, other-bot narrowing, interval validation, serialized task ownership, and shutdown order.

Related open PRs / overlap disclosure

Caveats / activation checks

  • The environment-variable fallback is used only when the whole missed_message_backfill config mapping is absent; config-file precedence is unchanged.
  • The configured interval should be operationally sane. The production canary uses 15 seconds and watches REST/rate-limit logs and event-loop latency.
  • Unit tests are L2 evidence only. Live recovery, response delivery, and duplicate suppression are verified separately after supervised activation.

Scope

Changed paths only:

  1. hermes_cli/config_defaults.py
  2. plugins/platforms/discord/adapter.py
  3. tests/gateway/test_discord_free_response.py
  4. tests/gateway/test_discord_missed_message_backfill.py
  5. tests/gateway/test_voice_command.py

No scheduler, watchdog service, database, auth weakening, liveness-threshold change, broad adapter refactor, or unrelated delivery change.

@alt-glitch alt-glitch added type/feature New feature or request comp/plugins Plugin system and bundled plugins platform/discord Discord bot adapter P3 Low — cosmetic, nice to have needs-decision Awaiting maintainer decision before any implementation 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 labels Aug 16, 2026
@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference, author can ignore or act on any point.

fix(discord): continuously recover missed gateway messages

  1. Admission behavior change: in free-response channels, a human reply that mentions another bot (but not Hermes) is now admitted (other_bots_mentioned and not raw_self_mention and (author_is_bot or not is_free_response or message.type != reply)). The intent is clear and tested, but this widens the bots response surface — Hermes will now join conversations directed at other bots in free-response channels. Consider an additional guard (e.g. only when the replied-to message is Hermes own, or when the reply reads like a question).
  2. Default interval_seconds: 60 with a 6-hour window and a 100-message per-pass limit means a full window walk every minute — repeatedly paging through Discord history could hit API rate limits on busy channels. Consider a higher default, or backing off when a pass found nothing new.
  3. _dispatch_recovered_message(..., claim=True) claims before dispatch — if _handle_message subsequently fails, is the claim rolled back? If not, a failed recovered dispatch permanently loses the message (it would be skipped by both the live path and later recovery passes). Verify the claim is success-only.
  4. The periodic loop coalesces by design (_ensure_missed_message_backfill_task returns the in-flight task without awaiting), so an in-flight scan skips that tick; effective cadence is "at most once per interval" — matches the docstring, just flagging as intended.

andrexibiza added a commit to andrexibiza/hermes-agent that referenced this pull request Aug 19, 2026
Reclassify closed packet-era PRs as historical evidence, promote NousResearch#81388 and NousResearch#87700 as current successors, and correct current state to 1 unwired / 10 blocked / 31 gap. Preserve the canonical 42-row contract digest and keep forbidden god-file candidates blocked rather than laundering them into accepted implementation paths.

Depends on NousResearch#90307
Part of NousResearch#79564
Part of NousResearch#78647

Signed-off-by: Andrex Ibiza, MBA <andrexibiza@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have platform/discord Discord bot adapter 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/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants