Skip to content

fix(discord): hydrate channel context when replying to a message - #49212

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-9ebc724b
Jun 19, 2026
Merged

fix(discord): hydrate channel context when replying to a message#49212
teknium1 merged 2 commits into
mainfrom
hermes/hermes-9ebc724b

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Replying to a message in a Discord channel now hydrates the surrounding channel context, the same way threads do — closing the gap where a reply received only the short [Replying to: "..."] snippet with no conversational context.

Root cause: the history-backfill gate in _handle_message fired only for mention-gated channels (_has_mention_gap) and threads (is_thread). A reply in a free-response (threads-off, non-mention) channel matched neither, so _fetch_channel_context was never called and the agent saw no channel history around the replied-to message.

Changes

  • plugins/platforms/discord/adapter.py:
    • Backfill gate now also fires on any reply (message.reference is not None), passing the replied-to message as an anchor.
    • _fetch_channel_context gains an optional reply_target: when set, a second backward scan ends at that message so the agent sees the exchange around what the user pointed at — even when the target sits before the self-message partition. Windows merge chronologically and de-dup by message id, under separate [Context around the replied-to message] / [Recent channel messages] headers.
    • Hardening: non-conversational status bumps are now skipped before the self-message partition check (a delayed status bump can't be mistaken for the last bot turn); author-name resolution is defensive against partial/deleted authors.

Validation

Before After
Reply in free-response channel only [Replying to: ...] snippet full _fetch_channel_context hydration
Reply to older message (behind partition) not reachable surfaced via reply-anchored window
Plain message in free channel no backfill no backfill (unchanged)

tests/gateway/test_discord_free_response.py: 43 passed (4 new — reply-anchored hydration, dedup when target already in primary window, gate fires on reply in free channel, plain free-channel message still skips). Full Discord gateway suite green.

Infographic

discord-reply-context-hydration

Replying to a message in a free-response (non-mention, threads-off)
channel previously received only the 500-char "[Replying to: ...]"
snippet — the history-backfill gate fired only for mention-gated
channels and threads, so a reply got no surrounding channel context.

Replies now route through the same _fetch_channel_context hydration
that threads use. When the user replied to a specific (often older)
message, a reply-anchored window is scanned ending at that message so
the agent sees the exchange around what was pointed at, even when the
target sits before the self-message partition. The two windows are
merged chronologically and de-duplicated by message id.

Also hardens the recent-window scan to skip non-conversational status
bumps before the self-message partition check, and makes author-name
resolution defensive against partial/deleted authors.
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-9ebc724b vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 11170 on HEAD, 11168 on base (🆕 +2)

🆕 New issues (1):

Rule Count
unresolved-attribute 1
First entries
tests/gateway/test_discord_free_response.py:1128: [unresolved-attribute] unresolved-attribute: Attribute `kwargs` is not defined on `None` in union `_Call | None`

✅ Fixed issues: none

Unchanged: 5854 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

…(discord.Message)

The e2e suite stubs the discord module, so discord.Message is a MagicMock
and isinstance(_resolved, discord.Message) raises 'isinstance() arg 2 must
be a type'. Any object with an int .id works as a scan anchor, so resolve
the reply target by duck-typing on .id and fall back to a _Snowflake from
the reference message_id.
@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 Jun 19, 2026
@teknium1
teknium1 merged commit ba49fb5 into main Jun 19, 2026
34 checks passed
@teknium1
teknium1 deleted the hermes/hermes-9ebc724b branch June 19, 2026 19:03
gnalvesteffer pushed a commit to gnalvesteffer/hermes-agent that referenced this pull request Jun 19, 2026
…sResearch#49212)

* fix(discord): hydrate channel context when replying to a message

Replying to a message in a free-response (non-mention, threads-off)
channel previously received only the 500-char "[Replying to: ...]"
snippet — the history-backfill gate fired only for mention-gated
channels and threads, so a reply got no surrounding channel context.

Replies now route through the same _fetch_channel_context hydration
that threads use. When the user replied to a specific (often older)
message, a reply-anchored window is scanned ending at that message so
the agent sees the exchange around what was pointed at, even when the
target sits before the self-message partition. The two windows are
merged chronologically and de-duplicated by message id.

Also hardens the recent-window scan to skip non-conversational status
bumps before the self-message partition check, and makes author-name
resolution defensive against partial/deleted authors.

* fix(discord): duck-type reply-target resolution instead of isinstance(discord.Message)

The e2e suite stubs the discord module, so discord.Message is a MagicMock
and isinstance(_resolved, discord.Message) raises 'isinstance() arg 2 must
be a type'. Any object with an int .id works as a scan anchor, so resolve
the reply target by duck-typing on .id and fall back to a _Snowflake from
the reference message_id.
TheoLong added a commit to TheoLong/hermes-agent that referenced this pull request Jun 21, 2026
…its)

Rebased onto 8a506ed. Stack now 6 commits:
- Dropped kanban auto-subscribe (SUPERSEDED by upstream f8d8f04, more
  complete + config-gated; parent-task inheritance gap retained for a
  follow-up PR).
- Resolved discord reply-context conflict against NousResearch#49212 (kept upstream's
  own-message distinction + our active fetch_messages capability).
- Documented prior VIRTUAL_ENV drop (superseded by _HERMES_PROVIDER_ENV_BLOCKLIST).
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…sResearch#49212)

* fix(discord): hydrate channel context when replying to a message

Replying to a message in a free-response (non-mention, threads-off)
channel previously received only the 500-char "[Replying to: ...]"
snippet — the history-backfill gate fired only for mention-gated
channels and threads, so a reply got no surrounding channel context.

Replies now route through the same _fetch_channel_context hydration
that threads use. When the user replied to a specific (often older)
message, a reply-anchored window is scanned ending at that message so
the agent sees the exchange around what was pointed at, even when the
target sits before the self-message partition. The two windows are
merged chronologically and de-duplicated by message id.

Also hardens the recent-window scan to skip non-conversational status
bumps before the self-message partition check, and makes author-name
resolution defensive against partial/deleted authors.

* fix(discord): duck-type reply-target resolution instead of isinstance(discord.Message)

The e2e suite stubs the discord module, so discord.Message is a MagicMock
and isinstance(_resolved, discord.Message) raises 'isinstance() arg 2 must
be a type'. Any object with an int .id works as a scan anchor, so resolve
the reply target by duck-typing on .id and fall back to a _Snowflake from
the reference message_id.
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
…sResearch#49212)

* fix(discord): hydrate channel context when replying to a message

Replying to a message in a free-response (non-mention, threads-off)
channel previously received only the 500-char "[Replying to: ...]"
snippet — the history-backfill gate fired only for mention-gated
channels and threads, so a reply got no surrounding channel context.

Replies now route through the same _fetch_channel_context hydration
that threads use. When the user replied to a specific (often older)
message, a reply-anchored window is scanned ending at that message so
the agent sees the exchange around what was pointed at, even when the
target sits before the self-message partition. The two windows are
merged chronologically and de-duplicated by message id.

Also hardens the recent-window scan to skip non-conversational status
bumps before the self-message partition check, and makes author-name
resolution defensive against partial/deleted authors.

* fix(discord): duck-type reply-target resolution instead of isinstance(discord.Message)

The e2e suite stubs the discord module, so discord.Message is a MagicMock
and isinstance(_resolved, discord.Message) raises 'isinstance() arg 2 must
be a type'. Any object with an int .id works as a scan anchor, so resolve
the reply target by duck-typing on .id and fall back to a _Snowflake from
the reference message_id.
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…sResearch#49212)

* fix(discord): hydrate channel context when replying to a message

Replying to a message in a free-response (non-mention, threads-off)
channel previously received only the 500-char "[Replying to: ...]"
snippet — the history-backfill gate fired only for mention-gated
channels and threads, so a reply got no surrounding channel context.

Replies now route through the same _fetch_channel_context hydration
that threads use. When the user replied to a specific (often older)
message, a reply-anchored window is scanned ending at that message so
the agent sees the exchange around what was pointed at, even when the
target sits before the self-message partition. The two windows are
merged chronologically and de-duplicated by message id.

Also hardens the recent-window scan to skip non-conversational status
bumps before the self-message partition check, and makes author-name
resolution defensive against partial/deleted authors.

* fix(discord): duck-type reply-target resolution instead of isinstance(discord.Message)

The e2e suite stubs the discord module, so discord.Message is a MagicMock
and isinstance(_resolved, discord.Message) raises 'isinstance() arg 2 must
be a type'. Any object with an int .id works as a scan anchor, so resolve
the reply target by duck-typing on .id and fall back to a _Snowflake from
the reference message_id.
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…sResearch#49212)

* fix(discord): hydrate channel context when replying to a message

Replying to a message in a free-response (non-mention, threads-off)
channel previously received only the 500-char "[Replying to: ...]"
snippet — the history-backfill gate fired only for mention-gated
channels and threads, so a reply got no surrounding channel context.

Replies now route through the same _fetch_channel_context hydration
that threads use. When the user replied to a specific (often older)
message, a reply-anchored window is scanned ending at that message so
the agent sees the exchange around what was pointed at, even when the
target sits before the self-message partition. The two windows are
merged chronologically and de-duplicated by message id.

Also hardens the recent-window scan to skip non-conversational status
bumps before the self-message partition check, and makes author-name
resolution defensive against partial/deleted authors.

* fix(discord): duck-type reply-target resolution instead of isinstance(discord.Message)

The e2e suite stubs the discord module, so discord.Message is a MagicMock
and isinstance(_resolved, discord.Message) raises 'isinstance() arg 2 must
be a type'. Any object with an int .id works as a scan anchor, so resolve
the reply target by duck-typing on .id and fall back to a _Snowflake from
the reference message_id.
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…sResearch#49212)

* fix(discord): hydrate channel context when replying to a message

Replying to a message in a free-response (non-mention, threads-off)
channel previously received only the 500-char "[Replying to: ...]"
snippet — the history-backfill gate fired only for mention-gated
channels and threads, so a reply got no surrounding channel context.

Replies now route through the same _fetch_channel_context hydration
that threads use. When the user replied to a specific (often older)
message, a reply-anchored window is scanned ending at that message so
the agent sees the exchange around what was pointed at, even when the
target sits before the self-message partition. The two windows are
merged chronologically and de-duplicated by message id.

Also hardens the recent-window scan to skip non-conversational status
bumps before the self-message partition check, and makes author-name
resolution defensive against partial/deleted authors.

* fix(discord): duck-type reply-target resolution instead of isinstance(discord.Message)

The e2e suite stubs the discord module, so discord.Message is a MagicMock
and isinstance(_resolved, discord.Message) raises 'isinstance() arg 2 must
be a type'. Any object with an int .id works as a scan anchor, so resolve
the reply target by duck-typing on .id and fall back to a _Snowflake from
the reference message_id.
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 type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants