From 06a7d19f986fa744b8e48cca46ecc300a5074135 Mon Sep 17 00:00:00 2001 From: teknium1 Date: Sun, 15 Mar 2026 23:08:56 -0700 Subject: [PATCH] fix(gateway): isolate group sessions per user Include participant identifiers in non-DM session keys when available so group and channel conversations no longer share one transcript across every active user in the chat. --- gateway/session.py | 17 ++++++++---- tests/gateway/test_session.py | 52 +++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/gateway/session.py b/gateway/session.py index 1778c2e4..3dafa178 100644 --- a/gateway/session.py +++ b/gateway/session.py @@ -328,7 +328,9 @@ def build_session_key(source: SessionSource) -> str: Group/channel rules: - chat_id identifies the parent group/channel. + - user_id/user_id_alt isolates participants within that parent chat when available. - thread_id differentiates threads within that parent chat. + - Without participant identifiers, messages fall back to one shared session per chat. - Without identifiers, messages fall back to one session per platform/chat_type. """ platform = source.platform.value @@ -340,13 +342,18 @@ def build_session_key(source: SessionSource) -> str: if source.thread_id: return f"agent:main:{platform}:dm:{source.thread_id}" return f"agent:main:{platform}:dm" + + participant_id = source.user_id_alt or source.user_id + key_parts = ["agent:main", platform, source.chat_type] + if source.chat_id: - if source.thread_id: - return f"agent:main:{platform}:{source.chat_type}:{source.chat_id}:{source.thread_id}" - return f"agent:main:{platform}:{source.chat_type}:{source.chat_id}" + key_parts.append(source.chat_id) if source.thread_id: - return f"agent:main:{platform}:{source.chat_type}:{source.thread_id}" - return f"agent:main:{platform}:{source.chat_type}" + key_parts.append(source.thread_id) + if participant_id: + key_parts.append(str(participant_id)) + + return ":".join(key_parts) class SessionStore: diff --git a/tests/gateway/test_session.py b/tests/gateway/test_session.py index cd0104ac..e9e629fe 100644 --- a/tests/gateway/test_session.py +++ b/tests/gateway/test_session.py @@ -369,6 +369,29 @@ class TestWhatsAppDMSessionKeyConsistency: ) assert store._generate_session_key(source) == build_session_key(source) + def test_store_creates_distinct_group_sessions_per_user(self, store): + first = SessionSource( + platform=Platform.DISCORD, + chat_id="guild-123", + chat_type="group", + user_id="alice", + user_name="Alice", + ) + second = SessionSource( + platform=Platform.DISCORD, + chat_id="guild-123", + chat_type="group", + user_id="bob", + user_name="Bob", + ) + + first_entry = store.get_or_create_session(first) + second_entry = store.get_or_create_session(second) + + assert first_entry.session_key == "agent:main:discord:group:guild-123:alice" + assert second_entry.session_key == "agent:main:discord:group:guild-123:bob" + assert first_entry.session_id != second_entry.session_id + def test_telegram_dm_includes_chat_id(self): """Non-WhatsApp DMs should also include chat_id to separate users.""" source = SessionSource( @@ -398,6 +421,24 @@ class TestWhatsAppDMSessionKeyConsistency: key = build_session_key(source) assert key == "agent:main:discord:group:guild-123" + def test_group_sessions_are_isolated_per_user_when_user_id_present(self): + first = SessionSource( + platform=Platform.DISCORD, + chat_id="guild-123", + chat_type="group", + user_id="alice", + ) + second = SessionSource( + platform=Platform.DISCORD, + chat_id="guild-123", + chat_type="group", + user_id="bob", + ) + + assert build_session_key(first) == "agent:main:discord:group:guild-123:alice" + assert build_session_key(second) == "agent:main:discord:group:guild-123:bob" + assert build_session_key(first) != build_session_key(second) + def test_group_thread_includes_thread_id(self): """Forum-style threads need a distinct session key within one group.""" source = SessionSource( @@ -409,6 +450,17 @@ class TestWhatsAppDMSessionKeyConsistency: key = build_session_key(source) assert key == "agent:main:telegram:group:-1002285219667:17585" + def test_group_thread_sessions_are_isolated_per_user(self): + source = SessionSource( + platform=Platform.TELEGRAM, + chat_id="-1002285219667", + chat_type="group", + thread_id="17585", + user_id="42", + ) + key = build_session_key(source) + assert key == "agent:main:telegram:group:-1002285219667:17585:42" + class TestSessionStoreEntriesAttribute: """Regression: /reset must access _entries, not _sessions."""