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

2open issues
2open PRs
7items grouped
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.

These 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

  • Issue#10554session_search should be scoped to the current chat by default in gateway conversations
  • Issue#30498Bug: re-anchor ambiguous gateway continuations before session_search

Open PRs2

  • PR#30502fix(agent): re-anchor before session_search for ambiguous continuations
  • PR#62138fix(session-search): scope gateway recall to current chat

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #350 fix(gateway): match _quick_key to _generate_session_key for WhatsApp DMs

    merged to main

    files: gateway/run.py, tests/gateway/test_session.py

    Show 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
  • Closed#349WhatsApp DM interrupt silently fails due to session key mismatch in _quick_key
  • Closed#1056[Bug]: Telegram and Discord DMs share a single global session (Context Bleeding)
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.00 means 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 Lab

At 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.

For your agent

The same facts, typed.