Neural long-term memory for AI agents. Store knowledge, recall it semantically, and — over time — encode it into per-agent LoRA adapters so the model knows what an agent has learned, not just retrieves text about it.
Inspired by Sakana AI's Doc-to-LoRA research on hypernetworks that convert documents into LoRA adapters in a single forward pass.
macOS (Apple Silicon) only — uses MLX for both inference and training.
store("Meta Q4 revenue was $43B")
│
▼
┌──────────────────────────────────────────┐
│ Memory Server (:2090) │
│ │
│ 1. Embed text (bge-small-en-v1.5) │
│ 2. Store in per-agent SQLite DB │
│ 3. On recall: cosine similarity → │
│ top-k memories → LLM synthesis │
│ 4. Optional: LoRA adapter per agent │
│ for weight-space memory │
└──────────────┬───────────────────────────┘
│ HTTP (localhost)
┌──────────────▼───────────────────────────┐
│ mlx-lm server (:2091, child proc) │
│ Qwen 3.5 35B A3B · per-request LoRA │
└──────────────────────────────────────────┘
Any agent that speaks HTTP can use it. The agent_id is an opaque string — each agent gets its own SQLite database, its own embeddings, and (optionally) its own LoRA adapter.
- macOS with Apple Silicon (M1+)
- uv (Python package manager)
git clone https://github.com/kortexa-ai/memory.server.git
cd memory.server
uv sync
uv run memory-serverThe server starts on http://localhost:2090 and launches an mlx-lm server on port 2091 as a subprocess. First run downloads the embedding model (~33MB) and MLX model (~19.5GB, cached in ~/.cache/huggingface/).
Swagger docs at http://localhost:2090/docs.
curl -X POST http://localhost:2090/v1/memory/store \
-H "Content-Type: application/json" \
-d '{
"agent_id": "my-agent",
"content": "The user prefers dark mode and Vim keybindings",
"source": "session-001",
"tags": ["preferences", "ui"]
}'curl -X POST http://localhost:2090/v1/memory/recall \
-H "Content-Type: application/json" \
-d '{
"agent_id": "my-agent",
"query": "What are the user preferences?"
}'Returns the top matching memories (by embedding similarity) plus a synthesized answer from the LLM.
curl -X POST http://localhost:2090/v1/memory/ingest \
-H "Content-Type: application/json" \
-d '{
"agent_id": "my-agent",
"transcript": "User: I just switched to Neovim. Agent: Nice! ...",
"session_id": "session-002",
"idempotency_key": "session-002",
"extract_knowledge": true
}'The LLM extracts discrete facts from the transcript and stores each as a separate memory with embeddings.
An optional idempotency_key is a 1–128 character non-secret identifier. Repeating the same key
and request returns the same logical result without duplicate insertion. Reusing it with changed
input returns HTTP 409. The server stores only key and request digests plus pending/complete state.
curl http://localhost:2090/v1/memory/status/my-agentOnce an agent has accumulated enough memories (default: 100+), you can train a personalized LoRA adapter:
curl -X POST http://localhost:2090/v1/memory/train/my-agentTraining runs as a background subprocess using mlx-lm. After training completes, the adapter is picked up on the next request — no restart needed.
All settings via environment variables (prefix MEMORY_) or .env file:
| Variable | Default | Description |
|---|---|---|
MEMORY_HOST |
0.0.0.0 |
Bind address |
MEMORY_PORT |
2090 |
Server port |
MEMORY_MODEL_REPO |
NexVeridian/Qwen3.5-35B-A3B-4bit |
MLX model for inference + training |
MEMORY_MODEL_SERVER_PORT |
2091 |
mlx-lm server port |
MEMORY_DATA_DIR |
data/agents |
Where agent databases and adapters live |
MEMORY_EMBEDDING_MODEL |
BAAI/bge-small-en-v1.5 |
Embedding model (via fastembed) |
MEMORY_TRAINING_MIN_MEMORIES |
100 |
Minimum memories before training is allowed |
The memory server is a FastAPI app that handles storage, retrieval, and training. It launches an mlx-lm server as a subprocess for LLM inference.
This gives us:
- ~74 tok/s inference on M4 Pro (3x faster than llama-server)
- Per-request LoRA adapter loading — no restart after training
- Same model for inference and training (no format conversion)
- Memory server controls the inference server's lifecycle
- Store: text → embedding (bge-small-en-v1.5, 384-dim) → SQLite with BLOB
- Recall: query → embedding → cosine similarity over all agent memories → top-k → LLM synthesis
- Ingest: transcript → LLM extracts facts → each fact stored + embedded
mlx-lm loads LoRA adapters per-request via the "adapters" field in chat completion requests. Adapters are stored as safetensors (the native MLX training output format) — no conversion step needed.
After training, the adapter at data/agents/{agent_id}/adapter/adapters.safetensors is loaded on the next recall request automatically.
- Read accumulated memories from SQLite
- Generate Q&A training pairs via the LLM
- Fine-tune QLoRA adapter using mlx-lm
- Output:
data/agents/{agent_id}/adapter/adapters.safetensors— loaded directly, no conversion
- Phase 1 (done): SQLite storage, LLM synthesis, REST API
- Phase 2 (done): Embedding-based semantic retrieval (fastembed + cosine similarity)
- Phase 3 (done): Per-agent LoRA training + per-request adapter loading via mlx-lm
- Phase 4 (planned): Doc2LoRA hypernetwork for sub-second adapter generation
MIT