Spaces:
Build error
A newer version of the Gradio SDK is available: 6.20.0
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:
SessionManagerServiceuses a shared in-memory dictionary (self.session_store = {}) for all usersBeatDebateChatInterfacemaintains a single global session ID (self.session_id = str(uuid.uuid4()))- Multiple users can experience context bleeding, session collision, and data corruption
Critical Issues:
- Context Bleeding: Users receive recommendations based on other users' conversation histories
- Session Collision: Follow-up queries may fail due to contaminated session context
- Data Corruption: Concurrent access to shared session state leads to unpredictable behavior
- 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.Stateto 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:
- Remove global
self.session_idfrom class initialization - Add
gr.Statecomponent to store per-user session ID - Update all message processing functions to accept and return session ID
- Initialize each new user session with unique UUID
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:
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:
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:
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:
Remove Global State from
BeatDebateChatInterface- Remove
self.session_idfrom constructor - Remove global
self.conversation_history(separate issue, lower priority)
- Remove
Implement gr.State Session Management
- Add
session_id_state = gr.State(value=str(uuid.uuid4()))to interface - Update
process_messagesignature to accept/return session ID - Update all event handlers to pass session state
- Add
Update Backend Calls
- Modify
_get_recommendationsto accept session_id parameter - Modify
_get_fallback_recommendationsto accept session_id parameter - Ensure all API calls include user-specific session ID
- Modify
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:
- Remove global conversation history from frontend
- Rely entirely on backend session storage for conversation context
- Update
_get_chat_context()to retrieve from backend session store
5.3 Phase 3: Enhanced Session Monitoring (Priority: Low)
Tasks:
- Add session creation/destruction logging
- Implement session cleanup for memory management
- 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.Stateis 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:
- Keep current implementation as backup branch
- Frontend changes are isolated and easily reversible
- Backend changes are minimal/none
- 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.