# Multi-User Session Handling - Design Document **Date**: January 2025 **Author**: BeatDebate Team **Status**: ✅ **COMPLETED** **Review Status**: Pending --- ## 1. Problem Statement **Objective**: Implement secure multi-user session handling for BeatDebate to prevent context bleeding and ensure proper session isolation when deployed on HuggingFace Spaces or any multi-user environment. **Current State**: BeatDebate has a critical concurrency vulnerability where: - `SessionManagerService` uses a shared in-memory dictionary (`self.session_store = {}`) for all users - `BeatDebateChatInterface` maintains a single global session ID (`self.session_id = str(uuid.uuid4())`) - Multiple users can experience context bleeding, session collision, and data corruption **Critical Issues**: 1. **Context Bleeding**: Users receive recommendations based on other users' conversation histories 2. **Session Collision**: Follow-up queries may fail due to contaminated session context 3. **Data Corruption**: Concurrent access to shared session state leads to unpredictable behavior 4. **Security Risk**: User preferences and interaction patterns leak between sessions **Value Proposition**: - **Multi-User Safety**: Each user gets isolated, private session context - **Reliable Follow-ups**: "More tracks" and "similar to these" work correctly per user - **Production Ready**: Safe deployment to HuggingFace Spaces and other shared environments - **User Experience**: Consistent, predictable behavior for concurrent users --- ## 2. Goals & Non-Goals ### ✅ In Scope - **Frontend Session Management**: Use `gr.State` to maintain per-user session IDs - **Session ID Propagation**: Pass user-specific session IDs from frontend to backend - **Session Context Isolation**: Ensure each user's conversation history stays separate - **Follow-up Query Support**: Maintain proper context for "more tracks" type queries - **Backwards Compatibility**: Existing backend session logic remains functional - **HuggingFace Spaces Compatibility**: Works correctly in shared deployment environments ### ❌ Out of Scope (v1) - **Persistent Session Storage**: Sessions still expire when browser is closed - **User Authentication**: No login system, relying on browser session isolation - **Session Migration**: No cross-device session continuity - **Advanced Session Analytics**: No detailed session usage tracking - **Session Sharing**: Users cannot share session contexts --- ## 3. Architecture Overview ### 3.1 Current Architecture (Problematic) ``` ┌─────────────────────────────────────────────────────────────┐ │ HuggingFace Spaces │ │ │ │ ┌─────────────────┐ ┌─────────────────────────────────┐│ │ │ User A │ │ User B ││ │ │ Browser │ │ Browser ││ │ │ │ │ ││ │ └─────────┬───────┘ └──────────┬──────────────────────┘│ │ │ │ │ │ ┌─────────▼───────────────────────▼─────────────────────┐ │ │ │ Gradio Interface │ │ │ │ ❌ SHARED session_id = "global-123" │ │ │ │ ❌ SHARED conversation_history = [] │ │ │ └─────────────────────┬─────────────────────────────────┘ │ │ │ │ │ ┌─────────────────────▼─────────────────────────────────┐ │ │ │ FastAPI Backend │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ │ │ SessionManagerService │ │ │ │ │ │ ❌ SHARED session_store = { │ │ │ │ │ │ "global-123": { │ │ │ │ │ │ // Mixed context from Users A & B │ │ │ │ │ │ } │ │ │ │ │ │ } │ │ │ │ │ └─────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### 3.2 Proposed Architecture (Secure) ``` ┌─────────────────────────────────────────────────────────────┐ │ HuggingFace Spaces │ │ │ │ ┌─────────────────┐ ┌─────────────────────────────────┐│ │ │ User A │ │ User B ││ │ │ Browser │ │ Browser ││ │ │ gr.State: │ │ gr.State: ││ │ │ "user-a-456" │ │ "user-b-789" ││ │ └─────────┬───────┘ └──────────┬──────────────────────┘│ │ │ │ │ │ ┌─────────▼───────────────────────▼─────────────────────┐ │ │ │ Gradio Interface │ │ │ │ ✅ ISOLATED session management per user │ │ │ │ ✅ Session ID passed with each request │ │ │ └─────────────────────┬─────────────────────────────────┘ │ │ │ │ │ ┌─────────────────────▼─────────────────────────────────┐ │ │ │ FastAPI Backend │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ │ │ SessionManagerService │ │ │ │ │ │ ✅ ISOLATED session_store = { │ │ │ │ │ │ "user-a-456": { /* User A context */ }, │ │ │ │ │ │ "user-b-789": { /* User B context */ } │ │ │ │ │ │ } │ │ │ │ │ └─────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 4. Technical Design ### 4.1 Frontend Changes (Gradio Interface) #### 4.1.1 Session State Management **File**: `src/ui/chat_interface.py` **Key Changes**: 1. Remove global `self.session_id` from class initialization 2. Add `gr.State` component to store per-user session ID 3. Update all message processing functions to accept and return session ID 4. Initialize each new user session with unique UUID ```python class BeatDebateChatInterface: def __init__(self, backend_url: str = "http://localhost:8000"): self.backend_url = backend_url self.response_formatter = ResponseFormatter() self.planning_display = PlanningDisplay() # ❌ REMOVE: self.session_id = str(uuid.uuid4()) # ❌ REMOVE: self.conversation_history = [] # This is also global state # Initialize fallback service self.fallback_service = None self._initialize_fallback_service() async def process_message( self, message: str, history: List[Tuple[str, str]], session_id: str # ✅ ADD: Accept session_id as parameter ) -> Tuple[str, List[Tuple[str, str]], str, str]: # ✅ ADD: Return updated session_id """Process message with isolated session context.""" # Pass session_id to backend calls recommendations_response = await self._get_recommendations(message, session_id) # Handle fallback with session isolation if should_fallback: recommendations_response = await self._get_fallback_recommendations( message, trigger_reason, session_id ) # Get potentially updated session_id from backend updated_session_id = recommendations_response.get("session_id", session_id) return "", updated_history, lastfm_player_html, updated_session_id def create_interface(self) -> gr.Blocks: with gr.Blocks(...) as interface: # ✅ ADD: Per-user session state session_id_state = gr.State(value=str(uuid.uuid4())) # UI components... chatbot = gr.Chatbot(...) msg_input = gr.Textbox(...) send_btn = gr.Button(...) player_display = gr.HTML(...) # ✅ MODIFY: Event handlers with session state async def handle_message(message, history, session_id): return await self.process_message(message, history, session_id) send_btn.click( fn=handle_message, inputs=[msg_input, chatbot, session_id_state], # Include session state outputs=[msg_input, chatbot, player_display, session_id_state] # Update session state ) msg_input.submit( fn=handle_message, inputs=[msg_input, chatbot, session_id_state], outputs=[msg_input, chatbot, player_display, session_id_state] ) ``` #### 4.1.2 Backend Request Updates All backend requests must include the user-specific session ID: ```python async def _get_recommendations(self, query: str, session_id: str) -> Optional[Dict]: request_data = { "query": query, "session_id": session_id, # ✅ Pass user-specific session ID "max_recommendations": 10, "include_previews": True, "chat_context": self._get_chat_context() # Note: This still needs refactoring } async def _get_fallback_recommendations( self, query: str, trigger_reason: FallbackTrigger, session_id: str # ✅ Accept session ID ) -> Optional[Dict[str, Any]]: fallback_request = FallbackRequest( query=query, session_id=session_id, # ✅ Include in fallback request chat_context=self._get_chat_context(), trigger_reason=trigger_reason, max_recommendations=10 ) ``` ### 4.2 Backend Verification (SessionManagerService) **File**: `src/services/session_manager_service.py` **Current Implementation Analysis**: ✅ Already session-safe! The backend `SessionManagerService` is already properly designed for multi-user scenarios: ```python class SessionManagerService: def __init__(self, cache_manager=None): self.session_store = {} # This dictionary is keyed by session_id async def create_or_update_session(self, session_id: str, ...): # ✅ Uses session_id as key - already isolated if session_id not in self.session_store: self.session_store[session_id] = { ... } async def get_session_context(self, session_id: str): # ✅ Returns only the specific session's context return self.session_store.get(session_id) ``` **No Changes Required**: The backend is already session-safe. The issue was entirely in the frontend's global session management. ### 4.3 Context Handler Integration **File**: `src/services/components/context_handler.py` **Verification**: ✅ Already session-aware! The context handler properly uses session-specific data: ```python async def process_conversation_history(self, request) -> List[Dict]: # ✅ Already retrieves session-specific history if hasattr(request, 'session_id') and request.session_id: session_context = await self.session_manager.get_session_context(request.session_id) ``` **No Changes Required**: Context handler already works correctly with proper session IDs. --- ## 5. Implementation Plan ### 5.1 Phase 1: Frontend Session Management (Priority: Critical) **Tasks**: 1. **Remove Global State** from `BeatDebateChatInterface` - Remove `self.session_id` from constructor - Remove global `self.conversation_history` (separate issue, lower priority) 2. **Implement gr.State Session Management** - Add `session_id_state = gr.State(value=str(uuid.uuid4()))` to interface - Update `process_message` signature to accept/return session ID - Update all event handlers to pass session state 3. **Update Backend Calls** - Modify `_get_recommendations` to accept session_id parameter - Modify `_get_fallback_recommendations` to accept session_id parameter - Ensure all API calls include user-specific session ID **Files to Modify**: - `src/ui/chat_interface.py` (Primary changes) **Testing Strategy**: - Open multiple browser tabs/windows - Verify each tab gets unique session ID - Test follow-up queries work independently per tab - Ensure no context bleeding between sessions ### 5.2 Phase 2: Global Conversation History Refactoring (Priority: Medium) **Issue**: `self.conversation_history` in chat interface is still global state **Tasks**: 1. Remove global conversation history from frontend 2. Rely entirely on backend session storage for conversation context 3. Update `_get_chat_context()` to retrieve from backend session store ### 5.3 Phase 3: Enhanced Session Monitoring (Priority: Low) **Tasks**: 1. Add session creation/destruction logging 2. Implement session cleanup for memory management 3. Add metrics for concurrent session count --- ## 6. Testing Strategy ### 6.1 Multi-User Simulation Tests **Scenario 1: Concurrent New Sessions** ``` 1. Open 3 browser tabs simultaneously 2. Each should get unique session ID (verify in logs) 3. Make different queries in each tab: - Tab 1: "Songs by Radiohead" - Tab 2: "Electronic music" - Tab 3: "Jazz for studying" 4. Verify each gets appropriate recommendations ``` **Scenario 2: Follow-up Query Isolation** ``` 1. Tab 1: "Songs by Mk.gee" → get Mk.gee recommendations 2. Tab 2: "Electronic music" → get electronic recommendations 3. Tab 1: "More tracks" → should get more Mk.gee, NOT electronic 4. Tab 2: "More tracks" → should get more electronic, NOT Mk.gee ``` **Scenario 3: Session Persistence** ``` 1. Tab 1: "Songs by Beatles" → get Beatles songs 2. Tab 1: "More like these" → should get more Beatles-style 3. Refresh Tab 1 → should get new session ID 4. Tab 1: "More tracks" → should NOT have Beatles context ``` ### 6.2 Performance Tests **Concurrent Load Test**: - Simulate 10+ concurrent users - Verify no session collision - Monitor memory usage of session store - Ensure reasonable response times ### 6.3 Error Handling Tests **Session ID Edge Cases**: - Missing session ID in request - Invalid session ID format - Backend should gracefully create new session --- ## 7. Deployment Considerations ### 7.1 HuggingFace Spaces Compatibility **Memory Management**: - Session store grows with concurrent users - Implement session timeout/cleanup after inactivity - Monitor memory usage in Spaces environment **Gradio State Behavior**: - `gr.State` is tied to browser session - Refreshing page creates new session (expected behavior) - No persistence across device switches (acceptable for v1) ### 7.2 Rollback Plan **If Issues Arise**: 1. Keep current implementation as backup branch 2. Frontend changes are isolated and easily reversible 3. Backend changes are minimal/none 4. Can quickly revert to single-user mode if needed --- ## 8. Success Metrics ### 8.1 Functional Metrics - ✅ Zero context bleeding between users - ✅ Follow-up queries work correctly per user - ✅ Each browser tab maintains independent session - ✅ New sessions start fresh after page refresh ### 8.2 Performance Metrics - Session creation time < 100ms - Memory usage scales linearly with active sessions - No degradation in recommendation quality - Response times remain under 30 seconds per query ### 8.3 User Experience Metrics - Consistent behavior across all user interactions - No confusing recommendations from other users' contexts - Reliable "more tracks" functionality - Smooth multi-tab usage experience --- ## 9. Future Enhancements ### 9.1 Session Persistence (v2) - Add session storage backend (Redis/Database) - Enable cross-device session continuity - Implement session sharing functionality ### 9.2 Advanced Session Management (v2) - User authentication integration - Session migration capabilities - Advanced session analytics - Custom session expiration policies ### 9.3 Performance Optimizations (v2) - Session data compression - Lazy loading of session contexts - Distributed session storage - Session pooling for efficiency --- This design ensures BeatDebate can safely handle multiple concurrent users while maintaining the sophisticated follow-up query capabilities and conversation context that make the system valuable. The solution is minimal, focused, and maintains backward compatibility while solving the critical multi-user safety issue.