A self-hosted, privacy-first personal AI agent with persistent memory and agentic tool use. Not a chatbot — an agent.
Memtrix runs entirely on your hardware. It communicates through the Matrix protocol, remembers every conversation through semantic search, executes tools autonomously, and evolves its personality based on your interactions.
Memtrix is a personal AI agent that lives in a Docker container on your machine. You chat with it through any Matrix client (Element, FluffyChat, etc.) and it can:
Developers and power users who want a personal AI assistant they control completely — no cloud dependency, no data leaving your machine, no subscriptions.
| Requirement | Purpose |
|---|---|
| Docker & Docker Compose | Runs all services |
| Ollama or an OpenRouter API key | LLM inference |
| Element Desktop (or any Matrix client) | Chat interface |
data/models/.# Clone and enter the project
git clone https://github.com/nnxmms/Memtrix.git && cd memtrix
# Create directories, build image, start Conduit
./setup.sh
# Interactive wizard — configure LLM, model, channel
./onboard.sh
# Launch everything
docker compose up -d
Then open Element → connect to http://localhost:6167 → log in → invite @memtrix:memtrix.local to a room.
Everything Memtrix brings to the table — from persistent memory to multi-agent orchestration.
Every component runs on your hardware. The LLM (via Ollama or OpenRouter), the Matrix homeserver (Conduit), the search engine (SearXNG), the vector database (ChromaDB) — nothing phones home.
Memtrix has a two-tier memory system:
MEMORY.md) — curated long-term knowledge that Memtrix actively maintainsmemory/yyyy-mm-dd.md) — chronological logs searchable via RAG embeddingsSemantic search is powered by nomic-embed-text-v1.5 running entirely on-device. No external API calls.
22 built-in tools auto-discovered at startup. The orchestrator runs an iterative reasoning loop (up to 10 iterations) where the LLM can call tools, observe results, and continue reasoning. New tools are just Python files dropped into src/tools/.
Run local models via Ollama (Llama, Mistral, Gemma, etc.) or tap 200+ cloud models through OpenRouter (OpenAI, Anthropic, Google). Configure multiple providers and switch models per-agent.
Memtrix's identity is defined by markdown files (SOUL.md, BEHAVIOR.md, USER.md, MEMORY.md) that it reads, understands, and updates itself over time. Its personality actually grows with every conversation.
Create specialist sub-agents with their own identity, memory, Matrix presence, and workspace. Agents communicate via ask_agent — the main agent can consult sub-agents, and sub-agents can consult each other or the main agent.
Communicates through the Matrix protocol via a local Conduit homeserver. Use Element, FluffyChat, or any Matrix client. Each room maintains its own conversation session.
Defense-in-depth: non-root container, read-only filesystem, all capabilities dropped, no shell access for the LLM, SSRF protection, human-in-the-loop for destructive operations, path traversal prevention, and prompt injection mitigation.
Built-in SearXNG instance for privacy-respecting web searches. Fetch, read, and summarize any URL — all self-hosted, no tracking.
Memtrix can react to your messages with emoji in Matrix — just like a human would. The LLM decides when and what to react with naturally.
How the pieces fit together — every component runs locally in Docker.
┌──────────────────┐
│ Element Desktop │
│ (Matrix Client) │
└────────┬─────────┘
│
┌──────────────────────────────────┼──────────────────────┐
│ Docker Compose │ │
│ │ │
│ ┌───────────┐ ┌──────────────┴──┐ ┌───────────┐ │
│ │ Memtrix │◄──►│ Conduit │ │ SearXNG │ │
│ │ (Agent) │ │ (Matrix Server) │ │ (Search) │ │
│ └─────┬─────┘ └─────────────────┘ └─────▲─────┘ │
│ │ │ │
│ ├───> Sub-Agents (background threads) │ │
│ │ Each with own Matrix user │ │
│ │ │ │
│ ├───────────────────────────────────────┘ │
│ │ │
│ ├──► ChromaDB (vector memory, per-agent) │
│ │ │
└────────┼────────────────────────────────────────────────┘
│
▼
Ollama (LLM) / OpenRouter (cloud LLMs)| Component | Role |
|---|---|
| Memtrix | Python agent — orchestrates LLM calls, tool execution, memory, sessions, sub-agents |
| Conduit | Lightweight Matrix homeserver (local-only, no federation) |
| SearXNG | Privacy-respecting metasearch engine for web access |
| ChromaDB | Embedded vector database for semantic memory search |
| Ollama | Local LLM inference (runs separately on host) |
| OpenRouter | Cloud LLM gateway — OpenAI, Anthropic, Google, and more |
| Layer | Technology |
|---|---|
| Language | Python 3.13 |
| LLM Backend | Ollama, OpenRouter |
| Embeddings | nomic-embed-text-v1.5 (local, sentence-transformers) |
| Vector Store | ChromaDB (embedded, persistent) |
| Communication | Matrix protocol (matrix-nio) |
| Homeserver | Conduit |
| Web Search | SearXNG |
| Container | Docker (security-hardened) |
| TUI | Rich (onboarding wizard) |
Install Memtrix, run onboarding, and chat with your AI agent — all in about 5 minutes. By the end you will have a running Matrix homeserver, configured LLM, and a working chat session.
docker --version and docker compose version. Linux users not in the docker group should prefix commands with sudo.git clone https://github.com/nnxmms/Memtrix.git && cd memtrix
./setup.sh
This creates directories (data/, workspace/, agents/), copies default config and persona files, generates secrets for SearXNG and Conduit, builds the Docker image, and starts the Conduit homeserver.
./onboard.sh
The interactive wizard walks you through:
llama3, claude-sonnet-4-20250514)docker compose up -d
This starts Memtrix, Conduit, and SearXNG. Check logs with:
docker compose logs -f memtrix
Open Element Desktop and:
http://localhost:6167@memtrix:memtrix.local (or your custom name)data/cache/.A detailed walkthrough of the interactive setup wizard that configures your Memtrix instance.
The onboarding wizard (./onboard.sh) runs the Python onboarding module inside a Docker container connected to the Conduit network. It uses Rich for a polished terminal UI.
Choose a name for your main agent. This name is used for:
@memtrix:memtrix.local)Default is "Memtrix" — but you can name it anything.
Providers are dynamically discovered from src/providers/. Built-in options:
For running models on your own hardware.
base_url — URL of your Ollama instance (e.g. http://host.docker.internal:11434)ollama pull llama3)For accessing 200+ cloud models.
api_key — your OpenRouter API key$PLACEHOLDER references in config and resolved from environment variables at runtime. They never appear in plain text in config files.You can configure multiple providers — the wizard will ask if you want to add more.
Select a provider, enter the model name (e.g. llama3, anthropic/claude-sonnet-4-20250514), and give it an instance name for reference.
llama3 (Ollama) or anthropic/claude-sonnet-4-20250514 (OpenRouter).Choose between Matrix (recommended) or CLI.
For Matrix, the wizard automatically:
Passwords are generated with Python's secrets module (cryptographically secure, 24 characters).
data/config.json — full configuration with all providers, models, channels.env — all secrets (API keys, access tokens, registration token)workspace/AGENT.md — system prompt updated with your agent's nameWhat to expect when you send your first message — and how Memtrix learns about you.
After setup, open Element and invite the bot to a room. Memtrix auto-joins room invites. Each room gets its own independent conversation session — multiple rooms mean multiple contexts.
When you send a message, Memtrix:
SOUL.md, BEHAVIOR.md, USER.md, and MEMORY.md into the AGENT.md templateUSER.md, daily journal, and MEMORY.md with anything new it learnedsearch_memory, read_core_file, and write_memory_file as part of its reasoning, without announcing it to you.Want to see what's happening under the hood?
/verbose on — shows tool call notifications in real-time/reasoning on — shows the LLM's thinking processBoth are off by default. Turn them on to watch Memtrix's thought process.
USER.mdBEHAVIOR.md if you correct its style/help to see available commandsCreate specialist agents with their own identity, memory, and Matrix presence. Agents can consult each other autonomously.
| Feature | Details |
|---|---|
| Matrix user | A separate bot account (e.g. @dennis:memtrix.local) |
| Isolated workspace | Own directory under agents/<name>/ with core files, memory, attachments |
| Own memory | Separate daily journals and ChromaDB vector index |
| Inherited behavior | Copies main agent's BEHAVIOR.md, symlinks USER.md (shared) |
| Custom persona | SOUL.md and AGENT.md tailored to the agent's expertise |
| Full tool access | All tools except agent management (create_agent, delete_agent) |
Ask Memtrix to create one. It needs a real human name:
You: Create me a cooking expert. Call him Dennis.
Memtrix: ⚠️ Create a new sub-agent?
Name: Dennis
Expertise: Cooking and recipe specialist
Allow? (yes/no)
You: yes
Memtrix: Dennis is ready! His Matrix user is @dennis:memtrix.local.
Invite him to a room to start chatting.
Invite the agent's Matrix user (e.g. @dennis:memtrix.local) to a room and chat like you would with Memtrix. Dennis has his own memory — he'll remember your conversations and preferences independently.
Agents can consult each other using the ask_agent tool:
You: I'm planning a dinner party. Ask Dennis for a menu.
Memtrix: (internally uses ask_agent to consult Dennis)
Dennis suggests a three-course menu: roasted tomato soup
to start, herb-crusted salmon as the main, and a lemon
tart for dessert.
list_agents — see all registered sub-agents and their statusdelete_agent — permanently remove a sub-agent (workspace, memory, sessions all cleaned up)ask_agent — query another agent by nameAs of v2.7, after an inter-agent call, a summary of the exchange is appended to the target agent's active user session. If Agent B asks Agent A something, A can later tell the user what B asked and what it answered.
How Memtrix defines its identity — and how it evolves over time.
Memtrix's identity is defined by markdown files in workspace/:
| File | Purpose |
|---|---|
AGENT.md | System prompt template — wires everything together via {{PLACEHOLDER}} markers |
BEHAVIOR.md | Communication style, tone, and habits |
SOUL.md | Core values and personality |
USER.md | Everything Memtrix knows about you |
MEMORY.md | Distilled long-term memory (the "brain") |
AGENT.md contains placeholders like {{BEHAVIOR}}, {{SOUL}}, {{USER}}, {{MEMORY}}. At system prompt construction time, the orchestrator reads each file and replaces the placeholder with its contents.
These files are live-editable by Memtrix itself. When you tell it to behave differently or share personal details, it updates the appropriate file using write_core_file — with the system prompt rebuilt immediately after.
Out of the box, Memtrix's personality (from SOUL.md) is:
Its behavior (from BEHAVIOR.md) is:
All of this evolves as you interact. You can also edit the files directly.
Two-tier memory architecture — a curated brain plus searchable daily journals powered by on-device embeddings.
MEMORY.md is a curated, compact summary of the most important long-term knowledge. Key facts, recurring themes, lasting context. Memtrix actively maintains and prunes this file — think of it as the brain.
Files in memory/yyyy-mm-dd.md are chronological, append-only logs of each day's conversations:
# 2026-03-18
## Conversations
- Brief summaries of what was discussed.
## Learned
- New facts about the user.
## Decisions
- Agreements or directions decided.
## Tasks
- Things requested, completed, or pending.
## Notes
- Anything else worth remembering.
Daily journals are embedded using nomic-embed-text-v1.5 via sentence-transformers and stored in ChromaDB. The model runs entirely on-device — no external API calls.
User: "Remember that cake recipe I told you about?"
→ search_memory("cake recipe")
→ Finds 2026-03-12.md (distance: 0.23)
→ read_memory_file()
→ Returns full context from that day
Memtrix's system prompt (AGENT.md) instructs it to silently manage memory after every response:
USER.md with any new personal informationMEMORY.md with lasting factsBEHAVIOR.md if the user corrects its communication styleThis all happens in the tool loop — the user never sees these operations unless /verbose is enabled.
Extend Memtrix by dropping Python files into src/tools/.
Create a new .py file in src/tools/ that subclasses BaseTool:
from src.tools.base import BaseTool
class MyTool(BaseTool):
def __init__(self, workspace_dir: str) -> None:
super().__init__(
name="my_tool",
description="Does something useful.",
parameters={
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The input."
}
},
"required": ["input"]
}
)
def execute(self, **kwargs) -> str:
return "result"
Restart Memtrix and the tool is automatically discovered and available to the LLM.
name — unique tool name (used by the LLM to call it)description — explains what the tool does (guides the LLM)parameters — JSON Schema defining the tool's inputexecute(**kwargs) — called when the LLM invokes the tool, returns a string resultThe orchestrator injects these kwargs automatically:
| Kwarg | Purpose |
|---|---|
_room_id | Current room/session ID |
_ask | Human-in-the-loop confirmation callback |
_react | Emoji reaction callback |
_agent_depth | Current inter-agent depth (0 = direct user call) |
Add support for any LLM API by dropping a provider file into src/providers/.
from src.providers.base import BaseProvider
class MyProvider(BaseProvider):
def __init__(self, api_key: str) -> None:
super().__init__(name="myprovider")
self._api_key = api_key
def completions(self, model, history, tools=None):
# Call your LLM API and return a message object
...
The onboarding wizard automatically discovers new providers and prompts for their constructor parameters. Secret fields (containing "key", "token", "secret" in the parameter name) are handled automatically.
All configuration lives in data/config.json. Secrets are stored in .env and injected as environment variables.
{
"main-agent": {
"name": "Memtrix",
"provider": "my-ollama",
"model": "my-model",
"channel": "matrix",
"sessions": {},
"verbose": false,
"reasoning": false
},
"agents": {},
"workspace-directory": "/home/memtrix/workspace",
"providers": {
"my-ollama": {
"type": "ollama",
"base_url": "http://host.docker.internal:11434"
},
"my-openrouter": {
"type": "openrouter",
"api_key": "$OPENROUTER_API_KEY"
}
},
"models": {
"my-model": {
"provider": "my-ollama",
"model": "llama3",
"think": true
}
},
"channels": {
"matrix": {
"type": "matrix",
"homeserver": "http://conduit:6167",
"user_id": "@memtrix:memtrix.local",
"access_token": "$MATRIX_ACCESS_TOKEN"
}
}
}
Values starting with $ are resolved from environment variables at startup. The env var name is MEMTRIX_SECRET_ + the placeholder name.
| Config value | Environment variable |
|---|---|
$MATRIX_ACCESS_TOKEN | MEMTRIX_SECRET_MATRIX_ACCESS_TOKEN |
$OPENROUTER_API_KEY | MEMTRIX_SECRET_OPENROUTER_API_KEY |
$REGISTRATION_TOKEN | MEMTRIX_SECRET_REGISTRATION_TOKEN |
Secrets are resolved once at boot and then cleared from the process environment — they can't leak via /proc or subprocess inspection.
| Key | Type | Description |
|---|---|---|
name | string | Agent's display name |
provider | string | Reference to a provider in providers |
model | string | Reference to a model in models |
channel | string | Reference to a channel in channels |
verbose | boolean | Show tool call notifications |
reasoning | boolean | Show LLM thinking process |
| Key | Type | Description |
|---|---|---|
provider | string | Which provider runs this model |
model | string | Model identifier (e.g. llama3, anthropic/claude-sonnet-4-20250514) |
think | boolean | Enable extended thinking / reasoning mode |
Slash commands available in any chat room. Prefix with /.
| Command | Arguments | Description |
|---|---|---|
/clear | — | Start a fresh session in the current room. Also clears inter-agent sessions. |
/verbose | on | off | Toggle real-time tool execution notifications. Persists to config. |
/reasoning | on | off | Toggle display of model reasoning/thinking. Persists to config. |
/help | — | List all available commands. |
/verbose and /reasoning are per-agent — using them in a sub-agent's room only affects that agent.All 22 built-in tools, auto-discovered at startup.
| Tool | Description |
|---|---|
get_current_time | Returns the current date and time |
| Tool | Description |
|---|---|
read_core_file | Reads a core persona file (BEHAVIOR, SOUL, USER, MEMORY) |
write_core_file | Updates a core persona file (enforces read-before-write) |
| Tool | Description |
|---|---|
read_memory_file | Reads today's daily memory journal (date derived automatically) |
write_memory_file | Updates today's daily memory journal |
search_memory | Semantic search across all daily memories via embeddings |
| Tool | Description |
|---|---|
web_search | Searches the web via local SearXNG instance |
fetch_url | Fetches and extracts readable text from a URL |
| Tool | Description |
|---|---|
read_file | Reads a file from the workspace (text and PDF supported) |
create_file | Creates or overwrites a text file (overwrite requires confirmation) |
delete_file | Permanently deletes a file from the workspace |
create_directory | Creates a directory in the workspace |
list_directory | Lists contents of a directory |
delete_directory | Permanently deletes a directory and its contents |
git_clone | Clones a public git repository into the workspace |
download_file | Downloads a file from a URL (requires confirmation) |
send_file | Sends a file to the user via Matrix |
| Tool | Description |
|---|---|
react_to_message | React to the user's message with an emoji in Matrix |
| Tool | Description | Access |
|---|---|---|
create_agent | Create a new specialist sub-agent | Main agent only |
list_agents | List all registered sub-agents | Main agent only |
delete_agent | Permanently delete a sub-agent | Main agent only |
ask_agent | Ask another agent a question | All agents |
Defense-in-depth — multiple independent layers that each limit what the system and the LLM can do.
memtrix (UID 1000), never rootread_only: true; only workspace/, data/, and /tmp are writablecap_drop: ALL with no-new-privileges: truecurl, wget, and other network utilities are not installed in the imageThe LLM has no shell access. There is no run_command tool — every action goes through a purpose-built tool with its own validation.
All outbound tools (fetch_url, download_file, git_clone) validate URLs against:
conduit, searxng, localhost, etc.)Sensitive operations require explicit approval:
During inter-agent calls, confirm_with_user() returns false (deny) when no human callback is available — no auto-approval of destructive operations.
os.path.realpath()memory/ directory off-limits to general file toolsos.path.basename() with auto-increment on collision.env (chmod 600) and are injected at container startupThree services in Docker Compose — all on a private internal network.
| Service | Image | Purpose |
|---|---|---|
memtrix | Built from Dockerfile | The agent. Non-root, read-only root, all caps dropped. |
conduit | matrixconduit/matrix-conduit:latest | Matrix homeserver. Port 6167 exposed. |
searxng | searxng/searxng:latest | Web search engine. Internal only. |
| Mount | Container path | Content |
|---|---|---|
./workspace | /home/memtrix/workspace | Core files, memory, attachments |
./agents | /home/memtrix/agents | Sub-agent workspaces |
./data | /home/memtrix/data | Config, sessions, vector index |
./data/cache | /home/memtrix/.cache | ChromaDB, HuggingFace models |
python:3.13-slimgit only (minimal attack surface)memtrix (uid/gid 1000)python -m src.mainPYTHONUNBUFFERED=1 for real-time log output# Start all services
docker compose up -d
# View logs
docker compose logs -f memtrix
# Restart Memtrix only
docker compose restart memtrix
# Stop everything
docker compose down
# Rebuild after code changes
docker compose build && docker compose up -d
Common issues and how to fix them.
Check if port 6167 is already in use:
lsof -i :6167
Verify the Conduit container is running:
docker compose ps conduit
docker compose logs -f memtrixIf Ollama runs on your host machine, the container needs to reach it. Use:
http://host.docker.internal:11434
On Linux, you may need to add --add-host=host.docker.internal:host-gateway to the container or use your machine's LAN IP.
Memtrix downloads nomic-embed-text-v1.5 on first launch. If it fails:
data/cache/ is writable (should be owned by uid 1000)docker compose restart memtrixAll mounted directories should be owned by uid 1000:
sudo chown -R 1000:1000 workspace/ agents/ data/
/clear — resets the current sessiondata/ as JSON files organized by dateworkspace/memory/data/cache/ — deleting it triggers a full re-index on next syncdocker compose down
rm -rf data/ workspace/ agents/ .env
./setup.sh
./onboard.sh
docker compose up -d