# NousResearch/hermes-agent · Gateway DM session isolation and scoping issues

## Open now

- 2 issues, 2 PRs open on GitHub.
- #10554: session_search should be scoped to the current chat by default in gateway conversations
- #30498: Bug: re-anchor ambiguous gateway continuations before session_search
- PR #30502: fix(agent): re-anchor before session_search for ambiguous continuations
- PR #62138: fix(session-search): scope gateway recall to current chat

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.

## TL;DR

- 7 member(s): 2 open issue(s), 2 closed issue(s), 2 open PR(s), 1 merged PR(s), 0 PR(s) closed without merging.
- A fix was merged to main in PR #350. Verify it addresses your case.
- **Recommended:** read PR #350 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-350`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): 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 member 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.

## Open related issues

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

## Closed / duplicate issues

- #349: WhatsApp DM interrupt silently fails due to session key mismatch in _quick_key
- #1056: \[Bug\]: Telegram and Discord DMs share a single global session (Context Bleeding)

## Prior & competing PRs

- **#350: fix(gateway): match _quick_key to _generate_session_key for WhatsApp DMs**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `gateway/run.py`, `tests/gateway/test_session.py`
  - fix hunks:
  - `gateway/run.py`

    ```diff
    @@ -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])
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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."""
     
    ```
- **#30502: fix(agent): re-anchor before session_search for ambiguous continuations**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `#70105`, `#70105`, `#70105`, `#70105`
  - files: `agent/prompt_builder.py`, `tests/agent/test_prompt_builder.py`
- **#62138: fix(session-search): scope gateway recall to current chat**. open
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `3ab4977ff`
  - files: `agent/agent_runtime_helpers.py`, `agent/tool_executor.py`, `gateway/run.py`, `hermes_state.py`, `tests/gateway/test_background_command.py`, `tests/run_agent/test_token_persistence_non_cli.py`, `tests/tools/test_session_search.py`, `tools/session_search_tool.py`, `website/docs/user-guide/sessions.md`, `website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/sessions.md`

## Fix status

- Merged to main in PR #350 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-03-04`. This confirmation is old and may be out of date (a revert/reopen would not have invalidated it here); re-verify.
- Subsystem: `agent`
