Bug Description
When a new Telegram message arrives for an already-active session, Hermes marks the running session as interrupted and queues the new message in _pending_messages. However, the running task still sends its old response before checking whether it has been interrupted. After that, the queued message is processed and produces another response.
This causes duplicate or near-duplicate visible replies, especially when the two prompts are closely related.
Telegram flood control makes the symptom worse because the stale response may be delayed and arrive noticeably later, making it look like Hermes repeated itself.
Steps to Reproduce
- Start a Telegram conversation with Hermes in a single DM or single session thread.
- Send a prompt that takes some time to process, especially one likely to produce a long answer.
- Before Hermes has fully finished sending the previous reply, send another related prompt in the same session.
- Observe that Hermes may first send a stale reply from the earlier turn and then send a second reply for the newer turn.
- Under Telegram flood control, the stale reply may arrive late, making the two responses look like duplicate answers.
Expected Behavior
If a running session has been interrupted by a newer incoming message, the stale response from the older turn should not be sent. Hermes should suppress the old reply and continue with the queued newer message only.
Actual Behavior
The interrupted older turn still sends its response, and then the queued newer message is processed and also sends a response. This creates duplicate or highly overlapping visible replies.
Affected Component
Gateway (Telegram/Discord/Slack/WhatsApp)
Messaging Platform (if gateway-related)
Telegram
Operating System
Ubuntu 24.04.4 LTS (Noble Numbat) Kernel: Linux 6.17.0-1007-aws
Python Version
Python 3.11.15
Hermes Version
0.8.0
Relevant Logs / Traceback
Supporting Evidence From Logs
Observed in /home/ubuntu/.hermes/logs/agent.log for Telegram DM chat 5769327191:
- 06:42:57 inbound: Hermes Live 重复回答
- 06:43:09 response ready: 514 chars
- 06:44:08 Sending response (111 chars)
- 06:44:13 inbound again: Hermes Live 重复回答
Also earlier in the same DM session:
- 06:17:52 response ready: 2658 chars
- 06:19:36 new inbound arrives before the prior turn has fully cleared
- 06:22:06 another response ready: 2871 chars
- 06:29:16 another inbound arrives while the session is still busy
This matches the active-session interrupt path rather than duplicate execution by two gateway processes.
Flood Control Interaction
Telegram flood control is present in the same log window, e.g.:
- Telegram flood control on send (attempt 1/3), retrying in ...
- Telegram flood control, waiting ...
Flood control is not the primary bug, but it amplifies the user-visible symptom by delaying stale sends.
Root Cause Analysis (optional)
The interrupt path is incomplete:
- gateway/platforms/base.py:1507-1559
- if a session is active, a new incoming message is stored in _pending_messages[session_key]
- the interrupt flag is set via self._active_sessions[session_key].set()
- gateway/platforms/base.py:1628-1718
- _process_message_background() awaits self._message_handler(event)
- then immediately sends the response with _send_with_retry()
- there is no check for interrupt_event.is_set() / pending replacement before sending
- gateway/platforms/base.py:1831-1845
- only after sending does the code pop _pending_messages and process the queued event
So the stale response from the interrupted turn is still delivered, followed by the response for the newer turn.
Why this is the definitive cause
The current code path deterministically allows this sequence:
- message A starts processing
- message B arrives while the same session is active
- B is queued and interrupt flag is set
- A still sends its response
- queued B is then processed and also sends a response
No duplicate gateway instance is required, and no duplicate inbound delivery is required.
Proposed Fix (optional)
Suggested Fix
In gateway/platforms/base.py, after response = await self._message_handler(event) and before any send call:
- check whether interrupt_event.is_set() or whether session_key in self._pending_messages
- if true, skip sending the stale response from the old turn
- proceed directly to the queued pending event
Possible patch shape:
response = await self._message_handler(event)
if interrupt_event.is_set() or session_key in self._pending_messages:
logger.info("[%s] Suppressing stale response for interrupted session %s", self.name, session_key)
response = None
A more careful version should distinguish true cancel/replacement from benign queued photo-burst behavior.
Are you willing to submit a PR for this?
Bug Description
When a new Telegram message arrives for an already-active session, Hermes marks the running session as interrupted and queues the new message in _pending_messages. However, the running task still sends its old response before checking whether it has been interrupted. After that, the queued message is processed and produces another response.
This causes duplicate or near-duplicate visible replies, especially when the two prompts are closely related.
Telegram flood control makes the symptom worse because the stale response may be delayed and arrive noticeably later, making it look like Hermes repeated itself.
Steps to Reproduce
Expected Behavior
If a running session has been interrupted by a newer incoming message, the stale response from the older turn should not be sent. Hermes should suppress the old reply and continue with the queued newer message only.
Actual Behavior
The interrupted older turn still sends its response, and then the queued newer message is processed and also sends a response. This creates duplicate or highly overlapping visible replies.
Affected Component
Gateway (Telegram/Discord/Slack/WhatsApp)
Messaging Platform (if gateway-related)
Telegram
Operating System
Ubuntu 24.04.4 LTS (Noble Numbat) Kernel: Linux 6.17.0-1007-aws
Python Version
Python 3.11.15
Hermes Version
0.8.0
Relevant Logs / Traceback
Root Cause Analysis (optional)
The interrupt path is incomplete:
So the stale response from the interrupted turn is still delivered, followed by the response for the newer turn.
Why this is the definitive cause
The current code path deterministically allows this sequence:
No duplicate gateway instance is required, and no duplicate inbound delivery is required.
Proposed Fix (optional)
Suggested Fix
In gateway/platforms/base.py, after response = await self._message_handler(event) and before any send call:
Possible patch shape:
response = await self._message_handler(event)
if interrupt_event.is_set() or session_key in self._pending_messages:
logger.info("[%s] Suppressing stale response for interrupted session %s", self.name, session_key)
response = None
A more careful version should distinguish true cancel/replacement from benign queued photo-burst behavior.
Are you willing to submit a PR for this?