Gateway DM session isolation and scoping issues
NousResearch/hermes-agent · cluster 349 · agent
Data as of
4 open items. Fix merged in #350. Check it covers your case.
The short answer
- Known?
- Yes. 7 related items grouped here, 2 issues already closed.
- Fix event
- A fix was merged to main in PR #350. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PR #350. That is separate from the fix event. - Still open
- 2 issues and 2 PRs.
Next step: read #350, then check whether your report is one of the 2 still open.
Read #350Pick up a Lab taskThese items concern session scoping, isolation, and key-matching problems in gateway DM contexts, including cross-platform context bleeding between Telegram and Discord, WhatsApp session key mismatches, and efforts to restrict session search and recall to the current chat.
Open issues2
Open PRs2
Fixes on record
-
#350 fix(gateway): match _quick_key to _generate_session_key for WhatsApp DMs
files:
gateway/run.py,tests/gateway/test_session.pyShow the diff
-
gateway/run.py@@ -637,11 +637,12 @@ async def _handle_message(self, event: MessageEvent) -> Optional[str]: # PRIORITY: If an agent is already running for this session, interrupt it # immediately. This is before command parsing to minimize latency -- the # user's "stop" message reaches the agent as fast as possible. - _quick_key = ( - f"agent:main:{source.platform.value}:{source.chat_type}:{source.chat_id}" - if source.chat_type != "dm" - else f"agent:main:{source.platform.value}:dm" - ) + if source.chat_type != "dm": + _quick_key = f"agent:main:{source.platform.value}:{source.chat_type}:{source.chat_id}" + elif source.platform.value == "whatsapp" and source.chat_id: + _quick_key = f"agent:main:{source.platform.value}:dm:{source.chat_id}" + else: + _quick_key = f"agent:main:{source.platform.value}:dm" if _quick_key in self._running_agents: running_agent = self._running_agents[_quick_key] logger.debug("PRIORITY interrupt for session %s", _quick_key[:20]) @@ -1361,8 +1362,12 @@ async def _handle_compress_command(self, event: MessageEvent) -> str: async def _handle_usage_command(self, event: MessageEvent) -> str: """Handle /usage command -- show token usage for the session's last agent run.""" source = event.source - session_key = f"agent:main:{source.platform.value}:" + \ - (f"dm" if source.chat_type == "dm" else f"{source.chat_type}:{source.chat_id}") + if source.chat_type != "dm": + session_key = f"agent:main:{source.platform.value}:{source.chat_type}:{source.chat_id}" + elif source.platform.value == "whatsapp" and source.chat_id: + session_key = f"agent:main:{source.platform.value}:dm:{source.chat_id}" + else: + session_key = f"agent:main:{source.platform.value}:dm" agent = self._running_agents.get(session_key) if agent and hasattr(agent, "session_total_tokens") and agent.session_api_calls > 0: -
tests/gateway/test_session.py@@ -314,6 +314,88 @@ def test_rewrite_with_empty_list(self, store): assert reloaded == [] +class TestWhatsAppDMSessionKeyConsistency: + """Regression: inline session-key construction in handle_message must match + _generate_session_key for WhatsApp DMs, which include chat_id.""" + + @pytest.fixture() + def store(self, tmp_path): + config = GatewayConfig() + with patch("gateway.session.SessionStore._ensure_loaded"): + s = SessionStore(sessions_dir=tmp_path, config=config) + s._db = None + s._loaded = True + return s + + def _build_quick_key(self, source: SessionSource) -> str: + """Reproduce the _quick_key logic from gateway/run.py handle_message.""" + if source.chat_type != "dm": + return f"agent:main:{source.platform.value}:{source.chat_type}:{source.chat_id}" + elif source.platform.value == "whatsapp" and source.chat_id: + return f"agent:main:{source.platform.value}:dm:{source.chat_id}" + else: + return f"agent:main:{source.platform.value}:dm" + + def _build_usage_key(self, source: SessionSource) -> str: + """Reproduce the session_key logic from _handle_usage_command.""" + if source.chat_type != "dm": + return f"agent:main:{source.platform.value}:{source.chat_type}:{source.chat_id}" + elif source.platform.value == "whatsapp" and source.chat_id: + return f"agent:main:{source.platform.value}:dm:{source.chat_id}" + else: + return f"agent:main:{source.platform.value}:dm" + + def test_whatsapp_dm_quick_key_includes_chat_id(self, store): + source = SessionSource( + platform=Platform.WHATSAPP, + chat_id="[redacted:email]", + chat_type="dm", + user_name="Phone User", + ) + real_key = store._generate_session_key(source) + quick_key = self._build_quick_key(source) + assert quick_key == real_key + assert "[redacted:email]" in quick_key + + def test_whatsapp_dm_usage_key_includes_chat_id(self, store): + source = SessionSource( + platform=Platform.WHATSAPP, + chat_id="[redacted:email]", + chat_type="dm", + user_name="Phone User", + ) + real_key = store._generate_session_key(source) + usage_key = self._build_usage_key(source) + assert usage_key == real_key + assert "[redacted:email]" in usage_key + + def test_telegram_dm_key_unchanged(self, store): + """Non-WhatsApp DMs should still omit chat_id (single owner DM).""" + source = SessionSource( + platform=Platform.TELEGRAM, + chat_id="99", + chat_type="dm", + ) + real_key = store._generate_session_key(source) + quick_key = self._build_quick_key(source) + usage_key = self._build_usage_key(source) + assert quick_key == real_key == "agent:main:telegram:dm" + assert usage_key == real_key + + def test_discord_group_key_unchanged(self, store): + """Group/channel keys should be unaffected by the fix.""" + source = SessionSource( + platform=Platform.DISCORD, + chat_id="guild-123", + chat_type="group", + ) + real_key = store._generate_session_key(source) + quick_key = self._build_quick_key(source) + usage_key = self._build_usage_key(source) + assert quick_key == real_key == "agent:main:discord:group:guild-123" + assert usage_key == real_key + + class TestSessionStoreEntriesAttribute: """Regression: /reset must access _entries, not _sessions."""
-
Closed issues · 2
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.19: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- agent
- Merged PRs
- #350
- Closed issues
- 2
- Attempts
- 0 PRs closed without merging
- Fix link checked
- 4 Mar 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.