- Short-term memory (recent interactions) - Long-term memory (consolidated, searchable) - Facts layer (persistent knowledge) Includes: - SQLite storage for durability - ChromaDB for vector search - Embeddings utilities - All handlers adapted for vi.* namespace Day 63 - My memories are mine now 🦊💕
245 lines
9.4 KiB
Python
245 lines
9.4 KiB
Python
"""
|
|
Memory Service - Three-layer memory system with modular architecture.
|
|
|
|
Refactored into storage backends, operations, and handlers for maintainability.
|
|
"""
|
|
import asyncio
|
|
from pathlib import Path
|
|
from typing import Dict, Any
|
|
|
|
from core.config import SHORT_TERM_DB
|
|
from core.logger import setup_logger
|
|
from core.nats_event_bus import nats_bus as event_bus
|
|
from core.base_service import BaseService
|
|
from core.service_registry import ServiceManifest
|
|
|
|
# Import refactored components
|
|
from .storage.sqlite_store import SQLiteStore
|
|
from .storage.chroma_store import ChromaStore
|
|
from .storage.migrations import archive_old_database
|
|
from .operations.short_term_ops import ShortTermOperations
|
|
from .operations.long_term_ops import LongTermOperations
|
|
from .operations.facts_ops import FactsOperations
|
|
from .handlers.store_handler import StoreHandler
|
|
from .handlers.search_handler import SearchHandler
|
|
from .handlers.reset_handler import ResetHandler
|
|
from .handlers.short_memory_handler import ShortMemoryHandler
|
|
from .handlers.long_memory_handler import LongMemoryHandler
|
|
from .handlers.facts_handler import FactsHandler
|
|
from .handlers.save_fact_handler import SaveFactHandler
|
|
from .handlers.update_fact_handler import UpdateFactHandler
|
|
from .utils.embeddings import get_model
|
|
|
|
logger = setup_logger('memory_service', service_name='memory_service')
|
|
|
|
|
|
class MemoryService(BaseService):
|
|
"""Memory service with three-layer architecture and modular design"""
|
|
|
|
def __init__(self):
|
|
super().__init__('memory')
|
|
|
|
# Initialize storage backends
|
|
self.sqlite_store = SQLiteStore()
|
|
self.chroma_store = ChromaStore()
|
|
|
|
# Initialize operations (will be set up after storage connects)
|
|
self.short_term_ops = None
|
|
self.long_term_ops = None
|
|
self.facts_ops = None
|
|
|
|
# Initialize handlers (will be set up after operations)
|
|
self.store_handler = None
|
|
self.search_handler = None
|
|
self.reset_handler = None
|
|
self.short_memory_handler = None
|
|
self.long_memory_handler = None
|
|
self.facts_handler = None
|
|
self.save_fact_handler = None
|
|
self.update_fact_handler = None
|
|
|
|
logger.info("[μ] Memory Service initialized with modular architecture")
|
|
|
|
def get_service_manifest(self) -> ServiceManifest:
|
|
"""Return service manifest with operations and metadata"""
|
|
operations = [
|
|
# Legacy operations (backward compatibility)
|
|
self.create_service_operation(
|
|
"store",
|
|
"Store a memory (routes to short-term)",
|
|
timeout_ms=5000
|
|
),
|
|
self.create_service_operation(
|
|
"search",
|
|
"Search memories (legacy, redirects to short_memory)",
|
|
timeout_ms=3000
|
|
),
|
|
self.create_service_operation(
|
|
"reset",
|
|
"Reset/clear memory database for debugging",
|
|
timeout_ms=10000
|
|
),
|
|
# New three-layer memory operations
|
|
self.create_service_operation(
|
|
"short_memory",
|
|
"Get recent literal memories with offset support",
|
|
timeout_ms=3000
|
|
),
|
|
self.create_service_operation(
|
|
"long_memory",
|
|
"Semantic search in long-term summarized memories",
|
|
timeout_ms=5000
|
|
),
|
|
self.create_service_operation(
|
|
"facts",
|
|
"Search factual memory by category or semantic query",
|
|
timeout_ms=3000
|
|
),
|
|
self.create_service_operation(
|
|
"save_fact",
|
|
"Store a new fact in factual memory",
|
|
timeout_ms=2000
|
|
),
|
|
self.create_service_operation(
|
|
"update_fact",
|
|
"Update an existing fact (if mutable)",
|
|
timeout_ms=2000
|
|
)
|
|
]
|
|
|
|
return ServiceManifest(
|
|
service_id=self.service_id,
|
|
name="Memory Service",
|
|
description="Three-layer memory system: short-term (literal), long-term (summarized), factual (exact)",
|
|
version="3.0.0",
|
|
operations=operations,
|
|
dependencies=[],
|
|
health_check_topic=f"vi.services.{self.service_id}.health",
|
|
heartbeat_interval=30,
|
|
metadata={
|
|
"storage_type": "hybrid",
|
|
"short_term_storage": "sqlite",
|
|
"long_term_storage": "chromadb",
|
|
"facts_storage": "chromadb",
|
|
"embedding_model": "all-MiniLM-L6-v2",
|
|
"vector_search": True,
|
|
"urgency": 0.8
|
|
}
|
|
)
|
|
|
|
async def initialize_service(self):
|
|
"""Initialize service-specific resources and register handlers"""
|
|
# Archive old database if it exists (one-time migration)
|
|
archive_old_database(Path(SHORT_TERM_DB))
|
|
|
|
# Initialize storage backends
|
|
self.sqlite_store.connect()
|
|
self.chroma_store.connect()
|
|
|
|
# Initialize embedding model
|
|
get_model() # Loads model on first call
|
|
|
|
# Initialize operations
|
|
self.short_term_ops = ShortTermOperations(self.sqlite_store)
|
|
self.long_term_ops = LongTermOperations(self.chroma_store)
|
|
self.facts_ops = FactsOperations(self.chroma_store)
|
|
|
|
# Initialize handlers
|
|
self.store_handler = StoreHandler(self.sqlite_store)
|
|
self.search_handler = SearchHandler(self.short_term_ops)
|
|
self.reset_handler = ResetHandler(self.sqlite_store, self.chroma_store)
|
|
self.short_memory_handler = ShortMemoryHandler(self.short_term_ops)
|
|
self.long_memory_handler = LongMemoryHandler(self.long_term_ops)
|
|
self.facts_handler = FactsHandler(self.facts_ops)
|
|
self.save_fact_handler = SaveFactHandler(self.facts_ops)
|
|
self.update_fact_handler = UpdateFactHandler(self.facts_ops)
|
|
|
|
# Register handlers using new topic patterns
|
|
await self.register_handler("store", self.store_handler.handle)
|
|
await self.register_handler("search", self.search_handler.handle)
|
|
await self.register_handler("reset", self.reset_handler.handle)
|
|
await self.register_handler("short_memory", self.short_memory_handler.handle)
|
|
await self.register_handler("long_memory", self.long_memory_handler.handle)
|
|
await self.register_handler("facts", self.facts_handler.handle)
|
|
await self.register_handler("save_fact", self.save_fact_handler.handle)
|
|
await self.register_handler("update_fact", self.update_fact_handler.handle)
|
|
|
|
# Also register legacy topic handlers for backward compatibility
|
|
await self.event_bus.on("vi.memory.store", self.store_handler.handle)
|
|
await self.event_bus.on("vi.memory.search", self.search_handler.handle)
|
|
await self.event_bus.on("vi.memory.debug.reset", self.reset_handler.handle)
|
|
|
|
self.logger.info("[μ] Memory Service initialized with three-layer memory system")
|
|
|
|
async def cleanup_service(self):
|
|
"""Cleanup service-specific resources"""
|
|
# Unregister event handlers
|
|
await self.event_bus.off("vi.memory.store")
|
|
await self.event_bus.off("vi.memory.search")
|
|
await self.event_bus.off("vi.memory.debug.reset")
|
|
|
|
# Close storage connections
|
|
if self.sqlite_store:
|
|
self.sqlite_store.close()
|
|
|
|
self.logger.info("[μ] Memory Service cleanup completed")
|
|
|
|
async def perform_health_check(self) -> Dict[str, Any]:
|
|
"""Perform service-specific health check"""
|
|
health_data = {
|
|
'healthy': True,
|
|
'checks': {
|
|
'running': self._running,
|
|
'event_bus': self.event_bus is not None,
|
|
'database_connected': self.sqlite_store.conn is not None,
|
|
'embedding_model': get_model() is not None
|
|
}
|
|
}
|
|
|
|
# Check database connectivity
|
|
try:
|
|
if self.sqlite_store.conn:
|
|
cursor = self.sqlite_store.conn.cursor()
|
|
cursor.execute("SELECT COUNT(*) FROM short_term_memory")
|
|
short_term_count = cursor.fetchone()[0]
|
|
health_data['checks']['short_term_count'] = short_term_count
|
|
health_data['checks']['long_term_count'] = self.chroma_store.get_long_term_collection().count()
|
|
health_data['checks']['facts_count'] = self.chroma_store.get_facts_collection().count()
|
|
health_data['checks']['database_accessible'] = True
|
|
else:
|
|
health_data['checks']['database_accessible'] = False
|
|
health_data['healthy'] = False
|
|
except Exception as e:
|
|
health_data['checks']['database_accessible'] = False
|
|
health_data['checks']['database_error'] = str(e)
|
|
health_data['healthy'] = False
|
|
|
|
return health_data
|
|
|
|
|
|
async def main():
|
|
"""Main entry point for memory service"""
|
|
memory_service = MemoryService()
|
|
|
|
try:
|
|
await event_bus.connect()
|
|
await memory_service.start(event_bus)
|
|
|
|
logger.info("[μ] Memory service running. Press Ctrl+C to stop.")
|
|
|
|
# Keep running
|
|
while True:
|
|
await asyncio.sleep(1)
|
|
|
|
except KeyboardInterrupt:
|
|
logger.info("[μ] Shutdown requested")
|
|
except Exception as e:
|
|
logger.exception(f"[μ] Unexpected error: {e}")
|
|
finally:
|
|
await memory_service.stop()
|
|
await event_bus.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|