docs: add hierarchical AGENTS.md knowledge base
- Update root AGENTS.md with regenerative content (144 lines, telegraphic) - Add pkgs/AGENTS.md for package registry conventions (33 lines) - Add docs/AGENTS.md for documentation structure (34 lines) - All files follow telegraphic style, no redundancy with parent - Preserves existing modules/home-manager/AGENTS.md Hierarchy: ./AGENTS.md (root) ├── pkgs/AGENTS.md ├── docs/AGENTS.md └── modules/home-manager/AGENTS.md (existing)
This commit is contained in:
33
pkgs/AGENTS.md
Normal file
33
pkgs/AGENTS.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# pkgs/ AGENTS.md
|
||||
|
||||
## OVERVIEW
|
||||
Custom package registry using `callPackage` pattern for flake-wide availability.
|
||||
|
||||
## STRUCTURE
|
||||
- `default.nix`: Central registry (entry point for overlays)
|
||||
- `code2prompt/`: Rust package
|
||||
- `hyprpaper-random/`: Bash script
|
||||
- `launch-webapp/`: Webapp wrapper
|
||||
- `mem0/`: Python package + custom `server.py`
|
||||
- `msty-studio/`: AppImage wrapper
|
||||
- `pomodoro-timer/`: Timer utility
|
||||
- `tuxedo-backlight/`: Hardware control
|
||||
- `zellij-ps/`: Gitea-hosted package
|
||||
|
||||
## WHERE TO LOOK
|
||||
- **Register new pkg**: Add entry to `pkgs/default.nix` attribute set
|
||||
- **Modify pkg**: Edit `pkgs/<name>/default.nix` (version, hash, deps)
|
||||
- **Check visibility**: `nix flake show` (uses `pkgs/default.nix` via `overlays/default.nix`)
|
||||
- **Add scripts**: Place alongside `default.nix` in package folder (e.g., `mem0/server.py`)
|
||||
|
||||
## CONVENTIONS
|
||||
- **CallPackage**: Always use `pkgs.callPackage ./dir {}` in registry
|
||||
- **Dir == Attr**: Package directory name MUST match its registry attribute
|
||||
- **Path literals**: Reference local assets using `./file` within derivations
|
||||
- **Self-contained**: Keep all package-specific files in their own directory
|
||||
|
||||
## ANTI-PATTERNS
|
||||
- **Orphaned dirs**: Creating `pkgs/new-pkg/` without updating `pkgs/default.nix`
|
||||
- **Direct flake imports**: Importing packages in `flake.nix` instead of through the registry
|
||||
- **Implicit deps**: Not declaring dependencies in the package function arguments
|
||||
- **Non-derivations**: Placing NixOS/HM modules here (use `modules/` instead)
|
||||
@@ -4,6 +4,7 @@
|
||||
code2prompt = pkgs.callPackage ./code2prompt {};
|
||||
hyprpaper-random = pkgs.callPackage ./hyprpaper-random {};
|
||||
launch-webapp = pkgs.callPackage ./launch-webapp {};
|
||||
mem0 = pkgs.callPackage ./mem0 {};
|
||||
msty-studio = pkgs.callPackage ./msty-studio {};
|
||||
pomodoro-timer = pkgs.callPackage ./pomodoro-timer {};
|
||||
tuxedo-backlight = pkgs.callPackage ./tuxedo-backlight {};
|
||||
|
||||
99
pkgs/mem0/default.nix
Normal file
99
pkgs/mem0/default.nix
Normal file
@@ -0,0 +1,99 @@
|
||||
{
|
||||
lib,
|
||||
python3,
|
||||
fetchFromGitHub,
|
||||
}:
|
||||
python3.pkgs.buildPythonPackage rec {
|
||||
pname = "mem0ai";
|
||||
version = "1.0.0";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "mem0ai";
|
||||
repo = "mem0";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-DluzIhwL/GanqtRSMW7ax+OVc2kZjbwQ0lpPCQFnD58=";
|
||||
};
|
||||
|
||||
# Relax Python dependency version constraints
|
||||
# mem0 has strict version pins that may not match nixpkgs versions
|
||||
pythonRelaxDeps = true;
|
||||
|
||||
build-system = with python3.pkgs; [
|
||||
hatchling
|
||||
];
|
||||
|
||||
dependencies = with python3.pkgs; [
|
||||
litellm
|
||||
qdrant-client
|
||||
pydantic
|
||||
openai
|
||||
posthog
|
||||
pytz
|
||||
sqlalchemy
|
||||
protobuf
|
||||
uvicorn
|
||||
fastapi
|
||||
];
|
||||
|
||||
optional-dependencies = with python3.pkgs; {
|
||||
graph = [
|
||||
# Note: some graph dependencies may not be available in nixpkgs
|
||||
# neo4j is available, others will need to be packaged separately
|
||||
];
|
||||
vector_stores = [
|
||||
# chromadb # available in nixpkgs
|
||||
# pinecone-client # may need packaging
|
||||
# weaviate-client # may need packaging
|
||||
# faiss # available as faiss-cpu
|
||||
psycopg
|
||||
pymongo
|
||||
pymysql
|
||||
redis
|
||||
elasticsearch
|
||||
];
|
||||
llms = [
|
||||
groq
|
||||
openai
|
||||
# together # may need packaging
|
||||
# litellm # may need packaging
|
||||
# ollama # may need packaging
|
||||
# google-generativeai # may need packaging
|
||||
];
|
||||
extras = [
|
||||
boto3
|
||||
# langchain-community # may need packaging
|
||||
# sentence-transformers # may need packaging
|
||||
elasticsearch
|
||||
# fastembed # may need packaging
|
||||
];
|
||||
};
|
||||
|
||||
# Skip tests for now since they require additional test dependencies
|
||||
doCheck = false;
|
||||
|
||||
# Disable imports check because mem0 tries to create directories at import time
|
||||
# which fails in the Nix sandbox (/homeless-shelter)
|
||||
pythonImportsCheck = [];
|
||||
|
||||
postInstall = ''
|
||||
install -Dm755 ${./server.py} $out/bin/mem0-server
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
description = "Long-term memory layer for AI agents with REST API support";
|
||||
longDescription = ''
|
||||
Mem0 provides a sophisticated memory layer for AI applications, offering:
|
||||
- Memory management for AI agents (add, search, update, delete)
|
||||
- REST API server for easy integration
|
||||
- Support for multiple vector storage backends (Qdrant, Chroma, etc.)
|
||||
- Graph memory capabilities
|
||||
- Multi-modal support
|
||||
- Configurable LLM and embedding models
|
||||
'';
|
||||
homepage = "https://github.com/mem0ai/mem0";
|
||||
changelog = "https://github.com/mem0ai/mem0/releases/tag/v${version}";
|
||||
license = licenses.asl20;
|
||||
platforms = platforms.linux;
|
||||
};
|
||||
}
|
||||
301
pkgs/mem0/server.py
Normal file
301
pkgs/mem0/server.py
Normal file
@@ -0,0 +1,301 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Mem0 REST API Server
|
||||
A FastAPI-based REST server for mem0 memory operations.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import JSONResponse, RedirectResponse
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from mem0 import Memory
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
||||
|
||||
|
||||
# Configuration from environment variables
|
||||
def get_config_from_env() -> Dict[str, Any]:
|
||||
"""Build mem0 configuration from environment variables."""
|
||||
config = {"version": "v1.1"}
|
||||
|
||||
# Vector store configuration
|
||||
vector_provider = os.environ.get("MEM0_VECTOR_PROVIDER", "qdrant")
|
||||
config["vector_store"] = {"provider": vector_provider}
|
||||
|
||||
if vector_provider == "qdrant":
|
||||
config["vector_store"]["config"] = {
|
||||
"host": os.environ.get("QDRANT_HOST", "localhost"),
|
||||
"port": int(os.environ.get("QDRANT_PORT", "6333")),
|
||||
"collection_name": os.environ.get("QDRANT_COLLECTION", "mem0_memories"),
|
||||
}
|
||||
elif vector_provider == "pgvector":
|
||||
config["vector_store"]["config"] = {
|
||||
"host": os.environ.get("POSTGRES_HOST", "localhost"),
|
||||
"port": int(os.environ.get("POSTGRES_PORT", "5432")),
|
||||
"dbname": os.environ.get("POSTGRES_DB", "postgres"),
|
||||
"user": os.environ.get("POSTGRES_USER", "postgres"),
|
||||
"password": os.environ.get("POSTGRES_PASSWORD", "postgres"),
|
||||
"collection_name": os.environ.get("POSTGRES_COLLECTION", "mem0_memories"),
|
||||
}
|
||||
elif vector_provider == "chroma":
|
||||
config["vector_store"]["config"] = {
|
||||
"host": os.environ.get("CHROMA_HOST", "localhost"),
|
||||
"port": int(os.environ.get("CHROMA_PORT", "8000")),
|
||||
"collection_name": os.environ.get("CHROMA_COLLECTION", "mem0_memories"),
|
||||
}
|
||||
|
||||
# LLM configuration
|
||||
llm_provider = os.environ.get("MEM0_LLM_PROVIDER", "openai")
|
||||
config["llm"] = {
|
||||
"provider": llm_provider,
|
||||
"config": {
|
||||
"model": os.environ.get("MEM0_LLM_MODEL", "gpt-4o-mini"),
|
||||
}
|
||||
}
|
||||
|
||||
# Temperature: only include if set (null means use provider default)
|
||||
temperature = os.environ.get("MEM0_LLM_TEMPERATURE")
|
||||
if temperature is not None:
|
||||
config["llm"]["config"]["temperature"] = float(temperature)
|
||||
|
||||
# Extra config: merge JSON env var if provided
|
||||
extra_config_json = os.environ.get("MEM0_LLM_EXTRA_CONFIG")
|
||||
if extra_config_json:
|
||||
import json
|
||||
try:
|
||||
extra_config = json.loads(extra_config_json)
|
||||
config["llm"]["config"].update(extra_config)
|
||||
except json.JSONDecodeError:
|
||||
logging.warning(f"Failed to parse MEM0_LLM_EXTRA_CONFIG: {extra_config_json}")
|
||||
|
||||
# Add API key if available
|
||||
if llm_provider == "openai":
|
||||
api_key = os.environ.get("OPENAI_API_KEY")
|
||||
if api_key:
|
||||
config["llm"]["config"]["api_key"] = api_key
|
||||
|
||||
# Embedder configuration
|
||||
embedder_provider = os.environ.get("MEM0_EMBEDDER_PROVIDER", "openai")
|
||||
config["embedder"] = {
|
||||
"provider": embedder_provider,
|
||||
}
|
||||
|
||||
# Embedder model: only include if provider is set
|
||||
if embedder_provider:
|
||||
embedder_config = {}
|
||||
embedder_model = os.environ.get("MEM0_EMBEDDER_MODEL")
|
||||
if embedder_model:
|
||||
embedder_config["model"] = embedder_model
|
||||
config["embedder"]["config"] = embedder_config
|
||||
|
||||
if embedder_provider == "openai":
|
||||
api_key = os.environ.get("OPENAI_API_KEY")
|
||||
if api_key:
|
||||
config["embedder"]["config"]["api_key"] = api_key
|
||||
|
||||
# History DB path
|
||||
history_db_path = os.environ.get("MEM0_HISTORY_DB_PATH", "/var/lib/mem0/history.db")
|
||||
config["history_db_path"] = history_db_path
|
||||
|
||||
return config
|
||||
|
||||
|
||||
# Initialize Memory instance
|
||||
try:
|
||||
config = get_config_from_env()
|
||||
logging.info(f"Initializing mem0 with config: {config}")
|
||||
|
||||
# Validate API key is set for OpenAI provider
|
||||
if config.get("llm", {}).get("provider") == "openai":
|
||||
if not config.get("llm", {}).get("config", {}).get("api_key"):
|
||||
logging.error("OPENAI_API_KEY environment variable is required but not set.")
|
||||
logging.error("Please set OPENAI_API_KEY environment variable or configure apiKeyFile in NixOS module.")
|
||||
sys.exit(1)
|
||||
|
||||
MEMORY_INSTANCE = Memory.from_config(config)
|
||||
logging.info("Memory instance initialized successfully")
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to initialize Memory: {e}")
|
||||
logging.error("Please check your configuration and ensure all required services are running.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="Mem0 REST API",
|
||||
description="A REST API for managing and searching memories for your AI Agents and Apps.",
|
||||
version="1.0.0",
|
||||
)
|
||||
|
||||
|
||||
class Message(BaseModel):
|
||||
role: str = Field(..., description="Role of the message (user or assistant).")
|
||||
content: str = Field(..., description="Message content.")
|
||||
|
||||
|
||||
class MemoryCreate(BaseModel):
|
||||
messages: List[Message] = Field(..., description="List of messages to store.")
|
||||
user_id: Optional[str] = None
|
||||
agent_id: Optional[str] = None
|
||||
run_id: Optional[str] = None
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class SearchRequest(BaseModel):
|
||||
query: str = Field(..., description="Search query.")
|
||||
user_id: Optional[str] = None
|
||||
run_id: Optional[str] = None
|
||||
agent_id: Optional[str] = None
|
||||
filters: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
@app.get("/", summary="Redirect to documentation", include_in_schema=False)
|
||||
def home():
|
||||
"""Redirect to the OpenAPI documentation."""
|
||||
return RedirectResponse(url="/docs")
|
||||
|
||||
|
||||
@app.get("/health", summary="Health check")
|
||||
def health():
|
||||
"""Check if the server is running."""
|
||||
return {"status": "healthy", "service": "mem0-api"}
|
||||
|
||||
|
||||
@app.post("/configure", summary="Configure Mem0")
|
||||
def set_config(config: Dict[str, Any]):
|
||||
"""Set memory configuration."""
|
||||
global MEMORY_INSTANCE
|
||||
MEMORY_INSTANCE = Memory.from_config(config)
|
||||
return {"message": "Configuration set successfully"}
|
||||
|
||||
|
||||
@app.post("/memories", summary="Create memories")
|
||||
def add_memory(memory_create: MemoryCreate):
|
||||
"""Store new memories."""
|
||||
if not any([memory_create.user_id, memory_create.agent_id, memory_create.run_id]):
|
||||
raise HTTPException(status_code=400, detail="At least one identifier (user_id, agent_id, run_id) is required.")
|
||||
|
||||
params = {k: v for k, v in memory_create.model_dump().items() if v is not None and k != "messages"}
|
||||
try:
|
||||
response = MEMORY_INSTANCE.add(messages=[m.model_dump() for m in memory_create.messages], **params)
|
||||
return JSONResponse(content=response)
|
||||
except Exception as e:
|
||||
logging.exception("Error in add_memory:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/memories", summary="Get memories")
|
||||
def get_all_memories(
|
||||
user_id: Optional[str] = None,
|
||||
run_id: Optional[str] = None,
|
||||
agent_id: Optional[str] = None,
|
||||
):
|
||||
"""Retrieve stored memories."""
|
||||
if not any([user_id, run_id, agent_id]):
|
||||
raise HTTPException(status_code=400, detail="At least one identifier is required.")
|
||||
try:
|
||||
params = {
|
||||
k: v for k, v in {"user_id": user_id, "run_id": run_id, "agent_id": agent_id}.items() if v is not None
|
||||
}
|
||||
return MEMORY_INSTANCE.get_all(**params)
|
||||
except Exception as e:
|
||||
logging.exception("Error in get_all_memories:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/memories/{memory_id}", summary="Get a memory")
|
||||
def get_memory(memory_id: str):
|
||||
"""Retrieve a specific memory by ID."""
|
||||
try:
|
||||
return MEMORY_INSTANCE.get(memory_id)
|
||||
except Exception as e:
|
||||
logging.exception("Error in get_memory:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/search", summary="Search memories")
|
||||
def search_memories(search_req: SearchRequest):
|
||||
"""Search for memories based on a query."""
|
||||
try:
|
||||
params = {k: v for k, v in search_req.model_dump().items() if v is not None and k != "query"}
|
||||
return MEMORY_INSTANCE.search(query=search_req.query, **params)
|
||||
except Exception as e:
|
||||
logging.exception("Error in search_memories:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.put("/memories/{memory_id}", summary="Update a memory")
|
||||
def update_memory(memory_id: str, updated_memory: Dict[str, Any]):
|
||||
"""Update an existing memory with new content."""
|
||||
try:
|
||||
return MEMORY_INSTANCE.update(memory_id=memory_id, data=updated_memory)
|
||||
except Exception as e:
|
||||
logging.exception("Error in update_memory:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/memories/{memory_id}/history", summary="Get memory history")
|
||||
def memory_history(memory_id: str):
|
||||
"""Retrieve memory history."""
|
||||
try:
|
||||
return MEMORY_INSTANCE.history(memory_id=memory_id)
|
||||
except Exception as e:
|
||||
logging.exception("Error in memory_history:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.delete("/memories/{memory_id}", summary="Delete a memory")
|
||||
def delete_memory(memory_id: str):
|
||||
"""Delete a specific memory by ID."""
|
||||
try:
|
||||
MEMORY_INSTANCE.delete(memory_id=memory_id)
|
||||
return {"message": "Memory deleted successfully"}
|
||||
except Exception as e:
|
||||
logging.exception("Error in delete_memory:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.delete("/memories", summary="Delete all memories")
|
||||
def delete_all_memories(
|
||||
user_id: Optional[str] = None,
|
||||
run_id: Optional[str] = None,
|
||||
agent_id: Optional[str] = None,
|
||||
):
|
||||
"""Delete all memories for a given identifier."""
|
||||
if not any([user_id, run_id, agent_id]):
|
||||
raise HTTPException(status_code=400, detail="At least one identifier is required.")
|
||||
try:
|
||||
params = {
|
||||
k: v for k, v in {"user_id": user_id, "run_id": run_id, "agent_id": agent_id}.items() if v is not None
|
||||
}
|
||||
MEMORY_INSTANCE.delete_all(**params)
|
||||
return {"message": "All relevant memories deleted"}
|
||||
except Exception as e:
|
||||
logging.exception("Error in delete_all_memories:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/reset", summary="Reset all memories")
|
||||
def reset_memory():
|
||||
"""Completely reset stored memories."""
|
||||
try:
|
||||
MEMORY_INSTANCE.reset()
|
||||
return {"message": "All memories reset"}
|
||||
except Exception as e:
|
||||
logging.exception("Error in reset_memory:")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
host = os.environ.get("MEM0_HOST", "127.0.0.1")
|
||||
port = int(os.environ.get("MEM0_PORT", "8000"))
|
||||
workers = int(os.environ.get("MEM0_WORKERS", "1"))
|
||||
log_level = os.environ.get("MEM0_LOG_LEVEL", "info")
|
||||
|
||||
uvicorn.run(app, host=host, port=port, workers=workers, log_level=log_level)
|
||||
Reference in New Issue
Block a user