Add posts: RAID silent corruption + self-hosted mem0 (en/zh, og+banner)
Deploy / build (push) Successful in 20s
Deploy / build (push) Successful in 20s
This commit is contained in:
1 parent
0dee1ea173
commit
55619aa04e
10 files changed
+657
-4
No files matched your search
@@ -0,0 +1,192 @@
|
||||
---
|
||||
title: "How I Self-Hosted a Mem0 Memory Stack (So My AI Agent Remembers)"
|
||||
description: "A private, self-hosted memory layer for AI agents — mem0 API + LiteLLM + PostgreSQL with pgvector on a Synology NAS. Semantic memory under your control, not a third party's."
|
||||
pubDate: 2026-09-16
|
||||
category: case-studies
|
||||
tags: [mem0, docker, postgres, pgvector, litellm, ai, sharing]
|
||||
ogImage: /og/self-hosting-mem0-memory-stack.png
|
||||
banner: /banners/self-hosting-mem0-memory-stack.png
|
||||
---
|
||||
|
||||
An AI agent that forgets everything between conversations is a tool you have
|
||||
to reintroduce yourself to every session. My AI assistant (the one that runs
|
||||
this very blog's workflow) used to start each chat from zero — until I gave it
|
||||
a **memory layer**: a self-hosted [mem0](https://mem0.ai) stack that stores
|
||||
facts it learns, retrieves them semantically, and survives across sessions.
|
||||
|
||||
This is the story of how I self-hosted it on my Synology NAS — private, fully
|
||||
under my control, no data leaving my LAN.
|
||||
|
||||
## Why this matters
|
||||
|
||||
A memory layer changes what an agent can actually *do* for a business:
|
||||
|
||||
- **It stops re-answering the same questions.** Remember a customer's granted
|
||||
trial, their product, their last support issue — and the next conversation
|
||||
picks up where the last one ended.
|
||||
- **It makes "personalization" real.** A bot that recalls your customer's
|
||||
preferences and history is a bot that feels like a person. That's not
|
||||
prompt-sugar; it's a stored, searchable record.
|
||||
- **It keeps control of your data.** Every "memory" I dislike about hosted
|
||||
memory services is solved by running the stack yourself — the facts live
|
||||
in my own PostgreSQL, on my own NAS, behind my own network.
|
||||
|
||||
For anyone running AI agents (support bots, research assistants, personal
|
||||
automation) the question isn't *should* the agent remember — it's whether the
|
||||
memory is a private asset or a third party's.
|
||||
|
||||
## What mem0 does
|
||||
|
||||
Mem0 is "memory in a box" for LLM apps. Instead of you hand-rolling an
|
||||
embeddings table and a similarity search, it gives you a small API that does
|
||||
the three things memory needs:
|
||||
|
||||
1. **Add a memory** — `POST /memories` with a fact or a conversation; it
|
||||
extracts, dedupes, and stores it (optionally classified by an LLM).
|
||||
2. **Search** — `GET /search` with a query returns the most relevant stored
|
||||
facts, ranked by semantic similarity.
|
||||
3. **User scoping** — memories belong to a `user_id`, so you can keep memory
|
||||
per customer, per project, or per conversation without cross-talk.
|
||||
|
||||
The "memory" is the extracted fact. The "retrieval" is vector similarity. You
|
||||
get the agent even if you never touch a vector database yourself.
|
||||
|
||||
## The stack
|
||||
|
||||
Three containers on my Synology DS1821+, as a Portainer compose stack:
|
||||
|
||||
- **mem0 API** — the memory service itself (custom local build), listening
|
||||
on port `20015`.
|
||||
- **LiteLLM** — the LLM gateway in front of whatever model does the fact
|
||||
extraction. I can swap the underlying model without touching mem0's config.
|
||||
- **PostgreSQL 17 + pgvector** — the database. `pgvector/pgvector` bundles
|
||||
Postgres with the vector extension as a first-class citizen, so semantic
|
||||
search lives in the same DB as everything else.
|
||||
|
||||
```text
|
||||
┌──────────────┐ /search & /memories ┌──────────────────┐
|
||||
│ AI agent / │ ──── X-Api-Key + JSON ───────▶ │ mem0 API :20015 │
|
||||
│ Hermes / bot │ └────────┬─────────┘
|
||||
└──────────────┘ │ pgvector
|
||||
▼
|
||||
PostgreSQL 17 + pgvector
|
||||
┌──────────────┐ model calls (extract) ┌──────────────────┐
|
||||
│ OpenAI etc. │ ◀───────── LiteLLM ──────────│ fact-classifier │
|
||||
└──────────────┘ └──────────────────┘
|
||||
```
|
||||
|
||||
A representative compose looks like this:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
mem0-postgres:
|
||||
image: pgvector/pgvector:pg17
|
||||
environment:
|
||||
POSTGRES_USER: mem0
|
||||
POSTGRES_PASSWORD: ${MEM0_DB_PASSWORD}
|
||||
POSTGRES_DB: mem0
|
||||
volumes:
|
||||
- ./data/pg:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
|
||||
mem0-litellm:
|
||||
image: ghcr.io/berriai/litellm:main-stable
|
||||
command: ["--config", "/app/config.yaml"]
|
||||
volumes:
|
||||
- ./litellm-config.yaml:/app/config.yaml
|
||||
restart: unless-stopped
|
||||
|
||||
mem0-api:
|
||||
build: ./mem0-api
|
||||
environment:
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
||||
OPENAI_API_BASE: http://mem0-litellm:4000/v1 # go through the gateway
|
||||
MEM0_API_KEY: ${MEM0_API_KEY} # X-Api-Key for /search & /memories
|
||||
POSTGRES_URL: postgresql://mem0:${MEM0_DB_PASSWORD}@mem0-postgres/mem0
|
||||
ports:
|
||||
- "20015:8000"
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
(Mine is built from the mem0 repo with the model/fact-extraction directed
|
||||
through LiteLLM; the exact image tag is me maintaining a local build, and I
|
||||
keep the real API key out of the compose file — it's an env secret.)
|
||||
|
||||
## Using it
|
||||
|
||||
Once it's up, the API is refreshingly small. Add a fact (fast, verbatim):
|
||||
|
||||
```bash
|
||||
curl -X POST http://192.168.1.1:20015/memories \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Api-Key: $MEM0_API_KEY" \
|
||||
-d '{
|
||||
"user_id": "customer-2211",
|
||||
"text": "Customer 2211 prefers email over WhatsApp for order updates."
|
||||
}'
|
||||
```
|
||||
|
||||
Retrieve the most relevant memories later:
|
||||
|
||||
```bash
|
||||
curl -X POST http://192.168.1.1:20015/memories/search \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Api-Key: $MEM0_API_KEY" \
|
||||
-d '{
|
||||
"user_id": "customer-2211",
|
||||
"query": "how should I reach this customer?",
|
||||
"limit": 5
|
||||
}'
|
||||
```
|
||||
|
||||
Then inject the returned facts into the agent's system prompt (or context)
|
||||
before it answers. That's the whole loop: **store facts, retrieve what's
|
||||
relevant, use it in the next reply.**
|
||||
|
||||
### The `infer` gotcha that cost me a day
|
||||
|
||||
- `POST /memories` with the **default `infer=true`** runs the LLM fact
|
||||
extractor — it classifies and cleans each memory. That's smart but **slow**,
|
||||
and every call costs a model round-trip.
|
||||
- If I just want the agent to note something verbatim and fast (user said
|
||||
"please call me Andy"), I pass **`infer=false`** — no LLM hop, near-instant
|
||||
storage.
|
||||
|
||||
The route of my stack is `/search` and `/memories` **without a `/v1` prefix**
|
||||
(an early wrapper that assumed `/v1` 404'd for a while), and auth is a plain
|
||||
`X-Api-Key` header. Small details, but they cost me time to discover.
|
||||
|
||||
## What I'd do differently
|
||||
|
||||
- **Set `infer=false` for routine logging from day one.** Only run the LLM
|
||||
extractor on the facts that matter; the default inference mode makes every
|
||||
write pricey for no benefit on throwaway notes.
|
||||
- **Treat the API key as a real secret.** It gets embedded in agent configs
|
||||
and compose envs, so I keep it out of the compose file and inject it via
|
||||
environment secrets.
|
||||
- **Put the DB on a disk I trust.** Mem0 is only as durable as its Postgres.
|
||||
I run it on my NAS's protected volumes, with a backup job — because a
|
||||
memory layer that forgets is worse than none.
|
||||
|
||||
## The result
|
||||
|
||||
A private, self-hosted memory layer answered by two small HTTP calls. My
|
||||
agent now remembers customers, preferences, and decisions across sessions —
|
||||
and because the stack sits in my own PostgreSQL behind my own network, none
|
||||
of that leaves my control.
|
||||
|
||||
If you run AI agents that keep forgetting their context, self-hosting mem0
|
||||
is one of the highest-value, lowest-friction upgrades you can make — and you
|
||||
can do it without sending your memory to someone else's database.
|
||||
|
||||
---
|
||||
|
||||
## Want a memory-enabled AI setup for your business?
|
||||
|
||||
I build and self-host AI agents, memory stacks, websites, and infrastructure
|
||||
for businesses. If you'd like your assistant to actually *remember* your
|
||||
customers — or you want to talk about hiring me — I'd love to help:
|
||||
|
||||
- 📱 **WhatsApp:** [+60 12-797 2969](https://wa.me/60127972969)
|
||||
- 📧 **Email:** [[email protected]](mailto:[email protected])
|
||||
- 🌐 **Website:** [hoelee.com](https://hoelee.com)
|
||||
Reference in new issue
Block a user