""" Memory and identity management for Think service. This module handles all interactions with the Memory and Identity services. """ from typing import Optional, Dict, Any, List from datetime import datetime from uuid import uuid4 from core.logger import setup_logger from core.service_discovery import discovery_client class MemoryManager: """Manages memory storage and identity resolution""" def __init__(self, logger_name: str = 'memory_manager'): self.logger = setup_logger(logger_name, service_name='think_service') async def resolve_identity(self, external_identity: str) -> Optional[Dict[str, Any]]: """Resolve external identity using identity service""" try: self.logger.debug(f"[💭] Resolving identity: {external_identity}") result = await discovery_client.call_service( "identity", "resolve", {"external_identity": external_identity}, timeout=5.0 ) if result.success and result.data and not result.data.get('error'): self.logger.debug(f"[💭] Identity resolved: {external_identity} → {result.data.get('resolved_identity')}") return result.data else: self.logger.warning(f"[💭] Failed to resolve identity: {result.error or result.data}") return None except Exception as e: self.logger.exception(f"[💭] Error resolving identity: {e}") return None async def store_memory(self, content: str, identities: list, interaction_id: str, modality: str) -> bool: """Store message content in memory""" try: self.logger.debug(f"[💭] Storing memory for interaction {interaction_id}") # Determine tags based on content patterns tags = ["message"] content_lower = content.lower() if any(greeting in content_lower for greeting in ["hello", "hi", "hey"]): tags.append("greeting") if any(question in content_lower for question in ["?", "what", "how", "why", "when", "where"]): tags.append("question") memory_payload = { "content": content, "identities": identities, "interaction_id": interaction_id, "tags": tags, "modality": modality, "source": "think_service", "metadata": { "timestamp": datetime.utcnow().isoformat(), "processed_by": "think_service" } } result = await discovery_client.call_service( "memory", "store", memory_payload, timeout=5.0 ) if result.success and result.data and result.data.get('status') == 'stored': self.logger.debug(f"[💭] Memory stored successfully: {result.data.get('memory_id')}") return True else: self.logger.warning(f"[💭] Failed to store memory: {result.error or result.data}") return False except Exception as e: self.logger.exception(f"[💭] Error storing memory: {e}") return False async def get_recent_memories(self, identity: str, limit: int = 10) -> list: """Get recent memories for Oracle context""" try: self.logger.debug(f"[💭] Getting recent memories for {identity}") result = await discovery_client.call_service( "memory", "search", { "identities": [identity], "limit": limit, "requesting_identity": identity }, timeout=3.0 ) if result.success and result.data and result.data.get("results"): memories = result.data["results"] self.logger.debug(f"[💭] Retrieved {len(memories)} recent memories") return memories else: self.logger.debug(f"[💭] No memories found for {identity}") return [] except Exception as e: self.logger.warning(f"[💭] Error getting recent memories: {e}") return [] async def get_short_memories(self, identity: str, limit: int = 10) -> list: """ Get recent literal memories from short-term storage. Use for: immediate conversation context, "what we just discussed" """ try: self.logger.debug(f"[💭] Getting short-term memories for {identity}") result = await discovery_client.call_service( "memory", "short_memory", { "limit": limit, "identity_id": identity }, timeout=3.0 ) if result.success and result.data and result.data.get("status") == "success": memories = result.data.get("memories", []) self.logger.debug(f"[💭] Retrieved {len(memories)} short-term memories") return memories else: self.logger.debug(f"[💭] No short-term memories found for {identity}") return [] except Exception as e: self.logger.warning(f"[💭] Error getting short-term memories: {e}") return [] async def get_long_memories(self, identity: str, query: str = None, limit: int = 5) -> list: """ Get summarized memories from long-term storage. Use for: historical context, "what we discussed last week" """ try: self.logger.debug(f"[💭] Getting long-term memories for {identity}") result = await discovery_client.call_service( "memory", "long_memory", { "query": query, "limit": limit, "identity_id": identity }, timeout=5.0 ) if result.success and result.data and result.data.get("status") == "success": memories = result.data.get("memories", []) self.logger.debug(f"[💭] Retrieved {len(memories)} long-term memories") return memories else: self.logger.debug(f"[💭] No long-term memories found for {identity}") return [] except Exception as e: self.logger.warning(f"[💭] Error getting long-term memories: {e}") return [] async def get_facts(self, identity: str, category: str = None, query: str = '') -> list: """ Get facts from factual memory storage. Use for: user preferences, birthdays, established knowledge """ try: self.logger.debug(f"[💭] Getting facts for {identity}, category={category}") result = await discovery_client.call_service( "memory", "facts", { "query": query, "limit": 10, "category": category, "identity_id": identity }, timeout=3.0 ) if result.success and result.data and result.data.get("status") == "success": facts = result.data.get("facts", []) self.logger.debug(f"[💭] Retrieved {len(facts)} facts") return facts else: self.logger.debug(f"[💭] No facts found for {identity}") return [] except Exception as e: self.logger.warning(f"[💭] Error getting facts: {e}") return [] async def save_fact( self, content: str, category: str, identities: list, mutable: bool = True ) -> Optional[str]: """ Save a fact to factual memory storage. Use when: user shares birthday, preferences, permanent knowledge Examples: - Birthday: category="personal", mutable=False - Preference: category="preferences", mutable=True - Knowledge: category="knowledge", mutable=True """ try: self.logger.info(f"[💭] Saving fact: category={category}, content='{content[:50]}...'") result = await discovery_client.call_service( "memory", "save_fact", { "content": content, "category": category, "identities": identities, "mutable": mutable, "metadata": { "source": "think_service", "timestamp": datetime.utcnow().isoformat() } }, timeout=2.0 ) if result.success and result.data and result.data.get("status") == "success": fact_id = result.data.get("fact_id") self.logger.info(f"[💭] Fact saved successfully: {fact_id}") return fact_id else: self.logger.warning(f"[💭] Failed to save fact: {result.error or result.data}") return None except Exception as e: self.logger.warning(f"[💭] Error saving fact: {e}") return None async def get_health_status(self) -> Dict[str, Any]: """Get system health status from health service""" try: self.logger.debug(f"[💭] Requesting health status from health service") # Generate unique request ID request_id = f"health_req_{int(datetime.utcnow().timestamp() * 1000)}_{uuid4().hex[:8]}" # Request health status from health service health_request = { "request_id": request_id, "requesting_service": "think_service", "timestamp": datetime.utcnow().isoformat() } result = await discovery_client.call_service( "health", "status", health_request, timeout=10.0 ) health_response = result.data if result.success else None if not health_response: self.logger.warning(f"[💭] No response from health service") return { "status": "unknown", "error": "Health service did not respond", "timestamp": datetime.utcnow().isoformat() } if health_response.get("error"): self.logger.warning(f"[💭] Health service returned error: {health_response['error']}") return { "status": "error", "error": health_response["error"], "timestamp": datetime.utcnow().isoformat() } # Extract useful information from health response summary = health_response.get("summary", {}) node_states = health_response.get("node_states", {}) # Transform to format expected by iterative reasoning with detailed metrics result = { "status": summary.get("overall_status", "unknown"), "cluster_health": { "overall_status": summary.get("overall_status"), "healthy_nodes": summary.get("healthy_nodes", 0), "total_nodes": summary.get("total_nodes", 0), "cluster_issues": summary.get("cluster_issues", []) }, "node_summary": { node_id: { "status": node_info.get("status", "unknown"), "cpu_percent": node_info.get("cpu_percent", 0.0), "memory_percent": node_info.get("memory_percent", 0.0), "disk_percent": node_info.get("disk_percent", 0.0), "cpu_temp": node_info.get("cpu_temp", 0.0), "services_running": node_info.get("services_running", []), "services_failed": node_info.get("services_failed", []) } for node_id, node_info in node_states.items() }, "timestamp": health_response.get("timestamp", datetime.utcnow().isoformat()) } self.logger.debug(f"[💭] Health status retrieved: {result['status']} ({result['cluster_health']['healthy_nodes']}/{result['cluster_health']['total_nodes']} nodes healthy)") return result except Exception as e: self.logger.error(f"[💭] Error getting health status: {e}") return { "status": "error", "error": str(e), "timestamp": datetime.utcnow().isoformat() }