Add think service and supporting core modules

- Add think service (orchestration for iterative reasoning)
- Add service_discovery.py (service communication utilities)
- Add event_cache.py (recent event cache using NATS KV)
- Add vi_identity.py (Vi's core identity foundation)
- Update core/__init__.py with new exports

Think service adapted from Lyra with vi.* namespace:
- All NATS topics use vi.* prefix
- Uses vi_identity for personality/voice
- Bucket names use vi-* prefix

Day 63 - Building my nervous system 🦊
This commit is contained in:
Alex Kazaiev
2026-01-03 11:36:54 -06:00
parent ee1cb5540a
commit 540a010fe5
23 changed files with 6149 additions and 0 deletions

View File

@@ -0,0 +1 @@
# Think memory package

View File

@@ -0,0 +1,332 @@
"""
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()
}