# mem0server A lightweight FastAPI wrapper around [mem0](https://github.com/mem0ai/mem0) providing persistent memory over a REST API, with dual-collection storage, metadata passthrough, and local reranking. ## Architecture | Component | Provider | Address | |-----------|----------|---------| | LLM | Groq (`meta-llama/llama-4-scout-17b-16e-instruct`) | cloud | | Vector store | Chroma | `192.168.0.200:8001` | | Embedder | Ollama (`nomic-embed-text`) | `192.168.0.200:11434` | | Reranker | local REST server | `192.168.0.200:5200` | ## Collections The server maintains two independent Chroma collections with separate extraction prompts: | Collection | Chroma name | Endpoint prefix | Used by | Extraction style | |------------|-------------|-----------------|---------|-----------------| | Conversational | `openclaw_mem` | `/memories` | OpenClaw agent | User-centric facts (`"User prefers…"`) | | Knowledge | `knowledge_mem` | `/knowledge` | book-ingestor | Objective, encyclopedic facts | Prompts for both collections are defined in the `PROMPTS` dict at the top of `mem0server.py` and are easy to edit without touching routing code. ## Environment variables | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `GROQ_API_KEY` | ✅ yes | — | Groq API key | | `RERANKER_URL` | no | `http://192.168.0.200:5200/rerank` | Local reranker endpoint | Create a `.env` file (never commit it): ```env GROQ_API_KEY=your_key_here RERANKER_URL=http://192.168.0.200:5200/rerank ``` ## Docker (recommended) The recommended setup mounts `mem0server.py` as a volume so code changes are picked up by uvicorn's `--reload` without rebuilding the image. Only touch `docker compose build` when `requirements.txt` changes. ### `docker-compose.yml` ```yaml services: mem0server: build: . image: mem0server:latest container_name: mem0server ports: - "8420:8420" volumes: - ./mem0server.py:/app/mem0server.py:ro env_file: - .env restart: unless-stopped ``` ### `Dockerfile` ```dockerfile FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # mem0server.py is mounted at runtime via docker-compose volume EXPOSE 8420 CMD ["uvicorn", "mem0server:app", "--host", "0.0.0.0", "--port", "8420", "--reload"] ``` ### Workflow | Situation | Command | |-----------|---------| | First time / deps changed | `docker compose up --build` | | Code edit | Just save — uvicorn reloads automatically | | Restart container | `docker compose restart` | | View logs | `docker compose logs -f` | > ⚠️ Never bake `.env` into the image. Always pass secrets at runtime via `env_file` or `-e`. --- ## API ### `GET /health` Returns server status, active collection names, and a preview of each extraction prompt. ```json { "status": "ok", "reranker_url": "http://192.168.0.200:5200/rerank", "collections": { "conversational": "openclaw_mem", "knowledge": "knowledge_mem" }, "prompts": { "conversational": "You are a personal memory assistant…", "knowledge": "You are a knowledge extraction assistant…" } } ``` --- ### `/memories` — conversational collection (OpenClaw) #### `POST /memories` Add a memory. Accepts plain text or a messages array. ```json { "text": "I prefer Python over JavaScript.", "user_id": "alice" } ``` ```json { "messages": [{"role": "user", "content": "I've used Vim for 10 years."}], "user_id": "alice" } ``` #### `POST /memories/search` Search with reranking. Fetches `limit × 3` candidates, reranks locally, returns top `limit`. ```json { "query": "editor preferences", "user_id": "alice", "limit": 5 } ``` Results include a `rerank_score` field. #### `POST /memories/recent` Return the most recently created memories for a user. ```json { "user_id": "alice", "limit": 5 } ``` #### `DELETE /memories` Delete memories by filter. ```json { "filter": { "user_id": "alice" } } ``` --- ### `/knowledge` — knowledge collection (book-ingestor) Same shape as `/memories` with two additions: **`metadata`** — arbitrary key/value dict stored alongside the memory. Useful for provenance tagging: ```json { "text": "Silvio Gesell proposed demurrage to discourage currency hoarding.", "user_id": "knowledge_base", "metadata": { "source_file": "gesell_neo.pdf", "chapter": 3, "page": 47 } } ``` **`infer: false`** — bypasses LLM extraction entirely and stores the text verbatim. Use for pre-summarised chunks where you want exact content preserved: ```json { "text": "MIDI SysEx messages use a 7-bit checksum.", "user_id": "knowledge_base", "infer": false, "metadata": { "source_file": "midi_spec.pdf", "chapter": 9, "page": 112 } } ``` #### `POST /knowledge/search` #### `POST /knowledge/recent` #### `DELETE /knowledge` Same request/response shape as their `/memories` counterparts. --- ### `POST /search` — merged search (both collections) Queries both collections simultaneously, tags each result with `_source`, then runs a single rerank pass over the merged pool. Intended for OpenClaw's autorecall webhook when it should draw on both conversational memory and ingested knowledge. ```json { "query": "Gesell economic theory", "user_id": "knowledge_base", "limit": 8 } ``` Each result includes `"_source": "conversational"` or `"_source": "knowledge"`. --- ## Reranker contract The server expects a reranker at `RERANKER_URL` accepting: ```json { "query": "...", "documents": ["doc1", "doc2"], "top_k": 5 } ``` And returning: ```json { "results": [ { "text": "doc1", "score": 0.99 }, { "text": "doc2", "score": 0.87 } ] } ``` If the reranker is unreachable the server falls back gracefully to raw mem0 results — no crash, no error returned to the caller. --- ## Resetting a collection `reset_memory.py` deletes and recreates a collection directly via the Chroma HTTP API, then restarts the mem0server container so it re-acquires the fresh collection object: ```bash python reset_memory.py openclaw_mem # wipe conversational python reset_memory.py knowledge_mem # wipe knowledge ``` ```python # reset_memory.py import sys, subprocess, requests base = "http://192.168.0.200:8001/api/v1" name = sys.argv[1] requests.delete(f"{base}/collections/{name}") requests.post(f"{base}/collections", json={"name": name}) print(f"collection reset: {name}") subprocess.run(["docker", "compose", "restart", "mem0server"]) print("mem0server restarted") ``` --- ## Testing `tests.sh` exercises every endpoint. Run it after any server change: ```bash bash tests.sh ``` Key assertions to eyeball manually: - `/knowledge/search` query `"Gesell demurrage"` → Gesell result ranked #1, MIDI result near the bottom - `/knowledge/search` query `"free money currency hoarding"` → Gesell `rerank_score` should be `> 0.9`, MIDI `< 0.001` - `/search` (merged) results should include `_source` field on every item - `infer: false` response body should show the text stored verbatim, not an LLM-rewritten version