tan-en-yao's picture
feat: add security, observability layers and update documentation
6062397
Raw
History Blame Contribute Delete
3.15 kB
"""Session management for FixMyNeighborhood.
Provides:
- Session-scoped orchestrator management
- Multi-user isolation via gr.State
- Session ID generation
"""
import hashlib
import time
from typing import Optional
from config import ANTHROPIC_API_KEY
from agents.controller import AutonomousController
def generate_session_id(orchestrator: Optional[AutonomousController] = None) -> str:
"""
Generate a unique session ID for rate limiting and audit.
Args:
orchestrator: Optional orchestrator instance for stable ID
Returns:
16-character hex session ID
"""
if orchestrator:
seed = str(id(orchestrator))
else:
seed = str(time.time())
return hashlib.sha256(seed.encode()).hexdigest()[:16]
def get_or_create_orchestrator(
session_orchestrator: Optional[AutonomousController]
) -> Optional[AutonomousController]:
"""
Get existing session orchestrator or create new one.
Uses gr.State to maintain orchestrator per browser session:
- Same user (same tab): Reuses orchestrator for multi-turn context
- Different users (different tabs/browsers): Separate orchestrators
Args:
session_orchestrator: Existing orchestrator from gr.State (None if first request)
Returns:
Orchestrator instance for this session, or None if API key not configured
"""
if session_orchestrator is not None:
return session_orchestrator
if not ANTHROPIC_API_KEY:
return None
try:
return AutonomousController()
except Exception as e:
print(f"Orchestrator creation error: {e}")
return None
class SessionManager:
"""
Manages session state for multi-user isolation.
In Gradio, each browser tab gets its own gr.State values.
This class provides additional session management utilities.
"""
def __init__(self):
self._active_sessions: dict = {}
def create_session(self) -> str:
"""Create a new session and return its ID."""
session_id = generate_session_id()
self._active_sessions[session_id] = {
"created_at": time.time(),
"last_activity": time.time(),
}
return session_id
def update_activity(self, session_id: str) -> None:
"""Update last activity time for a session."""
if session_id in self._active_sessions:
self._active_sessions[session_id]["last_activity"] = time.time()
def cleanup_stale_sessions(self, max_age_seconds: float = 3600) -> int:
"""
Remove sessions older than max_age.
Args:
max_age_seconds: Maximum session age in seconds (default 1 hour)
Returns:
Number of sessions cleaned up
"""
now = time.time()
stale = [
sid for sid, data in self._active_sessions.items()
if now - data["last_activity"] > max_age_seconds
]
for sid in stale:
del self._active_sessions[sid]
return len(stale)
def get_active_count(self) -> int:
"""Get count of active sessions."""
return len(self._active_sessions)