diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..7c4dc70
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,34 @@
+# OpenAI Configuration
+OPENAI_API_KEY=sk-your-openai-api-key-here
+
+# Supabase Configuration
+SUPABASE_CONNECTION_STRING=postgresql://supabase_admin:your-password@172.21.0.12:5432/postgres
+
+# Neo4j Configuration
+NEO4J_URI=neo4j://neo4j:7687
+NEO4J_USER=neo4j
+NEO4J_PASSWORD=your-neo4j-password
+
+# API Configuration
+API_HOST=0.0.0.0
+API_PORT=8080
+API_KEY=your-secure-api-key-here
+
+# MCP Server Configuration
+MCP_HOST=0.0.0.0
+MCP_PORT=8765
+
+# Mem0 Configuration
+MEM0_COLLECTION_NAME=t6_memories
+MEM0_EMBEDDING_DIMS=1536
+MEM0_VERSION=v1.1
+
+# Docker Network
+DOCKER_NETWORK=localai
+
+# Logging
+LOG_LEVEL=INFO
+LOG_FORMAT=json
+
+# Environment
+ENVIRONMENT=development
diff --git a/api/__init__.py b/api/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/api/auth.py b/api/auth.py
new file mode 100644
index 0000000..2fdaf41
--- /dev/null
+++ b/api/auth.py
@@ -0,0 +1,66 @@
+"""
+Authentication middleware for T6 Mem0 v2 REST API
+"""
+
+from fastapi import HTTPException, Security, status
+from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
+from config import settings
+
+# Security scheme
+security = HTTPBearer()
+
+
+async def verify_api_key(
+ credentials: HTTPAuthorizationCredentials = Security(security)
+) -> str:
+ """
+ Verify API key from Authorization header
+
+ Args:
+ credentials: HTTP Bearer credentials
+
+ Returns:
+ The verified API key
+
+ Raises:
+ HTTPException: If authentication fails
+ """
+ if not credentials:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Missing authentication credentials",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+
+ token = credentials.credentials
+
+ # Verify token matches configured API key
+ if token != settings.api_key:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Invalid or expired API key",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+
+ return token
+
+
+async def optional_api_key(
+ credentials: HTTPAuthorizationCredentials = Security(security)
+) -> str | None:
+ """
+ Optional API key verification (for public endpoints)
+
+ Args:
+ credentials: HTTP Bearer credentials
+
+ Returns:
+ The API key if provided, None otherwise
+ """
+ if not credentials:
+ return None
+
+ try:
+ return await verify_api_key(credentials)
+ except HTTPException:
+ return None
diff --git a/api/main.py b/api/main.py
new file mode 100644
index 0000000..5f97337
--- /dev/null
+++ b/api/main.py
@@ -0,0 +1,158 @@
+"""
+T6 Mem0 v2 REST API
+Main FastAPI application
+"""
+
+import logging
+import sys
+from contextlib import asynccontextmanager
+from fastapi import FastAPI, Request, status
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import JSONResponse
+from fastapi.exceptions import RequestValidationError
+
+from api.routes import router
+from api.memory_service import get_memory_service
+from config import settings
+
+# Configure logging
+logging.basicConfig(
+ level=getattr(logging, settings.log_level.upper()),
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ handlers=[
+ logging.StreamHandler(sys.stdout)
+ ]
+)
+
+logger = logging.getLogger(__name__)
+
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ """
+ Lifespan context manager for startup and shutdown events
+ """
+ # Startup
+ logger.info("Starting T6 Mem0 v2 REST API")
+ logger.info(f"Environment: {settings.environment}")
+ logger.info(f"API Host: {settings.api_host}:{settings.api_port}")
+
+ try:
+ # Initialize memory service
+ service = get_memory_service()
+ logger.info("Memory service initialized successfully")
+
+ yield
+
+ except Exception as e:
+ logger.error(f"Failed to initialize application: {e}")
+ raise
+
+ finally:
+ # Shutdown
+ logger.info("Shutting down T6 Mem0 v2 REST API")
+
+
+# Create FastAPI application
+app = FastAPI(
+ title="T6 Mem0 v2 API",
+ description="""
+ Memory system for LLM applications based on mem0.ai
+
+ ## Features
+ - **Add Memory**: Store conversation memories with semantic understanding
+ - **Search Memory**: Find relevant memories using semantic search
+ - **Manage Memory**: Update and delete memories
+ - **Multi-Agent**: Support for user, agent, and run-specific memories
+ - **Graph Relationships**: Neo4j integration for memory relationships
+
+ ## Authentication
+ All endpoints (except /health) require Bearer token authentication.
+ Include your API key in the Authorization header:
+ ```
+ Authorization: Bearer YOUR_API_KEY
+ ```
+ """,
+ version="0.1.0",
+ docs_url="/docs",
+ redoc_url="/redoc",
+ openapi_url="/openapi.json",
+ lifespan=lifespan
+)
+
+# CORS middleware
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=[
+ "http://localhost:3000",
+ "http://localhost:8080",
+ "http://localhost:5678", # n8n
+ ],
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
+
+# Exception handlers
+@app.exception_handler(RequestValidationError)
+async def validation_exception_handler(request: Request, exc: RequestValidationError):
+ """Handle validation errors"""
+ logger.warning(f"Validation error: {exc.errors()}")
+ return JSONResponse(
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
+ content={
+ "status": "error",
+ "error": "Validation failed",
+ "detail": exc.errors()
+ }
+ )
+
+
+@app.exception_handler(Exception)
+async def general_exception_handler(request: Request, exc: Exception):
+ """Handle general exceptions"""
+ logger.error(f"Unhandled exception: {exc}", exc_info=True)
+ return JSONResponse(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ content={
+ "status": "error",
+ "error": "Internal server error",
+ "detail": str(exc) if settings.environment == "development" else "An error occurred"
+ }
+ )
+
+
+# Include routers
+app.include_router(router)
+
+
+# Root endpoint
+@app.get(
+ "/",
+ summary="API Root",
+ description="Get API information",
+ tags=["info"]
+)
+async def root():
+ """API root endpoint"""
+ return {
+ "name": "T6 Mem0 v2 API",
+ "version": "0.1.0",
+ "status": "running",
+ "docs": "/docs",
+ "health": "/v1/health"
+ }
+
+
+# Run with uvicorn
+if __name__ == "__main__":
+ import uvicorn
+
+ uvicorn.run(
+ "api.main:app",
+ host=settings.api_host,
+ port=settings.api_port,
+ reload=settings.environment == "development",
+ log_level=settings.log_level.lower()
+ )
diff --git a/api/memory_service.py b/api/memory_service.py
new file mode 100644
index 0000000..1fa3f86
--- /dev/null
+++ b/api/memory_service.py
@@ -0,0 +1,353 @@
+"""
+Memory service wrapper for Mem0 core library
+"""
+
+import logging
+from typing import List, Dict, Any, Optional
+from mem0 import Memory
+from config import mem0_config
+
+logger = logging.getLogger(__name__)
+
+
+class MemoryService:
+ """Singleton service for memory operations using Mem0"""
+
+ _instance: Optional['MemoryService'] = None
+ _memory: Optional[Memory] = None
+
+ def __new__(cls):
+ if cls._instance is None:
+ cls._instance = super().__new__(cls)
+ return cls._instance
+
+ def __init__(self):
+ """Initialize memory service with Mem0"""
+ if self._memory is None:
+ logger.info("Initializing Mem0 with configuration")
+ try:
+ self._memory = Memory.from_config(config_dict=mem0_config)
+ logger.info("Mem0 initialized successfully")
+ except Exception as e:
+ logger.error(f"Failed to initialize Mem0: {e}")
+ raise
+
+ @property
+ def memory(self) -> Memory:
+ """Get Mem0 instance"""
+ if self._memory is None:
+ raise RuntimeError("MemoryService not initialized")
+ return self._memory
+
+ async def add_memory(
+ self,
+ messages: List[Dict[str, str]],
+ user_id: Optional[str] = None,
+ agent_id: Optional[str] = None,
+ run_id: Optional[str] = None,
+ metadata: Optional[Dict[str, Any]] = None
+ ) -> List[Dict[str, Any]]:
+ """
+ Add new memory from messages
+
+ Args:
+ messages: List of chat messages
+ user_id: User identifier
+ agent_id: Agent identifier
+ run_id: Run identifier
+ metadata: Additional metadata
+
+ Returns:
+ List of created memories
+
+ Raises:
+ Exception: If memory creation fails
+ """
+ try:
+ logger.info(f"Adding memory for user_id={user_id}, agent_id={agent_id}")
+
+ result = self.memory.add(
+ messages=messages,
+ user_id=user_id,
+ agent_id=agent_id,
+ run_id=run_id,
+ metadata=metadata or {}
+ )
+
+ logger.info(f"Successfully added {len(result.get('results', []))} memories")
+ return result.get('results', [])
+
+ except Exception as e:
+ logger.error(f"Failed to add memory: {e}")
+ raise
+
+ async def search_memories(
+ self,
+ query: str,
+ user_id: Optional[str] = None,
+ agent_id: Optional[str] = None,
+ run_id: Optional[str] = None,
+ limit: int = 10
+ ) -> List[Dict[str, Any]]:
+ """
+ Search memories by query
+
+ Args:
+ query: Search query
+ user_id: User identifier filter
+ agent_id: Agent identifier filter
+ run_id: Run identifier filter
+ limit: Maximum results
+
+ Returns:
+ List of matching memories with scores
+
+ Raises:
+ Exception: If search fails
+ """
+ try:
+ logger.info(f"Searching memories: query='{query}', user_id={user_id}, limit={limit}")
+
+ results = self.memory.search(
+ query=query,
+ user_id=user_id,
+ agent_id=agent_id,
+ run_id=run_id,
+ limit=limit
+ )
+
+ logger.info(f"Found {len(results)} matching memories")
+ return results
+
+ except Exception as e:
+ logger.error(f"Failed to search memories: {e}")
+ raise
+
+ async def get_memory(
+ self,
+ memory_id: str
+ ) -> Optional[Dict[str, Any]]:
+ """
+ Get specific memory by ID
+
+ Args:
+ memory_id: Memory identifier
+
+ Returns:
+ Memory data or None if not found
+
+ Raises:
+ Exception: If retrieval fails
+ """
+ try:
+ logger.info(f"Getting memory: id={memory_id}")
+
+ result = self.memory.get(memory_id=memory_id)
+
+ if result:
+ logger.info(f"Retrieved memory: {memory_id}")
+ else:
+ logger.warning(f"Memory not found: {memory_id}")
+
+ return result
+
+ except Exception as e:
+ logger.error(f"Failed to get memory: {e}")
+ raise
+
+ async def get_all_memories(
+ self,
+ user_id: Optional[str] = None,
+ agent_id: Optional[str] = None,
+ run_id: Optional[str] = None
+ ) -> List[Dict[str, Any]]:
+ """
+ Get all memories for a user/agent/run
+
+ Args:
+ user_id: User identifier filter
+ agent_id: Agent identifier filter
+ run_id: Run identifier filter
+
+ Returns:
+ List of all matching memories
+
+ Raises:
+ Exception: If retrieval fails
+ """
+ try:
+ logger.info(f"Getting all memories: user_id={user_id}, agent_id={agent_id}")
+
+ results = self.memory.get_all(
+ user_id=user_id,
+ agent_id=agent_id,
+ run_id=run_id
+ )
+
+ logger.info(f"Retrieved {len(results)} memories")
+ return results
+
+ except Exception as e:
+ logger.error(f"Failed to get all memories: {e}")
+ raise
+
+ async def update_memory(
+ self,
+ memory_id: str,
+ data: str
+ ) -> Dict[str, Any]:
+ """
+ Update existing memory
+
+ Args:
+ memory_id: Memory identifier
+ data: Updated memory text
+
+ Returns:
+ Updated memory data
+
+ Raises:
+ Exception: If update fails
+ """
+ try:
+ logger.info(f"Updating memory: id={memory_id}")
+
+ result = self.memory.update(
+ memory_id=memory_id,
+ data=data
+ )
+
+ logger.info(f"Successfully updated memory: {memory_id}")
+ return result
+
+ except Exception as e:
+ logger.error(f"Failed to update memory: {e}")
+ raise
+
+ async def delete_memory(
+ self,
+ memory_id: str
+ ) -> bool:
+ """
+ Delete memory by ID
+
+ Args:
+ memory_id: Memory identifier
+
+ Returns:
+ True if deleted successfully
+
+ Raises:
+ Exception: If deletion fails
+ """
+ try:
+ logger.info(f"Deleting memory: id={memory_id}")
+
+ self.memory.delete(memory_id=memory_id)
+
+ logger.info(f"Successfully deleted memory: {memory_id}")
+ return True
+
+ except Exception as e:
+ logger.error(f"Failed to delete memory: {e}")
+ raise
+
+ async def delete_all_memories(
+ self,
+ user_id: Optional[str] = None,
+ agent_id: Optional[str] = None,
+ run_id: Optional[str] = None
+ ) -> bool:
+ """
+ Delete all memories for a user/agent/run
+
+ Args:
+ user_id: User identifier filter
+ agent_id: Agent identifier filter
+ run_id: Run identifier filter
+
+ Returns:
+ True if deleted successfully
+
+ Raises:
+ Exception: If deletion fails
+ """
+ try:
+ logger.info(f"Deleting all memories: user_id={user_id}, agent_id={agent_id}")
+
+ self.memory.delete_all(
+ user_id=user_id,
+ agent_id=agent_id,
+ run_id=run_id
+ )
+
+ logger.info("Successfully deleted all matching memories")
+ return True
+
+ except Exception as e:
+ logger.error(f"Failed to delete all memories: {e}")
+ raise
+
+ async def get_memory_history(
+ self,
+ memory_id: str
+ ) -> List[Dict[str, Any]]:
+ """
+ Get history of a memory
+
+ Args:
+ memory_id: Memory identifier
+
+ Returns:
+ List of memory history entries
+
+ Raises:
+ Exception: If retrieval fails
+ """
+ try:
+ logger.info(f"Getting memory history: id={memory_id}")
+
+ result = self.memory.history(memory_id=memory_id)
+
+ logger.info(f"Retrieved history for memory: {memory_id}")
+ return result
+
+ except Exception as e:
+ logger.error(f"Failed to get memory history: {e}")
+ raise
+
+ async def health_check(self) -> Dict[str, str]:
+ """
+ Check health of memory service
+
+ Returns:
+ Dict with component health status
+ """
+ health = {}
+
+ try:
+ # Test Mem0 access
+ self.memory
+ health['mem0'] = 'healthy'
+ except Exception as e:
+ logger.error(f"Mem0 health check failed: {e}")
+ health['mem0'] = 'unhealthy'
+
+ return health
+
+
+# Global service instance
+_memory_service: Optional[MemoryService] = None
+
+
+def get_memory_service() -> MemoryService:
+ """
+ Get or create memory service singleton
+
+ Returns:
+ MemoryService instance
+ """
+ global _memory_service
+ if _memory_service is None:
+ _memory_service = MemoryService()
+ return _memory_service
diff --git a/api/models.py b/api/models.py
new file mode 100644
index 0000000..9eaff0a
--- /dev/null
+++ b/api/models.py
@@ -0,0 +1,227 @@
+"""
+Pydantic models for T6 Mem0 v2 REST API
+"""
+
+from typing import Optional, List, Dict, Any
+from datetime import datetime
+from pydantic import BaseModel, Field, ConfigDict
+from uuid import UUID
+
+
+class Message(BaseModel):
+ """Chat message for memory extraction"""
+ role: str = Field(..., description="Message role (user, assistant, system)")
+ content: str = Field(..., description="Message content")
+
+ model_config = ConfigDict(
+ json_schema_extra={
+ "example": {
+ "role": "user",
+ "content": "I love pizza with extra cheese"
+ }
+ }
+ )
+
+
+class AddMemoryRequest(BaseModel):
+ """Request to add new memory"""
+ messages: List[Message] = Field(..., description="Conversation messages")
+ user_id: Optional[str] = Field(None, description="User identifier")
+ agent_id: Optional[str] = Field(None, description="Agent identifier")
+ run_id: Optional[str] = Field(None, description="Run identifier")
+ metadata: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Additional metadata")
+
+ model_config = ConfigDict(
+ json_schema_extra={
+ "example": {
+ "messages": [
+ {"role": "user", "content": "I love pizza"},
+ {"role": "assistant", "content": "Great! What toppings?"},
+ {"role": "user", "content": "Extra cheese please"}
+ ],
+ "user_id": "alice",
+ "metadata": {"session": "chat_123", "source": "web"}
+ }
+ }
+ )
+
+
+class SearchMemoryRequest(BaseModel):
+ """Request to search memories"""
+ query: str = Field(..., description="Search query")
+ user_id: Optional[str] = Field(None, description="User identifier filter")
+ agent_id: Optional[str] = Field(None, description="Agent identifier filter")
+ run_id: Optional[str] = Field(None, description="Run identifier filter")
+ limit: int = Field(default=10, ge=1, le=100, description="Maximum results")
+
+ model_config = ConfigDict(
+ json_schema_extra={
+ "example": {
+ "query": "What food does the user like?",
+ "user_id": "alice",
+ "limit": 5
+ }
+ }
+ )
+
+
+class UpdateMemoryRequest(BaseModel):
+ """Request to update existing memory"""
+ memory_text: Optional[str] = Field(None, description="Updated memory text")
+ metadata: Optional[Dict[str, Any]] = Field(None, description="Updated metadata")
+
+ model_config = ConfigDict(
+ json_schema_extra={
+ "example": {
+ "memory_text": "User loves pizza with mushrooms and olives",
+ "metadata": {"verified": True, "updated_by": "admin"}
+ }
+ }
+ )
+
+
+class MemoryResponse(BaseModel):
+ """Memory response model"""
+ id: str = Field(..., description="Memory unique identifier")
+ memory: str = Field(..., description="Memory text content")
+ user_id: Optional[str] = Field(None, description="User identifier")
+ agent_id: Optional[str] = Field(None, description="Agent identifier")
+ run_id: Optional[str] = Field(None, description="Run identifier")
+ metadata: Dict[str, Any] = Field(default_factory=dict, description="Memory metadata")
+ created_at: Optional[datetime] = Field(None, description="Creation timestamp")
+ updated_at: Optional[datetime] = Field(None, description="Last update timestamp")
+ score: Optional[float] = Field(None, description="Relevance score (for search results)")
+
+ model_config = ConfigDict(
+ json_schema_extra={
+ "example": {
+ "id": "mem_abc123",
+ "memory": "User loves pizza with extra cheese",
+ "user_id": "alice",
+ "metadata": {"session": "chat_123"},
+ "created_at": "2025-10-13T12:00:00Z",
+ "score": 0.95
+ }
+ }
+ )
+
+
+class AddMemoryResponse(BaseModel):
+ """Response from adding memory"""
+ status: str = Field(..., description="Operation status")
+ memories: List[MemoryResponse] = Field(..., description="Created memories")
+ message: str = Field(..., description="Result message")
+
+ model_config = ConfigDict(
+ json_schema_extra={
+ "example": {
+ "status": "success",
+ "memories": [{
+ "id": "mem_abc123",
+ "memory": "User loves pizza with extra cheese",
+ "user_id": "alice"
+ }],
+ "message": "Successfully added 1 memory"
+ }
+ }
+ )
+
+
+class SearchMemoryResponse(BaseModel):
+ """Response from searching memories"""
+ status: str = Field(..., description="Operation status")
+ memories: List[MemoryResponse] = Field(..., description="Matching memories")
+ count: int = Field(..., description="Number of results")
+
+ model_config = ConfigDict(
+ json_schema_extra={
+ "example": {
+ "status": "success",
+ "memories": [{
+ "id": "mem_abc123",
+ "memory": "User loves pizza",
+ "user_id": "alice",
+ "score": 0.95
+ }],
+ "count": 1
+ }
+ }
+ )
+
+
+class DeleteMemoryResponse(BaseModel):
+ """Response from deleting memory"""
+ status: str = Field(..., description="Operation status")
+ message: str = Field(..., description="Result message")
+
+ model_config = ConfigDict(
+ json_schema_extra={
+ "example": {
+ "status": "success",
+ "message": "Memory deleted successfully"
+ }
+ }
+ )
+
+
+class HealthResponse(BaseModel):
+ """Health check response"""
+ status: str = Field(..., description="Service status")
+ version: str = Field(..., description="API version")
+ timestamp: datetime = Field(..., description="Check timestamp")
+ dependencies: Dict[str, str] = Field(..., description="Dependency status")
+
+ model_config = ConfigDict(
+ json_schema_extra={
+ "example": {
+ "status": "healthy",
+ "version": "0.1.0",
+ "timestamp": "2025-10-13T12:00:00Z",
+ "dependencies": {
+ "supabase": "connected",
+ "neo4j": "connected",
+ "openai": "available"
+ }
+ }
+ }
+ )
+
+
+class StatsResponse(BaseModel):
+ """Memory statistics response"""
+ total_memories: int = Field(..., description="Total memory count")
+ total_users: int = Field(..., description="Unique user count")
+ total_agents: int = Field(..., description="Unique agent count")
+ avg_memories_per_user: float = Field(..., description="Average memories per user")
+ oldest_memory: Optional[datetime] = Field(None, description="Oldest memory timestamp")
+ newest_memory: Optional[datetime] = Field(None, description="Newest memory timestamp")
+
+ model_config = ConfigDict(
+ json_schema_extra={
+ "example": {
+ "total_memories": 1523,
+ "total_users": 42,
+ "total_agents": 5,
+ "avg_memories_per_user": 36.26,
+ "oldest_memory": "2025-01-01T00:00:00Z",
+ "newest_memory": "2025-10-13T12:00:00Z"
+ }
+ }
+ )
+
+
+class ErrorResponse(BaseModel):
+ """Error response model"""
+ status: str = Field(default="error", description="Error status")
+ error: str = Field(..., description="Error message")
+ detail: Optional[str] = Field(None, description="Detailed error information")
+
+ model_config = ConfigDict(
+ json_schema_extra={
+ "example": {
+ "status": "error",
+ "error": "Authentication failed",
+ "detail": "Invalid or missing API key"
+ }
+ }
+ )
diff --git a/api/routes.py b/api/routes.py
new file mode 100644
index 0000000..fd18a9c
--- /dev/null
+++ b/api/routes.py
@@ -0,0 +1,377 @@
+"""
+API routes for T6 Mem0 v2 REST API
+"""
+
+import logging
+from typing import List
+from datetime import datetime
+from fastapi import APIRouter, Depends, HTTPException, status, Query
+from fastapi.responses import JSONResponse
+
+from api.auth import verify_api_key
+from api.models import (
+ AddMemoryRequest,
+ AddMemoryResponse,
+ SearchMemoryRequest,
+ SearchMemoryResponse,
+ UpdateMemoryRequest,
+ MemoryResponse,
+ DeleteMemoryResponse,
+ HealthResponse,
+ StatsResponse,
+ ErrorResponse
+)
+from api.memory_service import get_memory_service, MemoryService
+from config import settings
+
+logger = logging.getLogger(__name__)
+
+# Create router
+router = APIRouter(prefix="/v1", tags=["memories"])
+
+
+def format_memory_response(mem: dict) -> MemoryResponse:
+ """Format memory data to response model"""
+ return MemoryResponse(
+ id=mem.get('id', ''),
+ memory=mem.get('memory', mem.get('data', '')),
+ user_id=mem.get('user_id'),
+ agent_id=mem.get('agent_id'),
+ run_id=mem.get('run_id'),
+ metadata=mem.get('metadata', {}),
+ created_at=mem.get('created_at'),
+ updated_at=mem.get('updated_at'),
+ score=mem.get('score')
+ )
+
+
+@router.post(
+ "/memories/",
+ response_model=AddMemoryResponse,
+ status_code=status.HTTP_201_CREATED,
+ summary="Add new memory",
+ description="Add new memory from conversation messages",
+ responses={
+ 201: {"description": "Memory added successfully"},
+ 401: {"model": ErrorResponse, "description": "Unauthorized"},
+ 500: {"model": ErrorResponse, "description": "Internal server error"}
+ }
+)
+async def add_memory(
+ request: AddMemoryRequest,
+ _api_key: str = Depends(verify_api_key),
+ service: MemoryService = Depends(get_memory_service)
+):
+ """Add new memory from messages"""
+ try:
+ messages = [msg.model_dump() for msg in request.messages]
+
+ memories = await service.add_memory(
+ messages=messages,
+ user_id=request.user_id,
+ agent_id=request.agent_id,
+ run_id=request.run_id,
+ metadata=request.metadata
+ )
+
+ formatted_memories = [format_memory_response(mem) for mem in memories]
+
+ return AddMemoryResponse(
+ status="success",
+ memories=formatted_memories,
+ message=f"Successfully added {len(formatted_memories)} memory(ies)"
+ )
+
+ except Exception as e:
+ logger.error(f"Error adding memory: {e}")
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail=str(e)
+ )
+
+
+@router.get(
+ "/memories/search",
+ response_model=SearchMemoryResponse,
+ summary="Search memories",
+ description="Search memories by semantic similarity",
+ responses={
+ 200: {"description": "Search completed successfully"},
+ 401: {"model": ErrorResponse, "description": "Unauthorized"},
+ 500: {"model": ErrorResponse, "description": "Internal server error"}
+ }
+)
+async def search_memories(
+ query: str = Query(..., description="Search query"),
+ user_id: str | None = Query(None, description="Filter by user ID"),
+ agent_id: str | None = Query(None, description="Filter by agent ID"),
+ run_id: str | None = Query(None, description="Filter by run ID"),
+ limit: int = Query(10, ge=1, le=100, description="Maximum results"),
+ _api_key: str = Depends(verify_api_key),
+ service: MemoryService = Depends(get_memory_service)
+):
+ """Search memories by query"""
+ try:
+ memories = await service.search_memories(
+ query=query,
+ user_id=user_id,
+ agent_id=agent_id,
+ run_id=run_id,
+ limit=limit
+ )
+
+ formatted_memories = [format_memory_response(mem) for mem in memories]
+
+ return SearchMemoryResponse(
+ status="success",
+ memories=formatted_memories,
+ count=len(formatted_memories)
+ )
+
+ except Exception as e:
+ logger.error(f"Error searching memories: {e}")
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail=str(e)
+ )
+
+
+@router.get(
+ "/memories/{memory_id}",
+ response_model=MemoryResponse,
+ summary="Get specific memory",
+ description="Retrieve a specific memory by ID",
+ responses={
+ 200: {"description": "Memory retrieved successfully"},
+ 404: {"model": ErrorResponse, "description": "Memory not found"},
+ 401: {"model": ErrorResponse, "description": "Unauthorized"},
+ 500: {"model": ErrorResponse, "description": "Internal server error"}
+ }
+)
+async def get_memory(
+ memory_id: str,
+ _api_key: str = Depends(verify_api_key),
+ service: MemoryService = Depends(get_memory_service)
+):
+ """Get specific memory by ID"""
+ try:
+ memory = await service.get_memory(memory_id)
+
+ if not memory:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=f"Memory not found: {memory_id}"
+ )
+
+ return format_memory_response(memory)
+
+ except HTTPException:
+ raise
+ except Exception as e:
+ logger.error(f"Error getting memory: {e}")
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail=str(e)
+ )
+
+
+@router.get(
+ "/memories/user/{user_id}",
+ response_model=List[MemoryResponse],
+ summary="Get user memories",
+ description="Get all memories for a specific user",
+ responses={
+ 200: {"description": "Memories retrieved successfully"},
+ 401: {"model": ErrorResponse, "description": "Unauthorized"},
+ 500: {"model": ErrorResponse, "description": "Internal server error"}
+ }
+)
+async def get_user_memories(
+ user_id: str,
+ _api_key: str = Depends(verify_api_key),
+ service: MemoryService = Depends(get_memory_service)
+):
+ """Get all memories for a user"""
+ try:
+ memories = await service.get_all_memories(user_id=user_id)
+ return [format_memory_response(mem) for mem in memories]
+
+ except Exception as e:
+ logger.error(f"Error getting user memories: {e}")
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail=str(e)
+ )
+
+
+@router.patch(
+ "/memories/{memory_id}",
+ response_model=MemoryResponse,
+ summary="Update memory",
+ description="Update an existing memory",
+ responses={
+ 200: {"description": "Memory updated successfully"},
+ 404: {"model": ErrorResponse, "description": "Memory not found"},
+ 401: {"model": ErrorResponse, "description": "Unauthorized"},
+ 500: {"model": ErrorResponse, "description": "Internal server error"}
+ }
+)
+async def update_memory(
+ memory_id: str,
+ request: UpdateMemoryRequest,
+ _api_key: str = Depends(verify_api_key),
+ service: MemoryService = Depends(get_memory_service)
+):
+ """Update existing memory"""
+ try:
+ if not request.memory_text:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="memory_text is required for update"
+ )
+
+ updated = await service.update_memory(
+ memory_id=memory_id,
+ data=request.memory_text
+ )
+
+ return format_memory_response(updated)
+
+ except HTTPException:
+ raise
+ except Exception as e:
+ logger.error(f"Error updating memory: {e}")
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail=str(e)
+ )
+
+
+@router.delete(
+ "/memories/{memory_id}",
+ response_model=DeleteMemoryResponse,
+ summary="Delete memory",
+ description="Delete a specific memory",
+ responses={
+ 200: {"description": "Memory deleted successfully"},
+ 404: {"model": ErrorResponse, "description": "Memory not found"},
+ 401: {"model": ErrorResponse, "description": "Unauthorized"},
+ 500: {"model": ErrorResponse, "description": "Internal server error"}
+ }
+)
+async def delete_memory(
+ memory_id: str,
+ _api_key: str = Depends(verify_api_key),
+ service: MemoryService = Depends(get_memory_service)
+):
+ """Delete specific memory"""
+ try:
+ await service.delete_memory(memory_id)
+
+ return DeleteMemoryResponse(
+ status="success",
+ message=f"Memory {memory_id} deleted successfully"
+ )
+
+ except Exception as e:
+ logger.error(f"Error deleting memory: {e}")
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail=str(e)
+ )
+
+
+@router.delete(
+ "/memories/user/{user_id}",
+ response_model=DeleteMemoryResponse,
+ summary="Delete user memories",
+ description="Delete all memories for a specific user",
+ responses={
+ 200: {"description": "Memories deleted successfully"},
+ 401: {"model": ErrorResponse, "description": "Unauthorized"},
+ 500: {"model": ErrorResponse, "description": "Internal server error"}
+ }
+)
+async def delete_user_memories(
+ user_id: str,
+ _api_key: str = Depends(verify_api_key),
+ service: MemoryService = Depends(get_memory_service)
+):
+ """Delete all memories for a user"""
+ try:
+ await service.delete_all_memories(user_id=user_id)
+
+ return DeleteMemoryResponse(
+ status="success",
+ message=f"All memories for user {user_id} deleted successfully"
+ )
+
+ except Exception as e:
+ logger.error(f"Error deleting user memories: {e}")
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail=str(e)
+ )
+
+
+@router.get(
+ "/health",
+ response_model=HealthResponse,
+ summary="Health check",
+ description="Check API health status",
+ responses={
+ 200: {"description": "Service is healthy"}
+ }
+)
+async def health_check(
+ service: MemoryService = Depends(get_memory_service)
+):
+ """Health check endpoint"""
+ try:
+ health = await service.health_check()
+
+ return HealthResponse(
+ status="healthy" if all(v == "healthy" for v in health.values()) else "degraded",
+ version="0.1.0",
+ timestamp=datetime.utcnow(),
+ dependencies=health
+ )
+
+ except Exception as e:
+ logger.error(f"Health check failed: {e}")
+ return JSONResponse(
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
+ content={
+ "status": "unhealthy",
+ "version": "0.1.0",
+ "timestamp": datetime.utcnow().isoformat(),
+ "error": str(e)
+ }
+ )
+
+
+@router.get(
+ "/stats",
+ response_model=StatsResponse,
+ summary="Memory statistics",
+ description="Get system-wide memory statistics",
+ responses={
+ 200: {"description": "Statistics retrieved successfully"},
+ 401: {"model": ErrorResponse, "description": "Unauthorized"},
+ 500: {"model": ErrorResponse, "description": "Internal server error"}
+ }
+)
+async def get_stats(
+ _api_key: str = Depends(verify_api_key)
+):
+ """Get memory statistics"""
+ # Note: This would require direct database access or extension of mem0 API
+ # For now, return placeholder
+ return StatsResponse(
+ total_memories=0,
+ total_users=0,
+ total_agents=0,
+ avg_memories_per_user=0.0,
+ oldest_memory=None,
+ newest_memory=None
+ )
diff --git a/config.py b/config.py
new file mode 100644
index 0000000..5b5c4fd
--- /dev/null
+++ b/config.py
@@ -0,0 +1,121 @@
+"""
+Shared configuration for T6 Mem0 v2
+Loads environment variables and creates Mem0 configuration
+"""
+
+import os
+from typing import Dict, Any
+from pydantic_settings import BaseSettings
+from pydantic import Field
+
+
+class Settings(BaseSettings):
+ """Application settings loaded from environment variables"""
+
+ # OpenAI
+ openai_api_key: str = Field(..., env="OPENAI_API_KEY")
+
+ # Supabase
+ supabase_connection_string: str = Field(..., env="SUPABASE_CONNECTION_STRING")
+
+ # Neo4j
+ neo4j_uri: str = Field(..., env="NEO4J_URI")
+ neo4j_user: str = Field(default="neo4j", env="NEO4J_USER")
+ neo4j_password: str = Field(..., env="NEO4J_PASSWORD")
+
+ # API
+ api_host: str = Field(default="0.0.0.0", env="API_HOST")
+ api_port: int = Field(default=8080, env="API_PORT")
+ api_key: str = Field(..., env="API_KEY")
+
+ # MCP Server
+ mcp_host: str = Field(default="0.0.0.0", env="MCP_HOST")
+ mcp_port: int = Field(default=8765, env="MCP_PORT")
+
+ # Mem0
+ mem0_collection_name: str = Field(default="t6_memories", env="MEM0_COLLECTION_NAME")
+ mem0_embedding_dims: int = Field(default=1536, env="MEM0_EMBEDDING_DIMS")
+ mem0_version: str = Field(default="v1.1", env="MEM0_VERSION")
+
+ # Logging
+ log_level: str = Field(default="INFO", env="LOG_LEVEL")
+ log_format: str = Field(default="json", env="LOG_FORMAT")
+
+ # Environment
+ environment: str = Field(default="development", env="ENVIRONMENT")
+
+ class Config:
+ env_file = ".env"
+ env_file_encoding = "utf-8"
+ case_sensitive = False
+
+
+def get_settings() -> Settings:
+ """Get application settings"""
+ return Settings()
+
+
+def get_mem0_config(settings: Settings) -> Dict[str, Any]:
+ """
+ Generate Mem0 configuration from settings
+
+ Args:
+ settings: Application settings
+
+ Returns:
+ Dict containing Mem0 configuration
+ """
+ return {
+ # Vector Store - Supabase
+ "vector_store": {
+ "provider": "supabase",
+ "config": {
+ "connection_string": settings.supabase_connection_string,
+ "collection_name": settings.mem0_collection_name,
+ "embedding_model_dims": settings.mem0_embedding_dims,
+ "index_method": "hnsw", # Fastest search
+ "index_measure": "cosine_distance" # Best for embeddings
+ }
+ },
+
+ # Graph Store - Neo4j
+ "graph_store": {
+ "provider": "neo4j",
+ "config": {
+ "url": settings.neo4j_uri,
+ "username": settings.neo4j_user,
+ "password": settings.neo4j_password
+ }
+ },
+
+ # LLM Provider - OpenAI
+ "llm": {
+ "provider": "openai",
+ "config": {
+ "model": "gpt-4o-mini",
+ "temperature": 0.1,
+ "max_tokens": 2000,
+ "api_key": settings.openai_api_key
+ }
+ },
+
+ # Embedder - OpenAI
+ "embedder": {
+ "provider": "openai",
+ "config": {
+ "model": "text-embedding-3-small",
+ "embedding_dims": settings.mem0_embedding_dims,
+ "api_key": settings.openai_api_key
+ }
+ },
+
+ # Version
+ "version": settings.mem0_version
+ }
+
+
+# Global settings instance
+settings = get_settings()
+
+# Global mem0 config
+mem0_config = get_mem0_config(settings)
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..24d1f09
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,111 @@
+version: '3.8'
+
+services:
+ # Neo4j Graph Database
+ neo4j:
+ image: neo4j:5.26-community
+ container_name: t6-mem0-neo4j
+ restart: unless-stopped
+ ports:
+ - "7474:7474" # HTTP Browser UI
+ - "7687:7687" # Bolt Protocol
+ environment:
+ - NEO4J_AUTH=${NEO4J_USER:-neo4j}/${NEO4J_PASSWORD}
+ - NEO4J_PLUGINS=["apoc", "graph-data-science"]
+ - NEO4J_dbms_security_procedures_unrestricted=apoc.*,gds.*
+ - NEO4J_dbms_memory_heap_initial__size=512M
+ - NEO4J_dbms_memory_heap_max__size=2G
+ - NEO4J_dbms_memory_pagecache_size=512M
+ volumes:
+ - neo4j_data:/data
+ - neo4j_logs:/logs
+ - neo4j_import:/import
+ - neo4j_plugins:/plugins
+ networks:
+ - localai
+ healthcheck:
+ test: ["CMD-SHELL", "cypher-shell -u ${NEO4J_USER:-neo4j} -p ${NEO4J_PASSWORD} 'RETURN 1'"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+ # REST API Server
+ api:
+ build:
+ context: .
+ dockerfile: docker/Dockerfile.api
+ container_name: t6-mem0-api
+ restart: unless-stopped
+ ports:
+ - "${API_PORT:-8080}:8080"
+ environment:
+ - OPENAI_API_KEY=${OPENAI_API_KEY}
+ - SUPABASE_CONNECTION_STRING=${SUPABASE_CONNECTION_STRING}
+ - NEO4J_URI=neo4j://neo4j:7687
+ - NEO4J_USER=${NEO4J_USER:-neo4j}
+ - NEO4J_PASSWORD=${NEO4J_PASSWORD}
+ - API_HOST=0.0.0.0
+ - API_PORT=8080
+ - API_KEY=${API_KEY}
+ - MEM0_COLLECTION_NAME=${MEM0_COLLECTION_NAME:-t6_memories}
+ - MEM0_EMBEDDING_DIMS=${MEM0_EMBEDDING_DIMS:-1536}
+ - MEM0_VERSION=${MEM0_VERSION:-v1.1}
+ - LOG_LEVEL=${LOG_LEVEL:-INFO}
+ - ENVIRONMENT=${ENVIRONMENT:-production}
+ depends_on:
+ neo4j:
+ condition: service_healthy
+ networks:
+ - localai
+ healthcheck:
+ test: ["CMD-SHELL", "curl -f http://localhost:8080/v1/health || exit 1"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+
+ # MCP Server
+ mcp-server:
+ build:
+ context: .
+ dockerfile: docker/Dockerfile.mcp
+ container_name: t6-mem0-mcp
+ restart: unless-stopped
+ ports:
+ - "${MCP_PORT:-8765}:8765"
+ environment:
+ - OPENAI_API_KEY=${OPENAI_API_KEY}
+ - SUPABASE_CONNECTION_STRING=${SUPABASE_CONNECTION_STRING}
+ - NEO4J_URI=neo4j://neo4j:7687
+ - NEO4J_USER=${NEO4J_USER:-neo4j}
+ - NEO4J_PASSWORD=${NEO4J_PASSWORD}
+ - MCP_HOST=0.0.0.0
+ - MCP_PORT=8765
+ - MEM0_COLLECTION_NAME=${MEM0_COLLECTION_NAME:-t6_memories}
+ - MEM0_EMBEDDING_DIMS=${MEM0_EMBEDDING_DIMS:-1536}
+ - MEM0_VERSION=${MEM0_VERSION:-v1.1}
+ - LOG_LEVEL=${LOG_LEVEL:-INFO}
+ - ENVIRONMENT=${ENVIRONMENT:-production}
+ depends_on:
+ neo4j:
+ condition: service_healthy
+ networks:
+ - localai
+ healthcheck:
+ test: ["CMD-SHELL", "curl -f http://localhost:8765/health || exit 1"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+
+volumes:
+ neo4j_data:
+ name: t6-mem0-neo4j-data
+ neo4j_logs:
+ name: t6-mem0-neo4j-logs
+ neo4j_import:
+ name: t6-mem0-neo4j-import
+ neo4j_plugins:
+ name: t6-mem0-neo4j-plugins
+
+networks:
+ localai:
+ external: true
diff --git a/docker/Dockerfile.api b/docker/Dockerfile.api
new file mode 100644
index 0000000..dee22f9
--- /dev/null
+++ b/docker/Dockerfile.api
@@ -0,0 +1,35 @@
+FROM python:3.11-slim
+
+# Set working directory
+WORKDIR /app
+
+# Install system dependencies
+RUN apt-get update && apt-get install -y \
+ gcc \
+ g++ \
+ curl \
+ && rm -rf /var/lib/apt/lists/*
+
+# Copy requirements
+COPY requirements.txt .
+
+# Install Python dependencies
+RUN pip install --no-cache-dir -r requirements.txt
+
+# Copy application code
+COPY config.py .
+COPY api/ ./api/
+
+# Create non-root user
+RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
+USER appuser
+
+# Expose port
+EXPOSE 8080
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
+ CMD curl -f http://localhost:8080/v1/health || exit 1
+
+# Run application
+CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8080"]
diff --git a/docker/Dockerfile.mcp b/docker/Dockerfile.mcp
new file mode 100644
index 0000000..1532fe6
--- /dev/null
+++ b/docker/Dockerfile.mcp
@@ -0,0 +1,35 @@
+FROM python:3.11-slim
+
+# Set working directory
+WORKDIR /app
+
+# Install system dependencies
+RUN apt-get update && apt-get install -y \
+ gcc \
+ g++ \
+ curl \
+ && rm -rf /var/lib/apt/lists/*
+
+# Copy requirements
+COPY requirements.txt .
+
+# Install Python dependencies
+RUN pip install --no-cache-dir -r requirements.txt
+
+# Copy application code
+COPY config.py .
+COPY mcp-server/ ./mcp-server/
+
+# Create non-root user
+RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
+USER appuser
+
+# Expose port
+EXPOSE 8765
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
+ CMD curl -f http://localhost:8765/health || exit 1
+
+# Run MCP server
+CMD ["python", "-m", "mcp-server.main"]
diff --git a/docs/architecture.mdx b/docs/architecture.mdx
new file mode 100644
index 0000000..89b201c
--- /dev/null
+++ b/docs/architecture.mdx
@@ -0,0 +1,313 @@
+---
+title: 'System Architecture'
+description: 'Technical architecture and design decisions for T6 Mem0 v2'
+---
+
+## Architecture Overview
+
+T6 Mem0 v2 implements a **hybrid storage architecture** combining vector search, graph relationships, and structured data storage for optimal memory management.
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Client Layer │
+├──────────────────┬──────────────────┬──────────────────────┤
+│ Claude Code (MCP)│ N8N Workflows │ External Apps │
+└──────────────────┴──────────────────┴──────────────────────┘
+ │ │ │
+ │ │ │
+ ▼ ▼ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Interface Layer │
+├──────────────────────────────┬──────────────────────────────┤
+│ MCP Server (Port 8765) │ REST API (Port 8080) │
+│ - SSE Connections │ - FastAPI │
+│ - MCP Protocol │ - OpenAPI Spec │
+│ - Tool Registration │ - Auth Middleware │
+└──────────────────────────────┴──────────────────────────────┘
+ │ │
+ └────────┬───────────┘
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Core Layer │
+│ Mem0 Core Library │
+│ - Memory Management - Embedding Generation │
+│ - Semantic Search - Relationship Extraction │
+│ - Multi-Agent Support - Deduplication │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ┌───────────────────┼───────────────────┐
+ ▼ ▼ ▼
+┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
+│ Vector Store │ │ Graph Store │ │ External LLM │
+│ Supabase │ │ Neo4j │ │ OpenAI │
+│ (pgvector) │ │ (Cypher) │ │ (Embeddings) │
+│ 172.21.0.12 │ │ 172.21.0.x │ │ API Cloud │
+└─────────────────┘ └─────────────────┘ └─────────────────┘
+```
+
+## Design Decisions
+
+### 1. Hybrid Storage Architecture ✅
+
+**Why Multiple Storage Systems?**
+
+Each store is optimized for specific query patterns:
+
+
+
+ **Purpose**: Semantic similarity search
+
+ - Stores 1536-dimensional OpenAI embeddings
+ - HNSW indexing for fast approximate nearest neighbor search
+ - O(log n) query performance
+ - Cosine distance for similarity measurement
+
+
+
+ **Purpose**: Relationship modeling
+
+ - Entity extraction and connection mapping
+ - Relationship traversal and pathfinding
+ - Visual exploration in Neo4j Browser
+ - Dynamic knowledge graph evolution
+
+
+
+ **Purpose**: Flexible metadata
+
+ - Schema-less metadata storage
+ - Fast JSON queries with GIN indexes
+ - Eliminates need for separate Redis
+ - Simplifies infrastructure
+
+
+
+### 2. MCP Server Implementation
+
+**Custom vs. Pre-built**
+
+
+ We built a custom MCP server instead of using OpenMemory MCP because:
+ - OpenMemory uses Qdrant (we need Supabase)
+ - Full control over Supabase + Neo4j integration
+ - Exact match to our storage stack
+
+
+### 3. Docker Networking Strategy
+
+**localai Network Integration**
+
+All services run on the `localai` Docker network (172.21.0.0/16):
+
+```yaml
+services:
+ neo4j: 172.21.0.x:7687
+ api: 172.21.0.x:8080
+ mcp-server: 172.21.0.x:8765
+ supabase: 172.21.0.12:5432 (existing)
+```
+
+**Benefits:**
+- Container-to-container communication
+- Service discovery via Docker DNS
+- No host networking complications
+- Persistent IPs via Docker Compose
+
+## Data Flow
+
+### Adding a Memory
+
+
+
+ Client sends conversation messages via MCP or REST API
+
+
+ - LLM extracts key facts from messages
+ - Generates embedding vector (1536-dim)
+ - Identifies entities and relationships
+
+
+ Stores embedding + metadata in Supabase (pgvector)
+
+
+ Creates nodes and relationships in Neo4j
+
+
+ Returns memory ID and confirmation to client
+
+
+
+### Searching Memories
+
+
+
+ Convert search query to vector using OpenAI
+
+
+ Find similar memories in Supabase (cosine similarity)
+
+
+ Fetch related context from Neo4j graph
+
+
+ Return memories sorted by relevance score
+
+
+
+## Performance Characteristics
+
+Based on mem0.ai research:
+
+
+
+ Higher accuracy vs baseline OpenAI
+
+
+ Compared to full-context approaches
+
+
+ Through selective memory retrieval
+
+
+
+## Security Architecture
+
+### Authentication
+
+- **REST API**: Bearer token authentication
+- **MCP Server**: Client-specific SSE endpoints
+- **Tokens**: Stored securely in environment variables
+
+### Data Privacy
+
+
+ All data stored locally - no cloud sync or external storage
+
+
+- Supabase instance is local (172.21.0.12)
+- Neo4j runs in Docker container
+- User isolation via `user_id` filtering
+
+### Network Security
+
+- Services on private Docker network
+- No public exposure (use reverse proxy if needed)
+- Internal communication only
+
+## Scalability
+
+### Horizontal Scaling
+
+
+
+ Deploy multiple API containers behind load balancer
+
+
+ Dedicated instances per client group
+
+
+ Stateless design scales with containers
+
+
+
+### Vertical Scaling
+
+- **Supabase**: PostgreSQL connection pooling
+- **Neo4j**: Memory configuration tuning
+- **Vector Indexing**: HNSW for performance
+
+## Technology Choices
+
+| Component | Technology | Why? |
+|-----------|-----------|------|
+| Core Library | mem0ai | Production-ready, 26% accuracy boost |
+| Vector DB | Supabase (pgvector) | Existing infrastructure, PostgreSQL |
+| Graph DB | Neo4j | Best-in-class graph database |
+| LLM | OpenAI | High-quality embeddings, GPT-4o |
+| REST API | FastAPI | Fast, modern, auto-docs |
+| MCP Protocol | Python MCP SDK | Official MCP implementation |
+| Containers | Docker Compose | Simple orchestration |
+
+## Phase 2: Ollama Integration
+
+**Configuration-driven provider switching:**
+
+```python
+# Phase 1 (OpenAI)
+"llm": {
+ "provider": "openai",
+ "config": {"model": "gpt-4o-mini"}
+}
+
+# Phase 2 (Ollama)
+"llm": {
+ "provider": "ollama",
+ "config": {
+ "model": "llama3.1:8b",
+ "ollama_base_url": "http://172.21.0.1:11434"
+ }
+}
+```
+
+**No code changes required** - just environment variables!
+
+## Monitoring & Observability
+
+### Metrics to Track
+
+- Memory operations per second
+- Average response time
+- Vector search latency
+- Graph query complexity
+- OpenAI token usage
+
+### Logging
+
+- Structured JSON logs
+- Request/response tracking
+- Error aggregation
+- Performance profiling
+
+
+ Use Prometheus + Grafana for production monitoring
+
+
+## Deep Dive Resources
+
+For complete architectural details, see:
+
+- [ARCHITECTURE.md](https://git.colsys.tech/klas/t6_mem0_v2/blob/main/ARCHITECTURE.md)
+- [PROJECT_REQUIREMENTS.md](https://git.colsys.tech/klas/t6_mem0_v2/blob/main/PROJECT_REQUIREMENTS.md)
+
+## Next Steps
+
+
+
+ Configure vector store
+
+
+ Configure graph database
+
+
+ Explore endpoints
+
+
+ Connect with Claude Code
+
+
diff --git a/docs/introduction.mdx b/docs/introduction.mdx
new file mode 100644
index 0000000..973eb94
--- /dev/null
+++ b/docs/introduction.mdx
@@ -0,0 +1,168 @@
+---
+title: Introduction
+description: 'Welcome to T6 Mem0 v2 - Memory System for LLM Applications'
+---
+
+
+
+
+## What is T6 Mem0 v2?
+
+T6 Mem0 v2 is a comprehensive memory system for LLM applications built on **mem0.ai**, featuring:
+
+- 🔌 **MCP Server Integration** - Native Model Context Protocol support for Claude Code and AI tools
+- 🌐 **REST API** - Full HTTP API for memory operations
+- 🗄️ **Hybrid Storage** - Supabase (vector) + Neo4j (graph) for optimal performance
+- 🤖 **AI-Powered** - OpenAI embeddings with 26% accuracy improvement
+- 📊 **Graph Visualization** - Explore memory relationships in Neo4j Browser
+- 🐳 **Docker-Native** - Fully containerized deployment
+
+## Key Features
+
+
+
+ Find relevant memories using AI-powered semantic similarity
+
+
+ Use as MCP server with Claude Code, Cursor, and other AI tools
+
+
+ Visualize and explore memory connections with Neo4j
+
+
+ Isolate memories by user, agent, or run identifiers
+
+
+
+## Architecture
+
+T6 Mem0 v2 uses a **hybrid storage architecture** for optimal performance:
+
+```
+┌──────────────────────────────────┐
+│ Clients (Claude, N8N, Apps) │
+└──────────────┬───────────────────┘
+ │
+┌──────────────┴───────────────────┐
+│ MCP Server (8765) + REST (8080) │
+└──────────────┬───────────────────┘
+ │
+┌──────────────┴───────────────────┐
+│ Mem0 Core Library │
+└──────────────┬───────────────────┘
+ │
+ ┌──────────┴──────────┐
+ │ │
+┌───┴──────┐ ┌──────┴─────┐
+│ Supabase │ │ Neo4j │
+│ (Vector) │ │ (Graph) │
+└──────────┘ └────────────┘
+```
+
+### Storage Layers
+
+- **Vector Store (Supabase)**: Semantic similarity search with pgvector
+- **Graph Store (Neo4j)**: Relationship modeling between memories
+- **Key-Value Store (PostgreSQL JSONB)**: Flexible metadata storage
+
+## Performance
+
+Based on mem0.ai research:
+
+- **26% higher accuracy** compared to baseline OpenAI
+- **91% lower latency** than full-context approaches
+- **90% token cost savings** through selective retrieval
+
+## Use Cases
+
+
+
+ Maintain context across conversations, remember user preferences, and provide personalized responses
+
+
+ Give agents long-term memory, enable learning from past interactions, and improve decision-making
+
+
+ Remember customer history, track issues across sessions, and provide consistent support
+
+
+ Track learning progress, adapt to user knowledge level, and personalize content delivery
+
+
+
+## Quick Links
+
+
+
+ Get up and running in 5 minutes
+
+
+ Understand the system design
+
+
+ Explore the REST API
+
+
+ Connect with Claude Code
+
+
+
+## Technology Stack
+
+- **Core**: mem0ai library
+- **Vector DB**: Supabase with pgvector
+- **Graph DB**: Neo4j 5.x
+- **LLM**: OpenAI API (Phase 1), Ollama (Phase 2)
+- **REST API**: FastAPI + Pydantic
+- **MCP**: Python MCP SDK
+- **Container**: Docker & Docker Compose
+
+## Support & Community
+
+- **Repository**: [git.colsys.tech/klas/t6_mem0_v2](https://git.colsys.tech/klas/t6_mem0_v2)
+- **mem0.ai**: [Official mem0 website](https://mem0.ai)
+- **Issues**: Contact maintainer
+
+---
+
+Ready to get started? Continue to the [Quickstart Guide](/quickstart).
diff --git a/docs/mint.json b/docs/mint.json
new file mode 100644
index 0000000..add5940
--- /dev/null
+++ b/docs/mint.json
@@ -0,0 +1,111 @@
+{
+ "name": "T6 Mem0 v2",
+ "logo": {
+ "dark": "/logo/dark.svg",
+ "light": "/logo/light.svg"
+ },
+ "favicon": "/favicon.svg",
+ "colors": {
+ "primary": "#0D9373",
+ "light": "#07C983",
+ "dark": "#0D9373",
+ "anchors": {
+ "from": "#0D9373",
+ "to": "#07C983"
+ }
+ },
+ "topbarLinks": [
+ {
+ "name": "Support",
+ "url": "mailto:support@example.com"
+ }
+ ],
+ "topbarCtaButton": {
+ "name": "Dashboard",
+ "url": "https://git.colsys.tech/klas/t6_mem0_v2"
+ },
+ "tabs": [
+ {
+ "name": "API Reference",
+ "url": "api-reference"
+ },
+ {
+ "name": "MCP Integration",
+ "url": "mcp"
+ }
+ ],
+ "anchors": [
+ {
+ "name": "GitHub",
+ "icon": "github",
+ "url": "https://git.colsys.tech/klas/t6_mem0_v2"
+ },
+ {
+ "name": "mem0.ai",
+ "icon": "link",
+ "url": "https://mem0.ai"
+ }
+ ],
+ "navigation": [
+ {
+ "group": "Get Started",
+ "pages": [
+ "introduction",
+ "quickstart",
+ "architecture"
+ ]
+ },
+ {
+ "group": "Setup",
+ "pages": [
+ "setup/installation",
+ "setup/configuration",
+ "setup/supabase",
+ "setup/neo4j"
+ ]
+ },
+ {
+ "group": "API Documentation",
+ "pages": [
+ "api-reference/introduction",
+ "api-reference/authentication"
+ ]
+ },
+ {
+ "group": "Memory Operations",
+ "pages": [
+ "api-reference/memories/add",
+ "api-reference/memories/search",
+ "api-reference/memories/get",
+ "api-reference/memories/update",
+ "api-reference/memories/delete"
+ ]
+ },
+ {
+ "group": "System",
+ "pages": [
+ "api-reference/health",
+ "api-reference/stats"
+ ]
+ },
+ {
+ "group": "MCP Server",
+ "pages": [
+ "mcp/introduction",
+ "mcp/installation",
+ "mcp/tools"
+ ]
+ },
+ {
+ "group": "Examples",
+ "pages": [
+ "examples/claude-code",
+ "examples/n8n",
+ "examples/python"
+ ]
+ }
+ ],
+ "footerSocials": {
+ "github": "https://git.colsys.tech/klas/t6_mem0_v2"
+ }
+}
diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx
new file mode 100644
index 0000000..915a548
--- /dev/null
+++ b/docs/quickstart.mdx
@@ -0,0 +1,259 @@
+---
+title: 'Quickstart'
+description: 'Get T6 Mem0 v2 running in 5 minutes'
+---
+
+## Prerequisites
+
+Before you begin, ensure you have:
+
+- Docker and Docker Compose installed
+- Existing Supabase instance (PostgreSQL with pgvector)
+- OpenAI API key
+- Git access to the repository
+
+
+ **Ready to go?** Let's set up your memory system!
+
+
+## Step 1: Clone Repository
+
+```bash
+git clone https://git.colsys.tech/klas/t6_mem0_v2
+cd t6_mem0_v2
+```
+
+## Step 2: Configure Environment
+
+Create `.env` file from template:
+
+```bash
+cp .env.example .env
+```
+
+Edit `.env` with your credentials:
+
+```bash
+# OpenAI Configuration
+OPENAI_API_KEY=sk-your-openai-api-key-here
+
+# Supabase Configuration (your existing instance)
+SUPABASE_CONNECTION_STRING=postgresql://supabase_admin:password@172.21.0.12:5432/postgres
+
+# Neo4j Configuration
+NEO4J_PASSWORD=your-secure-neo4j-password
+
+# API Configuration
+API_KEY=your-secure-api-key-here
+```
+
+
+ **Important**: Replace all placeholder values with your actual credentials. Never commit the `.env` file to version control!
+
+
+## Step 3: Apply Database Migrations
+
+Run the Supabase migration to set up the vector store:
+
+### Option A: Using Supabase SQL Editor (Recommended)
+
+1. Open your Supabase dashboard
+2. Navigate to **SQL Editor**
+3. Copy contents from `migrations/supabase/001_init_vector_store.sql`
+4. Paste and execute
+
+### Option B: Using psql
+
+```bash
+psql "$SUPABASE_CONNECTION_STRING" -f migrations/supabase/001_init_vector_store.sql
+```
+
+
+ The migration creates tables, indexes, and functions needed for vector similarity search. See [Supabase Setup](/setup/supabase) for details.
+
+
+## Step 4: Start Services
+
+Launch all services with Docker Compose:
+
+```bash
+docker compose up -d
+```
+
+This starts:
+- **Neo4j** (ports 7474, 7687)
+- **REST API** (port 8080)
+- **MCP Server** (port 8765)
+
+## Step 5: Verify Installation
+
+Check service health:
+
+```bash
+# Check API health
+curl http://localhost:8080/v1/health
+
+# Expected response:
+# {
+# "status": "healthy",
+# "version": "0.1.0",
+# "dependencies": {
+# "mem0": "healthy"
+# }
+# }
+```
+
+
+ **Success!** All services are running. Let's try using the memory system.
+
+
+## Step 6: Add Your First Memory
+
+### Using REST API
+
+```bash
+curl -X POST http://localhost:8080/v1/memories/ \
+ -H "Authorization: Bearer YOUR_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "messages": [
+ {"role": "user", "content": "I love pizza with mushrooms and olives"}
+ ],
+ "user_id": "alice"
+ }'
+```
+
+### Response
+
+```json
+{
+ "status": "success",
+ "memories": [
+ {
+ "id": "mem_abc123",
+ "memory": "User loves pizza with mushrooms and olives",
+ "user_id": "alice",
+ "created_at": "2025-10-13T12:00:00Z"
+ }
+ ],
+ "message": "Successfully added 1 memory(ies)"
+}
+```
+
+## Step 7: Search Memories
+
+```bash
+curl -X GET "http://localhost:8080/v1/memories/search?query=What food does Alice like?&user_id=alice" \
+ -H "Authorization: Bearer YOUR_API_KEY"
+```
+
+### Response
+
+```json
+{
+ "status": "success",
+ "memories": [
+ {
+ "id": "mem_abc123",
+ "memory": "User loves pizza with mushrooms and olives",
+ "user_id": "alice",
+ "score": 0.95,
+ "created_at": "2025-10-13T12:00:00Z"
+ }
+ ],
+ "count": 1
+}
+```
+
+
+ **Congratulations!** Your memory system is working. The AI remembered Alice's food preferences and retrieved them semantically.
+
+
+## Step 8: Explore Neo4j (Optional)
+
+View memory relationships in Neo4j Browser:
+
+1. Open http://localhost:7474 in your browser
+2. Login with:
+ - **Username**: `neo4j`
+ - **Password**: (from your `.env` NEO4J_PASSWORD)
+3. Run query:
+
+```cypher
+MATCH (n) RETURN n LIMIT 25
+```
+
+
+ Neo4j visualizes relationships between memories, entities, and concepts extracted by mem0.
+
+
+## Next Steps
+
+
+
+ Connect with Claude Code for AI assistant integration
+
+
+ Explore all available endpoints
+
+
+ Understand the system design
+
+
+ See integration examples
+
+
+
+## Common Issues
+
+
+
+ - Verify `SUPABASE_CONNECTION_STRING` is correct
+ - Ensure Supabase is accessible from Docker network
+ - Check if pgvector extension is enabled
+ - Run migration script if tables don't exist
+
+
+
+ - Check if ports 7474 and 7687 are available
+ - Verify `NEO4J_PASSWORD` is set in `.env`
+ - Check Docker logs: `docker logs t6-mem0-neo4j`
+
+
+
+ - Verify `API_KEY` in `.env` matches request header
+ - Ensure Authorization header format: `Bearer YOUR_API_KEY`
+
+
+
+ - Check `OPENAI_API_KEY` is valid
+ - Verify API key has sufficient credits
+ - Check internet connectivity from containers
+
+
+
+## Getting Help
+
+- Review [Architecture documentation](/architecture)
+- Check [API Reference](/api-reference/introduction)
+- See [Setup guides](/setup/installation)
+
+---
+
+**Ready for production?** Continue to [Configuration](/setup/configuration) for advanced settings.
diff --git a/mcp-server/__init__.py b/mcp-server/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/mcp-server/main.py b/mcp-server/main.py
new file mode 100644
index 0000000..b4bd0e3
--- /dev/null
+++ b/mcp-server/main.py
@@ -0,0 +1,165 @@
+"""
+T6 Mem0 v2 MCP Server
+Model Context Protocol server for memory operations
+"""
+
+import logging
+import sys
+import asyncio
+from mcp.server import Server
+from mcp.server.stdio import stdio_server
+from mcp.types import (
+ Resource,
+ Tool,
+ TextContent,
+ ImageContent,
+ EmbeddedResource
+)
+from mem0 import Memory
+
+from config import mem0_config, settings
+from mcp_server.tools import MemoryTools
+
+# Configure logging
+logging.basicConfig(
+ level=getattr(logging, settings.log_level.upper()),
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ handlers=[logging.StreamHandler(sys.stderr)] # MCP uses stderr for logs
+)
+
+logger = logging.getLogger(__name__)
+
+
+class T6Mem0Server:
+ """T6 Mem0 v2 MCP Server"""
+
+ def __init__(self):
+ """Initialize MCP server"""
+ self.server = Server("t6-mem0-v2")
+ self.memory: Memory | None = None
+ self.tools: MemoryTools | None = None
+
+ # Setup handlers
+ self.setup_handlers()
+
+ def setup_handlers(self):
+ """Setup MCP server handlers"""
+
+ @self.server.list_resources()
+ async def list_resources() -> list[Resource]:
+ """
+ List available resources (for future extension)
+ """
+ return []
+
+ @self.server.read_resource()
+ async def read_resource(uri: str) -> str:
+ """
+ Read resource by URI (for future extension)
+ """
+ logger.warning(f"Resource read not implemented: {uri}")
+ return ""
+
+ @self.server.list_tools()
+ async def list_tools() -> list[Tool]:
+ """List available memory tools"""
+ logger.info("Listing tools")
+ if not self.tools:
+ raise RuntimeError("Tools not initialized")
+ return self.tools.get_tool_definitions()
+
+ @self.server.call_tool()
+ async def call_tool(name: str, arguments: dict) -> list[TextContent | ImageContent | EmbeddedResource]:
+ """
+ Handle tool calls
+
+ Args:
+ name: Tool name
+ arguments: Tool arguments
+
+ Returns:
+ Tool response
+ """
+ logger.info(f"Tool called: {name}")
+ logger.debug(f"Arguments: {arguments}")
+
+ if not self.tools:
+ raise RuntimeError("Tools not initialized")
+
+ # Route to appropriate handler
+ handlers = {
+ "add_memory": self.tools.handle_add_memory,
+ "search_memories": self.tools.handle_search_memories,
+ "get_memory": self.tools.handle_get_memory,
+ "get_all_memories": self.tools.handle_get_all_memories,
+ "update_memory": self.tools.handle_update_memory,
+ "delete_memory": self.tools.handle_delete_memory,
+ "delete_all_memories": self.tools.handle_delete_all_memories,
+ }
+
+ handler = handlers.get(name)
+ if not handler:
+ logger.error(f"Unknown tool: {name}")
+ return [TextContent(type="text", text=f"Error: Unknown tool '{name}'")]
+
+ try:
+ return await handler(arguments)
+ except Exception as e:
+ logger.error(f"Tool execution failed: {e}", exc_info=True)
+ return [TextContent(type="text", text=f"Error executing tool: {str(e)}")]
+
+ async def initialize(self):
+ """Initialize memory service"""
+ logger.info("Initializing T6 Mem0 v2 MCP Server")
+ logger.info(f"Environment: {settings.environment}")
+
+ try:
+ # Initialize Mem0
+ logger.info("Initializing Mem0...")
+ self.memory = Memory.from_config(config_dict=mem0_config)
+ logger.info("Mem0 initialized successfully")
+
+ # Initialize tools
+ self.tools = MemoryTools(self.memory)
+ logger.info("Tools initialized successfully")
+
+ logger.info("T6 Mem0 v2 MCP Server ready")
+
+ except Exception as e:
+ logger.error(f"Failed to initialize server: {e}", exc_info=True)
+ raise
+
+ async def run(self):
+ """Run the MCP server"""
+ try:
+ # Initialize before running
+ await self.initialize()
+
+ # Run server with stdio transport
+ logger.info("Starting MCP server with stdio transport")
+ async with stdio_server() as (read_stream, write_stream):
+ await self.server.run(
+ read_stream,
+ write_stream,
+ self.server.create_initialization_options()
+ )
+
+ except Exception as e:
+ logger.error(f"Server error: {e}", exc_info=True)
+ raise
+
+
+async def main():
+ """Main entry point"""
+ try:
+ server = T6Mem0Server()
+ await server.run()
+ except KeyboardInterrupt:
+ logger.info("Server stopped by user")
+ except Exception as e:
+ logger.error(f"Fatal error: {e}", exc_info=True)
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/mcp-server/run.sh b/mcp-server/run.sh
new file mode 100755
index 0000000..0ea6bfc
--- /dev/null
+++ b/mcp-server/run.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+# Run T6 Mem0 v2 MCP Server
+
+set -e
+
+# Load environment variables
+if [ -f ../.env ]; then
+ export $(cat ../.env | grep -v '^#' | xargs)
+fi
+
+# Run MCP server
+echo "Starting T6 Mem0 v2 MCP Server..."
+python -m mcp_server.main
diff --git a/mcp-server/tools.py b/mcp-server/tools.py
new file mode 100644
index 0000000..0abff21
--- /dev/null
+++ b/mcp-server/tools.py
@@ -0,0 +1,343 @@
+"""
+MCP tools for T6 Mem0 v2
+Tool definitions and handlers
+"""
+
+import logging
+from typing import Any, Dict, List
+from mcp.types import Tool, TextContent
+from mem0 import Memory
+
+logger = logging.getLogger(__name__)
+
+
+class MemoryTools:
+ """MCP tools for memory operations"""
+
+ def __init__(self, memory: Memory):
+ """
+ Initialize memory tools
+
+ Args:
+ memory: Mem0 instance
+ """
+ self.memory = memory
+
+ def get_tool_definitions(self) -> List[Tool]:
+ """
+ Get MCP tool definitions
+
+ Returns:
+ List of Tool definitions
+ """
+ return [
+ Tool(
+ name="add_memory",
+ description="Add new memory from messages. Extracts and stores important information from conversation.",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "messages": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "role": {"type": "string", "enum": ["user", "assistant", "system"]},
+ "content": {"type": "string"}
+ },
+ "required": ["role", "content"]
+ },
+ "description": "Conversation messages to extract memory from"
+ },
+ "user_id": {
+ "type": "string",
+ "description": "User identifier (optional)"
+ },
+ "agent_id": {
+ "type": "string",
+ "description": "Agent identifier (optional)"
+ },
+ "metadata": {
+ "type": "object",
+ "description": "Additional metadata (optional)"
+ }
+ },
+ "required": ["messages"]
+ }
+ ),
+ Tool(
+ name="search_memories",
+ description="Search memories by semantic similarity. Find relevant memories based on a query.",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query"
+ },
+ "user_id": {
+ "type": "string",
+ "description": "Filter by user ID (optional)"
+ },
+ "agent_id": {
+ "type": "string",
+ "description": "Filter by agent ID (optional)"
+ },
+ "limit": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 50,
+ "default": 10,
+ "description": "Maximum number of results"
+ }
+ },
+ "required": ["query"]
+ }
+ ),
+ Tool(
+ name="get_memory",
+ description="Get a specific memory by its ID",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "memory_id": {
+ "type": "string",
+ "description": "Memory identifier"
+ }
+ },
+ "required": ["memory_id"]
+ }
+ ),
+ Tool(
+ name="get_all_memories",
+ description="Get all memories for a user or agent",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "user_id": {
+ "type": "string",
+ "description": "User identifier (optional)"
+ },
+ "agent_id": {
+ "type": "string",
+ "description": "Agent identifier (optional)"
+ }
+ }
+ }
+ ),
+ Tool(
+ name="update_memory",
+ description="Update an existing memory's content",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "memory_id": {
+ "type": "string",
+ "description": "Memory identifier"
+ },
+ "data": {
+ "type": "string",
+ "description": "New memory content"
+ }
+ },
+ "required": ["memory_id", "data"]
+ }
+ ),
+ Tool(
+ name="delete_memory",
+ description="Delete a specific memory by ID",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "memory_id": {
+ "type": "string",
+ "description": "Memory identifier"
+ }
+ },
+ "required": ["memory_id"]
+ }
+ ),
+ Tool(
+ name="delete_all_memories",
+ description="Delete all memories for a user or agent. Use with caution!",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "user_id": {
+ "type": "string",
+ "description": "User identifier (optional)"
+ },
+ "agent_id": {
+ "type": "string",
+ "description": "Agent identifier (optional)"
+ }
+ }
+ }
+ )
+ ]
+
+ async def handle_add_memory(self, arguments: Dict[str, Any]) -> List[TextContent]:
+ """Handle add_memory tool call"""
+ try:
+ messages = arguments.get("messages", [])
+ user_id = arguments.get("user_id")
+ agent_id = arguments.get("agent_id")
+ metadata = arguments.get("metadata", {})
+
+ result = self.memory.add(
+ messages=messages,
+ user_id=user_id,
+ agent_id=agent_id,
+ metadata=metadata
+ )
+
+ memories = result.get('results', [])
+ response = f"Successfully added {len(memories)} memory(ies):\n\n"
+
+ for mem in memories:
+ response += f"- {mem.get('memory', mem.get('data', 'N/A'))}\n"
+ response += f" ID: {mem.get('id', 'N/A')}\n\n"
+
+ return [TextContent(type="text", text=response)]
+
+ except Exception as e:
+ logger.error(f"Error adding memory: {e}")
+ return [TextContent(type="text", text=f"Error: {str(e)}")]
+
+ async def handle_search_memories(self, arguments: Dict[str, Any]) -> List[TextContent]:
+ """Handle search_memories tool call"""
+ try:
+ query = arguments.get("query")
+ user_id = arguments.get("user_id")
+ agent_id = arguments.get("agent_id")
+ limit = arguments.get("limit", 10)
+
+ memories = self.memory.search(
+ query=query,
+ user_id=user_id,
+ agent_id=agent_id,
+ limit=limit
+ )
+
+ if not memories:
+ return [TextContent(type="text", text="No memories found matching your query.")]
+
+ response = f"Found {len(memories)} relevant memory(ies):\n\n"
+
+ for i, mem in enumerate(memories, 1):
+ response += f"{i}. {mem.get('memory', mem.get('data', 'N/A'))}\n"
+ response += f" ID: {mem.get('id', 'N/A')}\n"
+ if 'score' in mem:
+ response += f" Relevance: {mem['score']:.2%}\n"
+ response += "\n"
+
+ return [TextContent(type="text", text=response)]
+
+ except Exception as e:
+ logger.error(f"Error searching memories: {e}")
+ return [TextContent(type="text", text=f"Error: {str(e)}")]
+
+ async def handle_get_memory(self, arguments: Dict[str, Any]) -> List[TextContent]:
+ """Handle get_memory tool call"""
+ try:
+ memory_id = arguments.get("memory_id")
+
+ memory = self.memory.get(memory_id=memory_id)
+
+ if not memory:
+ return [TextContent(type="text", text=f"Memory not found: {memory_id}")]
+
+ response = f"Memory Details:\n\n"
+ response += f"ID: {memory.get('id', 'N/A')}\n"
+ response += f"Content: {memory.get('memory', memory.get('data', 'N/A'))}\n"
+
+ if memory.get('user_id'):
+ response += f"User ID: {memory['user_id']}\n"
+ if memory.get('agent_id'):
+ response += f"Agent ID: {memory['agent_id']}\n"
+ if memory.get('metadata'):
+ response += f"Metadata: {memory['metadata']}\n"
+
+ return [TextContent(type="text", text=response)]
+
+ except Exception as e:
+ logger.error(f"Error getting memory: {e}")
+ return [TextContent(type="text", text=f"Error: {str(e)}")]
+
+ async def handle_get_all_memories(self, arguments: Dict[str, Any]) -> List[TextContent]:
+ """Handle get_all_memories tool call"""
+ try:
+ user_id = arguments.get("user_id")
+ agent_id = arguments.get("agent_id")
+
+ memories = self.memory.get_all(
+ user_id=user_id,
+ agent_id=agent_id
+ )
+
+ if not memories:
+ return [TextContent(type="text", text="No memories found.")]
+
+ response = f"Retrieved {len(memories)} memory(ies):\n\n"
+
+ for i, mem in enumerate(memories, 1):
+ response += f"{i}. {mem.get('memory', mem.get('data', 'N/A'))}\n"
+ response += f" ID: {mem.get('id', 'N/A')}\n\n"
+
+ return [TextContent(type="text", text=response)]
+
+ except Exception as e:
+ logger.error(f"Error getting all memories: {e}")
+ return [TextContent(type="text", text=f"Error: {str(e)}")]
+
+ async def handle_update_memory(self, arguments: Dict[str, Any]) -> List[TextContent]:
+ """Handle update_memory tool call"""
+ try:
+ memory_id = arguments.get("memory_id")
+ data = arguments.get("data")
+
+ result = self.memory.update(
+ memory_id=memory_id,
+ data=data
+ )
+
+ response = f"Memory updated successfully:\n\n"
+ response += f"ID: {result.get('id', memory_id)}\n"
+ response += f"New Content: {data}\n"
+
+ return [TextContent(type="text", text=response)]
+
+ except Exception as e:
+ logger.error(f"Error updating memory: {e}")
+ return [TextContent(type="text", text=f"Error: {str(e)}")]
+
+ async def handle_delete_memory(self, arguments: Dict[str, Any]) -> List[TextContent]:
+ """Handle delete_memory tool call"""
+ try:
+ memory_id = arguments.get("memory_id")
+
+ self.memory.delete(memory_id=memory_id)
+
+ return [TextContent(type="text", text=f"Memory {memory_id} deleted successfully.")]
+
+ except Exception as e:
+ logger.error(f"Error deleting memory: {e}")
+ return [TextContent(type="text", text=f"Error: {str(e)}")]
+
+ async def handle_delete_all_memories(self, arguments: Dict[str, Any]) -> List[TextContent]:
+ """Handle delete_all_memories tool call"""
+ try:
+ user_id = arguments.get("user_id")
+ agent_id = arguments.get("agent_id")
+
+ self.memory.delete_all(
+ user_id=user_id,
+ agent_id=agent_id
+ )
+
+ filter_str = f"user_id={user_id}" if user_id else f"agent_id={agent_id}" if agent_id else "all filters"
+ return [TextContent(type="text", text=f"All memories deleted for {filter_str}.")]
+
+ except Exception as e:
+ logger.error(f"Error deleting all memories: {e}")
+ return [TextContent(type="text", text=f"Error: {str(e)}")]
diff --git a/migrations/supabase/001_init_vector_store.sql b/migrations/supabase/001_init_vector_store.sql
new file mode 100644
index 0000000..94112f5
--- /dev/null
+++ b/migrations/supabase/001_init_vector_store.sql
@@ -0,0 +1,172 @@
+-- T6 Mem0 v2 - Initial Vector Store Setup
+-- This migration creates the necessary tables and functions for Mem0 vector storage
+
+-- Enable pgvector extension
+CREATE EXTENSION IF NOT EXISTS vector;
+
+-- Create memories table
+CREATE TABLE IF NOT EXISTS t6_memories (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ embedding vector(1536), -- OpenAI text-embedding-3-small dimension
+ metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
+ user_id TEXT,
+ agent_id TEXT,
+ run_id TEXT,
+ memory_text TEXT NOT NULL,
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+ hash TEXT, -- For deduplication
+
+ -- Indexes
+ CONSTRAINT t6_memories_hash_unique UNIQUE (hash)
+);
+
+-- Create index on embedding using HNSW for fast similarity search
+CREATE INDEX IF NOT EXISTS t6_memories_embedding_idx
+ON t6_memories
+USING hnsw (embedding vector_cosine_ops);
+
+-- Create indexes on metadata fields for filtering
+CREATE INDEX IF NOT EXISTS t6_memories_user_id_idx
+ON t6_memories (user_id);
+
+CREATE INDEX IF NOT EXISTS t6_memories_agent_id_idx
+ON t6_memories (agent_id);
+
+CREATE INDEX IF NOT EXISTS t6_memories_run_id_idx
+ON t6_memories (run_id);
+
+CREATE INDEX IF NOT EXISTS t6_memories_created_at_idx
+ON t6_memories (created_at DESC);
+
+-- Create GIN index on metadata for JSON queries
+CREATE INDEX IF NOT EXISTS t6_memories_metadata_idx
+ON t6_memories
+USING GIN (metadata);
+
+-- Create full-text search index on memory_text
+CREATE INDEX IF NOT EXISTS t6_memories_text_search_idx
+ON t6_memories
+USING GIN (to_tsvector('english', memory_text));
+
+-- Function to update the updated_at timestamp
+CREATE OR REPLACE FUNCTION update_t6_memories_updated_at()
+RETURNS TRIGGER AS $$
+BEGIN
+ NEW.updated_at = NOW();
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+-- Trigger to automatically update updated_at
+CREATE TRIGGER t6_memories_updated_at_trigger
+ BEFORE UPDATE ON t6_memories
+ FOR EACH ROW
+ EXECUTE FUNCTION update_t6_memories_updated_at();
+
+-- Function for vector similarity search with filters
+CREATE OR REPLACE FUNCTION match_t6_memories(
+ query_embedding vector(1536),
+ match_count INT DEFAULT 10,
+ filter_user_id TEXT DEFAULT NULL,
+ filter_agent_id TEXT DEFAULT NULL,
+ filter_run_id TEXT DEFAULT NULL
+)
+RETURNS TABLE (
+ id UUID,
+ memory_text TEXT,
+ metadata JSONB,
+ user_id TEXT,
+ agent_id TEXT,
+ run_id TEXT,
+ similarity FLOAT,
+ created_at TIMESTAMP WITH TIME ZONE
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RETURN QUERY
+ SELECT
+ t6_memories.id,
+ t6_memories.memory_text,
+ t6_memories.metadata,
+ t6_memories.user_id,
+ t6_memories.agent_id,
+ t6_memories.run_id,
+ 1 - (t6_memories.embedding <=> query_embedding) AS similarity,
+ t6_memories.created_at
+ FROM t6_memories
+ WHERE
+ (filter_user_id IS NULL OR t6_memories.user_id = filter_user_id) AND
+ (filter_agent_id IS NULL OR t6_memories.agent_id = filter_agent_id) AND
+ (filter_run_id IS NULL OR t6_memories.run_id = filter_run_id)
+ ORDER BY t6_memories.embedding <=> query_embedding
+ LIMIT match_count;
+END;
+$$;
+
+-- Function to get memory statistics
+CREATE OR REPLACE FUNCTION get_t6_memory_stats()
+RETURNS TABLE (
+ total_memories BIGINT,
+ total_users BIGINT,
+ total_agents BIGINT,
+ avg_memories_per_user NUMERIC,
+ oldest_memory TIMESTAMP WITH TIME ZONE,
+ newest_memory TIMESTAMP WITH TIME ZONE
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RETURN QUERY
+ SELECT
+ COUNT(*)::BIGINT AS total_memories,
+ COUNT(DISTINCT user_id)::BIGINT AS total_users,
+ COUNT(DISTINCT agent_id)::BIGINT AS total_agents,
+ CASE
+ WHEN COUNT(DISTINCT user_id) > 0
+ THEN ROUND(COUNT(*)::NUMERIC / COUNT(DISTINCT user_id), 2)
+ ELSE 0
+ END AS avg_memories_per_user,
+ MIN(created_at) AS oldest_memory,
+ MAX(created_at) AS newest_memory
+ FROM t6_memories;
+END;
+$$;
+
+-- Create a view for recent memories
+CREATE OR REPLACE VIEW t6_recent_memories AS
+SELECT
+ id,
+ user_id,
+ agent_id,
+ run_id,
+ memory_text,
+ metadata,
+ created_at,
+ updated_at
+FROM t6_memories
+ORDER BY created_at DESC
+LIMIT 100;
+
+-- Grant necessary permissions (adjust as needed for your setup)
+-- GRANT SELECT, INSERT, UPDATE, DELETE ON t6_memories TO authenticated;
+-- GRANT EXECUTE ON FUNCTION match_t6_memories TO authenticated;
+-- GRANT EXECUTE ON FUNCTION get_t6_memory_stats TO authenticated;
+
+-- Comments for documentation
+COMMENT ON TABLE t6_memories IS 'Storage for T6 Mem0 v2 memory vectors and metadata';
+COMMENT ON COLUMN t6_memories.embedding IS 'OpenAI text-embedding-3-small vector (1536 dimensions)';
+COMMENT ON COLUMN t6_memories.metadata IS 'Flexible JSON metadata for additional memory properties';
+COMMENT ON COLUMN t6_memories.hash IS 'Hash for deduplication of identical memories';
+COMMENT ON FUNCTION match_t6_memories IS 'Performs cosine similarity search on memory embeddings with optional filters';
+COMMENT ON FUNCTION get_t6_memory_stats IS 'Returns statistics about stored memories';
+
+-- Success message
+DO $$
+BEGIN
+ RAISE NOTICE 'T6 Mem0 v2 vector store initialized successfully!';
+ RAISE NOTICE 'Table: t6_memories';
+ RAISE NOTICE 'Functions: match_t6_memories, get_t6_memory_stats';
+ RAISE NOTICE 'View: t6_recent_memories';
+END $$;
diff --git a/migrations/supabase/README.md b/migrations/supabase/README.md
new file mode 100644
index 0000000..14f8406
--- /dev/null
+++ b/migrations/supabase/README.md
@@ -0,0 +1,153 @@
+# Supabase Migrations for T6 Mem0 v2
+
+## Overview
+
+This directory contains SQL migrations for setting up the Supabase vector store used by T6 Mem0 v2.
+
+## Migrations
+
+### 001_init_vector_store.sql
+
+Initial setup migration that creates:
+
+- **pgvector extension**: Enables vector similarity search
+- **t6_memories table**: Main storage for memory vectors and metadata
+- **Indexes**: HNSW for vectors, B-tree for filters, GIN for JSONB
+- **Functions**:
+ - `match_t6_memories()`: Vector similarity search with filters
+ - `get_t6_memory_stats()`: Memory statistics
+ - `update_t6_memories_updated_at()`: Auto-update timestamp
+- **View**: `t6_recent_memories` for quick access to recent entries
+
+## Applying Migrations
+
+### Method 1: Supabase SQL Editor (Recommended)
+
+1. Open your Supabase project dashboard
+2. Navigate to SQL Editor
+3. Create a new query
+4. Copy and paste the contents of `001_init_vector_store.sql`
+5. Click "Run" to execute
+
+### Method 2: psql Command Line
+
+```bash
+# Connect to your Supabase database
+psql "postgresql://supabase_admin:PASSWORD@172.21.0.12:5432/postgres"
+
+# Run the migration
+\i migrations/supabase/001_init_vector_store.sql
+```
+
+### Method 3: Programmatic Application
+
+```python
+import psycopg2
+
+# Connect to Supabase
+conn = psycopg2.connect(
+ "postgresql://supabase_admin:PASSWORD@172.21.0.12:5432/postgres"
+)
+
+# Read and execute migration
+with open('migrations/supabase/001_init_vector_store.sql', 'r') as f:
+ migration_sql = f.read()
+
+with conn.cursor() as cur:
+ cur.execute(migration_sql)
+ conn.commit()
+
+conn.close()
+```
+
+## Verification
+
+After applying the migration, verify the setup:
+
+```sql
+-- Check if pgvector extension is enabled
+SELECT * FROM pg_extension WHERE extname = 'vector';
+
+-- Check if table exists
+\d t6_memories
+
+-- Verify indexes
+\di t6_memories*
+
+-- Test the similarity search function
+SELECT * FROM match_t6_memories(
+ '[0.1, 0.2, ...]'::vector(1536), -- Sample embedding
+ 10, -- Match count
+ 'test_user', -- User ID filter
+ NULL, -- Agent ID filter
+ NULL -- Run ID filter
+);
+
+-- Get memory statistics
+SELECT * FROM get_t6_memory_stats();
+```
+
+## Rollback
+
+If you need to rollback the migration:
+
+```sql
+-- Drop view
+DROP VIEW IF EXISTS t6_recent_memories;
+
+-- Drop functions
+DROP FUNCTION IF EXISTS get_t6_memory_stats();
+DROP FUNCTION IF EXISTS match_t6_memories(vector, INT, TEXT, TEXT, TEXT);
+DROP FUNCTION IF EXISTS update_t6_memories_updated_at();
+
+-- Drop trigger
+DROP TRIGGER IF EXISTS t6_memories_updated_at_trigger ON t6_memories;
+
+-- Drop table (WARNING: This will delete all data!)
+DROP TABLE IF EXISTS t6_memories CASCADE;
+
+-- Optionally remove extension (only if not used elsewhere)
+-- DROP EXTENSION IF EXISTS vector CASCADE;
+```
+
+## Schema
+
+### t6_memories Table
+
+| Column | Type | Description |
+|--------|------|-------------|
+| id | UUID | Primary key |
+| embedding | vector(1536) | OpenAI embedding vector |
+| metadata | JSONB | Flexible metadata |
+| user_id | TEXT | User identifier |
+| agent_id | TEXT | Agent identifier |
+| run_id | TEXT | Run identifier |
+| memory_text | TEXT | Original memory text |
+| created_at | TIMESTAMPTZ | Creation timestamp |
+| updated_at | TIMESTAMPTZ | Last update timestamp |
+| hash | TEXT | Deduplication hash (unique) |
+
+### Indexes
+
+- **t6_memories_embedding_idx**: HNSW index for fast vector search
+- **t6_memories_user_id_idx**: B-tree for user filtering
+- **t6_memories_agent_id_idx**: B-tree for agent filtering
+- **t6_memories_run_id_idx**: B-tree for run filtering
+- **t6_memories_created_at_idx**: B-tree for time-based queries
+- **t6_memories_metadata_idx**: GIN for JSON queries
+- **t6_memories_text_search_idx**: GIN for full-text search
+
+## Notes
+
+- The HNSW index provides O(log n) approximate nearest neighbor search
+- Cosine distance is used for similarity (1 - cosine similarity)
+- All timestamps are stored in UTC
+- The hash column ensures deduplication of identical memories
+- Metadata is stored as JSONB for flexible schema evolution
+
+## Support
+
+For issues or questions about migrations, refer to:
+- [Supabase Vector Documentation](https://supabase.com/docs/guides/database/extensions/pgvector)
+- [pgvector Documentation](https://github.com/pgvector/pgvector)
+- Project Architecture: `../../ARCHITECTURE.md`
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..725ecb8
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,34 @@
+# Core Memory System
+mem0ai[graph]==0.1.*
+
+# Web Framework
+fastapi==0.115.*
+uvicorn[standard]==0.32.*
+pydantic==2.9.*
+pydantic-settings==2.6.*
+
+# MCP Server
+mcp==1.3.*
+
+# Database Drivers
+psycopg2-binary==2.9.*
+neo4j==5.26.*
+
+# OpenAI
+openai==1.58.*
+
+# Utilities
+python-dotenv==1.0.*
+httpx==0.28.*
+pyyaml==6.0.*
+
+# Development
+pytest==8.3.*
+pytest-asyncio==0.24.*
+pytest-cov==6.0.*
+black==24.10.*
+ruff==0.8.*
+mypy==1.13.*
+
+# Monitoring
+prometheus-client==0.21.*
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/api/__init__.py b/tests/api/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/mcp-server/__init__.py b/tests/mcp-server/__init__.py
new file mode 100644
index 0000000..e69de29