hermes - 💡(How to fix) Fix [Bug] Telegram DM topic response routed to All Messages after session split

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Code Example

# gateway/run.py  ~line 7705
agent_result = await self._run_agent(
    ...
    source=source,          # ← original inbound source
    session_id=session_entry.session_id,
    ...
)

# ~line 7760
logger.info("response ready: platform=%s chat=%s ...", ...)

# ~line 7900+ (inside _handle_message_with_agent → _process_turn)
# Response text is returned up-stack, eventually passed to:
#   adapter.send(chat_id, content=response, reply_to=..., metadata=thread_meta)
# where thread_meta comes from:
#   _thread_metadata_for_source(event.source, self._reply_anchor_for_event(event))
# If event.source.thread_id is None → metadata has no direct_messages_topic_id → All Messages

---

# In gateway/run.py, inside the session-split block (~line 15817):
if _session_was_split and self._is_telegram_topic_lane(source):
    # Ensure the response metadata routes back to the correct DM topic.
    # If source.thread_id was lost, look it up from the binding we just refreshed.
    if source.thread_id is None:
        binding = self._session_db.get_telegram_topic_binding(
            chat_id=str(source.chat_id),
            thread_id=None,  # lookup by session_key instead
        )
        if binding:
            source.thread_id = binding.thread_id
RAW_BUFFERClick to expand / collapse

Bug Description

When context compression triggers a session split during a Telegram DM topic conversation, the agent's final response is delivered to the "All Messages" (root) thread instead of the active DM topic thread.

This is a related but distinct issue from #20470 (which only fixed topic→session binding persistence).


Root-Cause Analysis

  1. During a turn, run_agent.py mid-turn compression may create a new child session (agent.session_id changes).
  2. gateway/run.py detects this at Session split detected: old_id → new_id (compression) and updates session_entry.session_id.
  3. Bug: the response is sent using the original inbound event.source for thread metadata (_reply_anchor_for_event(event), _thread_metadata_for_source(event.source, …)).
    If that inbound event had thread_id=None — which happens for synthetic / recovered events, or if the original source object lost its thread_id during the split — the Telegram adapter receives no message_thread_id.
  4. Telegram adapter defaults to message_thread_id=None → message lands in the General ("All Messages") topic.

Code Path

# gateway/run.py  ~line 7705
agent_result = await self._run_agent(
    ...
    source=source,          # ← original inbound source
    session_id=session_entry.session_id,
    ...
)

# ~line 7760
logger.info("response ready: platform=%s chat=%s ...", ...)

# ~line 7900+ (inside _handle_message_with_agent → _process_turn)
# Response text is returned up-stack, eventually passed to:
#   adapter.send(chat_id, content=response, reply_to=..., metadata=thread_meta)
# where thread_meta comes from:
#   _thread_metadata_for_source(event.source, self._reply_anchor_for_event(event))
# If event.source.thread_id is None → metadata has no direct_messages_topic_id → All Messages

Affected Scenarios

  • Synthetic messages injected during a long-running task before compression fires (e.g. "⏳ Still working..." delivered correctly to topic, but the final response after split goes to All Messages).
  • Any tool execution that crosses the compression boundary during a DM topic session.
  • Session resume after gateway restart where the resumed event loses thread context.

Proposed Fix

After a session split is detected, recover the active Telegram DM topic thread ID from the SessionDB binding and inject it into the response metadata before calling adapter.send.

# In gateway/run.py, inside the session-split block (~line 15817):
if _session_was_split and self._is_telegram_topic_lane(source):
    # Ensure the response metadata routes back to the correct DM topic.
    # If source.thread_id was lost, look it up from the binding we just refreshed.
    if source.thread_id is None:
        binding = self._session_db.get_telegram_topic_binding(
            chat_id=str(source.chat_id),
            thread_id=None,  # lookup by session_key instead
        )
        if binding:
            source.thread_id = binding.thread_id

A cleaner alternative: persist the event.source (or at least its thread_id) before _run_agent begins, and restore it after split.


Environment

  • Hermes version: v0.14.0
  • Platform: Telegram (DM topics)
  • macOS 15.6.1

Related Issues

  • #20470 — Telegram DM topic binding not refreshed after compression split (fixed in #23244)
  • #14238 — Pending response lost when session split occurs at response boundary
  • #27114 — Telegram DM replies fail when message thread id is not explicitly set

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - 💡(How to fix) Fix [Bug] Telegram DM topic response routed to All Messages after session split