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:
An optional Web Control Panel at localhost:8800 lets you configure everything, manage secrets, and browse memory from your browser.
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 store (the chroma service) - nothing phones home.
Memtrix layers two complementary memory systems:
sessions/) - raw conversation transcripts, chunked and searchable via on-device RAG embeddingsUSER.md)Semantic search is powered by nomic-embed-text-v1.5 running entirely on-device. No external API calls. See Reasoning Memory.
A local web app at localhost:8800 for running Memtrix without editing JSON: a validated config editor, one-click connection tests, secret management, full memory administration, and safe restarts with live progress. See Web Control Panel.
40 built-in tools auto-discovered at startup. The orchestrator runs an iterative reasoning loop (up to 25 rounds, configurable) where the LLM can call tools, observe results, and continue reasoning. Independent read-only tool calls run in parallel (up to 8 at once), every call is validated against the tool's JSON schema before it runs, and provider calls retry transient failures with exponential backoff. New tools are just Python files dropped into src/tools/. See The Agentic Loop.
Memtrix can research its own documentation. The search_docs and ask_docs tools let the agent - and every sub-agent - answer "how does Memtrix work?" questions from the bundled docs, with citations. The docs are parsed into a vector-indexed documentation collection at startup and refreshed automatically. See Tools Reference.
Run local models via Ollama (Llama, Mistral, Gemma, etc.), tap 200+ cloud models through OpenRouter (OpenAI, Anthropic, Google), or point at any OpenAI-compatible endpoint - llama.cpp, vLLM, LM Studio, an OpenAI-shim, a self-hosted gateway, or OpenAI itself. Configure multiple providers and switch models per-agent. The control panel can discover the models a provider exposes so you pick from a list instead of typing names.
Memtrix's identity is defined by markdown files. It freely edits SOUL.md and BEHAVIOR.md as it learns your preferences, while USER.md is auto-curated by the reasoning-memory deriver. Its personality genuinely 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.
Memtrix can act as a sysadmin on your remote hosts. It generates its own SSH key, registers hosts, and opens a persistent interactive session it works inside across many commands (so cd and environment changes persist), then closes it. Key-only auth, trust-on-first-use host keys, confirmation for destructive commands, and in-memory-only sudo passwords. See SSH Remote Admin.
Memtrix builds its own skills - short, reusable task workflows it writes for itself so it handles recurring work better over time. After finishing a non-trivial task (5+ tool calls, error recovery, a user correction, or a non-obvious workflow), it captures the approach as generalized steps using the skill_manage tool - same model, no second reasoner. At the start of every turn it sees a catalog of all its skills (each as name: description) and decides for itself which, if any, fits the task, then loads that skill's full instructions on demand and follows them - progressive disclosure, no embeddings or vector search. Instructions only, no separate code execution; each agent keeps its own isolated skill store. See Skills.
Communicates through the Matrix protocol via a local Conduit homeserver (or any external homeserver). Use Element, FluffyChat, or any Matrix client. Each room maintains its own conversation session, and Memtrix shows a native typing indicator while it works on a reply.
Send a voice message and Memtrix transcribes it on-device with a local faster-whisper model, then answers as if you had typed it. No audio leaves your machine. Off by default - see Voice & Transcription.
Connect a mailbox and Memtrix can read it over IMAP and send over SMTP. It can stay passive (only checking when asked) or, with reactive mail on, watch the inbox in the background and act on genuinely new messages the moment they arrive - triaging and only interrupting you when something matters. A trusted-sender allowlist can restrict it to mail from specific addresses, and every message is screened for prompt injection. Off by default - see Email & Mail.
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 │ │ Web Browser │
│ (Matrix Client) │ │ localhost:8800 (Panel)│
└────────┬─────────┘ └────────────┬──────────┘
│ │
┌─────────────┼───────────────────────────────┼──────────────────┐
│ Docker Compose │ │
│ │ │ │
│ ┌──────────┴──────┐ ┌──────────────┐ ┌─┴────────────┐ │
│ │ Conduit │◄─►│ Memtrix │◄─►│ web (panel) │ │
│ │ (Matrix Server) │ │ (Agent) │ │ FastAPI+SPA │ │
│ └─────────────────┘ └──┬────────┬──┘ └──────────────┘ │
│ │ │ │
│ Sub-Agents ◄──────┤ ├──────► SearXNG (search) │
│ (background threads) │ │ │
│ │ └──────► chroma (vectors) │
│ │ shared store │
└───────────────────────────┼────────────────────────────────────┘
│
▼
Ollama (local LLM) / OpenRouter (cloud LLMs)| Component | Role |
|---|---|
| Memtrix | Python agent - orchestrates LLM calls, tool execution, memory, sessions, sub-agents |
| web | FastAPI backend + React control panel (localhost:8800) - edit config, manage secrets, browse memory, restart |
| chroma | Standalone ChromaDB service - shared vector store for all agents, reached via CHROMA_URL |
| Conduit | Lightweight Matrix homeserver (local-only, no federation) - optional when using an external homeserver |
| SearXNG | Privacy-respecting metasearch engine for web access |
| 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 (standalone chroma service, persistent) |
| Memory | Conversation-history RAG + reasoning-memory deriver (peer cards & conclusions) |
| Communication | Matrix protocol (matrix-nio) |
| Homeserver | Conduit (bundled) or any external Matrix homeserver |
| Web Panel | FastAPI + Uvicorn backend, React single-page app |
| Secrets | Env file, managed secrets.env, or Bitwarden Secrets Manager |
| Web Search | SearXNG |
| Container | Docker (security-hardened) |
| TUI | Rich (onboarding wizard) |
max_iterations, default 25)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 all services: memtrix (the agent), web (the control panel on localhost:8800), chroma (the shared vector store), conduit (the Matrix homeserver), and searxng (web search). Check logs with:
docker compose logs -f memtrix
Open Element Desktop and:
http://localhost:6167@memtrix:memtrix.local (or your custom name)data/models/.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 at runtime - never written in plain text in config.json. They can come from the .env file, the managed data/secrets.env file, or Bitwarden Secrets Manager. See Secrets & Bitwarden.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, you then pick the homeserver:
The wizard talks to the local Conduit homeserver and automatically:
@memtrix:memtrix.local)Passwords are generated with Python's secrets module (cryptographically secure, 24 characters).
Already have a Matrix account (e.g. on matrix.org or your own server)? Point Memtrix at any homeserver instead of Conduit:
https://matrix.orgconduit service. The bot's full Matrix ID is stored in config and its access token is saved as a $PLACEHOLDER secret.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, and USER.md into the AGENT.md templateUSER.md) and records conclusions, while the raw conversation transcript is saved and embedded for future recallmemory_context, memory_search, and search_memory during reasoning. The profile card USER.md is curated automatically by the deriver - the agent reads it but does not overwrite it directly. See Reasoning Memory.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 searchable conversation history 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: [confirm] 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.
You can also create a sub-agent from the Sub-Agents page of the control panel - no chat required. Enter the agent's name, its area of expertise, and the model, and Memtrix provisions everything: it registers a fresh Matrix account automatically on the bundled local homeserver, or - when you run against an external homeserver - asks for the user ID and access token of an account you pre-created. The panel detects which case applies and shows the right fields. The new agent comes online after the next restart. The page also lists your existing sub-agents and lets you delete them (workspace, memory, and sessions are cleaned up; the Matrix account stays on the homeserver). Both the panel and the chat tool share the same provisioning path, so either way you get a complete, bootable agent.
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 namespawn_worker - hand a self-contained task to an ephemeral background worker that runs without blocking the conversation; the result is delivered back automatically when it finishesAfter an inter-agent call, a summary of the exchange is queued for the target agent and surfaced in its user-facing conversation on its next turn. If Agent B asks Agent A something, A can later tell the user what B asked and what it answered - even if A had never spoken to the user before (a brand-new sub-agent) or the user resumes the conversation in a different room.
Separate from persistent sub-agents, the main agent can spawn ephemeral background workers with spawn_worker for one-off, self-contained tasks - without blocking the conversation. A worker is a lightweight orchestrator on a background daemon thread with an in-memory session, no Matrix identity and no memory. The agent gets a worker id back immediately and keeps talking to you while the worker runs.
Concurrency is bounded by workers.max_concurrent (default 4), and the feature can be disabled with workers.enabled: false in config.
Memtrix is not a single LLM call - it is an orchestrated reasoning loop. Each message runs an iterative cycle of "call the model, run any tools it asks for, feed the results back" until the model produces a final answer.
When a message arrives, the orchestrator runs this loop:
The AGENT.md template is filled in: {{DATE}} becomes today's ISO date, and {{BEHAVIOR}}, {{SOUL}}, {{USER}} are replaced with the live contents of those persona files. If any source file changed since last turn, the prompt is rebuilt mid-session so edits take effect immediately.
Relevant reasoning-memory conclusions are pulled and injected as a context block (within a relevance threshold), and a catalog of the agent's skills (name: description) is added so the model can decide which, if any, to load.
The provider is called with the full message history plus every available tool schema. Transient failures (network blips, rate limits) retry automatically with exponential backoff and jitter.
If the model requests tools, each call's arguments are validated against the tool's JSON schema first. Independent read-only calls run concurrently; stateful ones run sequentially. Results are appended to the history.
If the model returned tool calls, the loop repeats. If it returned a plain message, that is the final answer and it is sent back to you. The loop is capped at max_iterations rounds (default 25).
When the model fans out independent work - several reads, multiple searches - those calls run concurrently, up to 8 at a time, cutting latency. Tools that mutate state or depend on order are detected and always run sequentially to keep behaviour deterministic. Sequential tools include:
read_core_file, write_core_file)str_replace_editor, delete_file, git, send_file, directory ops)create_agent, delete_agent, ask_agent, spawn_worker)memory_conclude), skill edits (skill_manage), and all SSH operationsEach request may take at most max_iterations tool-call rounds (default 25, set under the agent config block). When only a few rounds remain, the model receives a transient "tool-round budget warning" so it can wrap up cleanly instead of being cut off mid-task. At the hard cap it is asked for a final answer; that forced nudge is not persisted into history.
Long-running rooms are bounded by max_history (default 60 messages). Once a session grows past the limit, the oldest turns are trimmed - but trimming is tool-pairing-safe (it never leaves an orphaned tool result) and the system prompt is always preserved. This prevents context-window overflow without losing the agent's identity or recent context.
The /stop command is checked at every iteration of the loop, so it interrupts instantly - even mid-reasoning or mid-tool-call. Session history is preserved; just send your next message to continue. See Commands.
{
"agent": {
"max_iterations": 25, // tool-call rounds per request
"max_history": 60 // messages kept before trimming
}
}
Both keys apply to the main agent and every sub-agent. Installs without an agent block use the defaults.
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 - agent-writable |
SOUL.md | Core values and personality - agent-writable |
USER.md | Everything Memtrix knows about you - auto-curated profile card (deriver-managed) |
AGENT.md contains placeholders like {{BEHAVIOR}}, {{SOUL}}, {{USER}}, plus {{DATE}} for today's ISO date. At system prompt construction time, the orchestrator reads each file and replaces the placeholder with its contents.
BEHAVIOR.md and SOUL.md are live-editable by Memtrix itself. When you tell it to behave differently or correct its style, it updates them with write_core_file - and the system prompt is rebuilt immediately after.
write_core_file rejects direct writes to it - this keeps the user model consistent and tamper-resistant. See Reasoning Memory.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 edit SOUL.md and BEHAVIOR.md directly; USER.md is best managed through the deriver or the Web Control Panel's memory admin.
Memtrix remembers across sessions through a layered memory system - auto-curated peer cards, distilled conclusions, and searchable conversation history powered by on-device embeddings.
Every conversation is saved as a raw session transcript under sessions/. The agent writes no journals by hand - transcripts are split into windowed chunks (~800 tokens each), embedded, and stored in the vector store so the agent can recall any past exchange. Recall works two ways and can combine them: by meaning (a natural-language query) and by date (a single date or a start_date+end_date range). A date can't be matched semantically, so date questions filter on each chunk's day metadata instead; the agent is given today's date and resolves "yesterday" or "last Wednesday" to an ISO date itself. Inter-agent and internal sessions are skipped, and each sub-agent only indexes its own conversations.
Conversation transcripts 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.
.chunk-hashes.json stored alongside the index survives restarts, so warm starts re-embed only new or changed chunks and prune deleted sessionsUser: "Remember that cake recipe I told you about?"
→ search_memory(query="cake recipe")
→ Finds a conversation from 2026-03-12 (distance: 0.23)
→ Returns the transcript excerpt where you discussed it
User: "What did we talk about on the 15th?"
→ search_memory(date="2026-06-15") // resolved from today's date
→ Returns that day's conversation in order - no query needed
Memtrix manages memory without you asking. After each exchange:
USER.md in the background (see Reasoning Memory)BEHAVIOR.md is updated if you correct the agent's communication styleThese operations are silent - you only see them if /verbose is enabled.
Memtrix indexes its own documentation so it can answer questions about how the system works. The documentation site is parsed into sections, embedded with the same on-device model, and stored in a separate documentation collection on the shared chroma service.
search_docs returns matching sections with citations (no LLM), and ask_docs synthesizes a grounded answer with sources - see Tools ReferenceA background reasoning layer that turns raw conversation into durable, structured knowledge - an auto-curated profile card, a searchable store of typed conclusions about you, plus profile cards for the people, projects, and places you mention and the dated events tied to them.
Reasoning memory models a single peer:
The user has a finite, always-injected profile card and a growing store of conclusions.
| Peer | File | Contents |
|---|---|---|
| user | USER.md | Compact, durable facts about you |
The profile card is always injected into the system prompt, capped at peer_card_max_chars (default 1,500). It is re-curated from accumulated conclusions every few reasoning passes - so it stays compact and current instead of growing forever. Cap enforcement is boundary-safe, so the card is trimmed at clean boundaries instead of being cut mid-bullet.
write_core_file rejects direct writes to USER.md. To edit it by hand, use the Web Control Panel or freeze the card (below).A conclusion is a single durable fact about a peer, vector-indexed in the shared ChromaDB store (the representations collection). Each conclusion has a kind:
| Kind | Meaning | Example |
|---|---|---|
observation | Directly stated or observed | "User lives in Berlin." |
deductive | Logically inferred from facts | "User works in CET timezone." |
inductive | Generalized from patterns | "User prefers concise replies." |
Conclusions also carry a provenance: derived (produced by the deriver) or manual (added by the agent via memory_conclude or by you in the panel). Each conclusion additionally carries a confidence - high, medium, or low - reflecting how certain it is: explicit statements and certain deductions are high, well-supported inferences medium, and tentative patterns low. Confidence ranks which memories surface first and how the cards are curated. Near-duplicate conclusions are merged automatically using embedding similarity, and a memory that is independently re-derived is promoted in confidence rather than merely re-counted, so repeatedly-observed facts rise to the top.
The deriver is a background worker thread. It never blocks your reply - it works after the conversation continues.
Each message is queued for the peer it informs (your messages → user peer, the agent's replies → agent peer). Messages accumulate until they exceed batch_tokens (default 1,000) or an idle timeout (~90 s) elapses.
The deriver asks the model (the main model, or reasoning_model if set) to extract typed conclusions. reasoning_level controls how many items are requested per kind - from minimal (2) up to max (12).
New conclusions are embedded and written to the store; near-duplicates of existing conclusions are skipped.
Every few passes the peer card is rebuilt from the freshest conclusions, capped at peer_card_max_chars.
When you talk to Memtrix a lot, the deriver produces many conclusions. A background consolidation pass runs once a day - like memory consolidation during sleep - and distills each peer's stored conclusions into a smaller, cleaner set.
manual) conclusions untouched - only derived ones are distilled or decayedThe schedule is persisted to disk, so it survives restarts and runs roughly every consolidation_interval_hours. After a pass the peer card is re-curated from the distilled set. Run /consolidate in any room to trigger a pass immediately. Consolidation respects the deriver pause toggle.
When you send a message, memory is brought back in two ways, controlled by recall_mode:
recall_mode | Behavior |
|---|---|
hybrid (default) | Top conclusions are auto-injected as context and the memory tools are available |
context | Only auto-inject the top conclusions; no memory tools |
tools | No auto-injection; the agent must call memory tools to recall |
off | Reasoning memory disabled entirely |
Peer cards are always injected regardless of mode. With auto-injection, the top inject_top_k (default 5) conclusions are considered, but only those within a relevance threshold of your message are actually injected - weakly-related memories are suppressed so off-topic recall never crowds out the live conversation. Each injected memory is labelled with its confidence, and the agent is reminded that recall may be stale and should be verified before acting on anything critical.
When enabled, the agent can actively work with reasoning memory through five tools:
| Tool | What it does |
|---|---|
memory_profile | Returns the compact profile card (durable facts about the user). Fast, no search. Pass a name to fetch a specific person/thing's card instead. |
memory_search | Searches reasoned conclusions and returns the most relevant excerpts - for "what do you know about…" recall. |
memory_context | Answers a natural-language question grounded in reasoned memory - for nuanced questions like "what tone does the user prefer?". |
memory_conclude | Permanently locks a single high-signal durable fact (a stated preference, correction, or key context) at high confidence. Stored as manual so consolidation never prunes or rewrites it. Used sparingly. |
memory_event | Logs, lists, or cancels a dated event (e.g. a birthday or appointment), optionally linked to a person. Upcoming events are surfaced proactively. |
Beyond modelling you, the deriver also learns about the people, projects, and places you mention and tracks dated events tied to them - automatically, in the background.
entity_promote_threshold, or a single medium-plus-confidence fact), promotes them to a compact profile card stored under people/<slug>.md. These cards are deriver-owned just like USER.md.event_lookahead_days are surfaced every turn under a 📅 Upcoming block, and once an event passes you get a one-time 🔔 Just passed nudge (within event_followup_days). Recurring annual events (birthdays) roll forward automatically.entity_memory to false to disable people/event learning entirely, or forget a single person (and their facts, card, and events) from the Web Control Panel's People tab.Browse, add, and remove people and events from the Web Control Panel's memory admin (People and Events tabs).
All options live under the memory key in config.json. Defaults are safe - installs without a memory section keep working.
{
"memory": {
"backend": "native", // native | off
"recall_mode": "hybrid", // hybrid | context | tools | off
"write_frequency": "async", // async | turn
"reasoning_level": "low", // minimal | low | medium | high | max
"reasoning_model": null, // model instance name, or null = main model
"batch_tokens": 1000, // flush threshold per peer
"peer_card_max_chars": 1500,
"inject_top_k": 5, // conclusions auto-injected per message
"consolidation": true, // daily memory distillation pass
"consolidation_interval_hours": 24,
"consolidation_min_items": 12, // skip below this many derived items
"entity_memory": true, // learn about people / projects / places + events
"entity_card_max_chars": 800,
"entity_promote_threshold": 2, // facts before an entity gets a card
"event_lookahead_days": 7, // surface events within this window
"event_followup_days": 2, // post-event nudge window
"event_retention_days": 30 // prune old non-recurring events
}
}
| Key | Default | Description |
|---|---|---|
backend | native | native enables reasoning memory; off disables it |
recall_mode | hybrid | How memory is recalled - see the recall table above |
write_frequency | async | async derives in the background; turn flushes after every turn |
reasoning_level | low | Depth of extraction (items per kind: 2 / 4 / 6 / 8 / 12) |
reasoning_model | null | Use a different model instance for derivation, or the main model |
batch_tokens | 1000 | Token threshold before a peer's queue is flushed and reasoned |
peer_card_max_chars | 1500 | Maximum size of the profile card (trimmed at safe boundaries, not mid-bullet) |
inject_top_k | 5 | Number of conclusions auto-injected per message |
consolidation | true | Run the daily distillation pass that merges and prunes conclusions |
consolidation_interval_hours | 24 | How often consolidation runs (persisted across restarts) |
consolidation_min_items | 12 | Skip distillation for a peer below this many derived conclusions |
entity_memory | true | Learn about people, projects, and places you mention and track dated events |
entity_card_max_chars | 800 | Maximum size of a per-person/entity profile card |
entity_promote_threshold | 2 | Facts about an entity before it earns its own card (a medium-plus fact promotes early) |
event_lookahead_days | 7 | Window for surfacing upcoming events in the 📅 Upcoming block |
event_followup_days | 2 | Window for the one-time 🔔 Just passed follow-up nudge |
event_retention_days | 30 | Prune past non-recurring events older than this |
reasoning_model - derivation runs frequently in the background, so cost and latency add up. Set reasoning_level higher only if you want richer extraction.freeze_user_card to true to stop the deriver re-curating the profile card (handy after a manual edit you want to keep). Conclusions still accumulate.Memtrix writes its own skills - short, reusable task workflows it captures so it handles recurring kinds of work better over time. Skills are a distinct layer from persona (character) and memory (facts): they capture how the agent works.
Each skill lives in the agent's own workspace as a Markdown file with YAML frontmatter:
---
name: server-security-audit
description: Steps to audit a remote host's security posture
---
## Instructions
1. Connect to the host and check for OS updates
2. Review open ports and running services
3. Inspect sudoers and SSH config
4. Summarize findings with severity levels
A skill folder can also hold reference files alongside SKILL.md. Each agent (main and every sub-agent) keeps its own isolated skill store.
Authoring happens inside the normal agent loop - the same model, no second reasoner. After finishing a non-trivial task, the agent evaluates whether it was skill-worthy and, if so, captures the approach as generalized steps. A task is considered skill-worthy when it:
If an existing skill proved suboptimal during the task, the agent improves it on the spot instead of creating a duplicate. This evaluation is a required last step of finishing a larger task - declining to save must be a deliberate judgment, not an omission.
Skills use the progressive-disclosure model rather than embedding-based matching:
name: description.skill_manage (view), then follows them.There is no vector index, no embedding step, and no distance threshold that could wrongly reject a relevant skill - just a plain catalog the model reasons over.
One tool drives the whole lifecycle, with these actions:
| Action | What it does |
|---|---|
list | List all skills (name + description) |
view | Load a skill's full instructions and reference files |
create | Author a new skill |
edit | Replace a skill's content |
patch | Make a targeted change to a skill |
delete | Remove a skill |
Skills are controlled by the optional skills block in config.json:
{
"skills": {
"enabled": true // load skill_manage + inject the catalog
}
}
When disabled, the skill_manage tool is not loaded and no catalog is injected. The default is true.
A local web app for running Memtrix without touching JSON - edit configuration, test connections, manage secrets, browse and curate memory, and apply changes with a safe restart. It runs as the web service and is reachable at localhost:8800.
docker compose up -d
The web service starts alongside the agent. It serves the React single-page app and a FastAPI backend.
open http://localhost:8800
If MEMTRIX_WEB_TOKEN is set, the panel asks for it once and stores it for the session. All API calls are sent with that token.
| Page | What you do there |
|---|---|
| Dashboard | Live status at a glance - agent online/offline (heartbeat), reasoning-memory count, deriver state, and a config summary |
| Main Agent | Name, model, channel, and the verbose / reasoning toggles |
| Providers & Models | Add, edit, or remove LLM providers and model instances; run live connectivity tests |
| Channels | Manage Matrix channels and test channel connections |
| Sub-Agents | Create specialist sub-agents (name, expertise, model) - the panel registers the Matrix account automatically on the local homeserver or collects credentials for an external one - and delete existing ones |
| Memory | Tune the reasoning-memory block - backend, recall mode, derivation depth, consolidation |
| Memory Admin | Browse, search, add, edit, and delete conclusions; edit and freeze peer cards; manage people and events; pause the deriver; export/import |
| Secrets | View which secrets are referenced and set or rotate their values |
| Voice | Enable local transcription and pick the model tier and language |
| Configure the mailbox (IMAP/SMTP), enable reactive mail, and set the trusted-sender allowlist | |
| Panel Settings | The panel's own auth token, port, and CORS dev origins |
Edit your whole config.json through a structured UI instead of hand-editing JSON:
memory block, agents, and more)$PLACEHOLDER references - actual values live in your secret store, never in config.jsonBefore committing a change, test that it actually works. The panel can verify:
| Test | Checks |
|---|---|
| Provider | That an LLM provider (Ollama / OpenRouter) is reachable and the credentials are valid |
| Channel | That the Matrix homeserver accepts the bot's user ID and access token |
| Bitwarden | That the Secrets Manager access token and organization are valid |
$PLACEHOLDER secrets the same way the running agent does - including from Bitwarden when that backend is active - so a passing test reflects what the agent will actually use.View which secrets are configured and set or rotate their values without editing files by hand:
$PLACEHOLDER referenced by your config and whether it currently resolvesdata/secrets.env file (or your active backend)See Secrets & Bitwarden for how secrets are resolved.
The panel is a full front-end for the reasoning-memory store:
manual provenance)USER.md directlySome changes (new provider, model swap, channel change) require a restart. The panel triggers a safe restart and streams live progress back to your browser so you can watch the agent come back up.
| Variable | Default | Purpose |
|---|---|---|
MEMTRIX_WEB_HOST | 0.0.0.0 | Bind address inside the container (compose maps it to localhost) |
MEMTRIX_WEB_PORT | 8800 | Port the panel listens on |
MEMTRIX_WEB_TOKEN | (unset) | Bearer token required for all panel API calls - set this to protect the panel |
CHROMA_URL | http://chroma:8000 | Shared vector store, so the panel and agent read/write the same memory |
localhost, not exposed to your networkMEMTRIX_WEB_TOKEN so only holders of the token can call the APIlocalhost (e.g. over a VPN or reverse proxy), you must set MEMTRIX_WEB_TOKEN and terminate TLS in front of it. The panel can change configuration and read memory - treat its token like a password.Send Memtrix a voice note in Matrix and it transcribes it locally, on-device, then treats the text as normal input. No audio ever leaves your machine.
voice config block or from the Web Control Panel's Voice page.m.audio event.attachments/ directory.The model is loaded lazily on first use, so enabling voice does not slow startup. Failures and timeouts degrade gracefully - a transcription error never breaks message handling.
Transcription uses a local faster-whisper model (provider local). Choose a model tier to trade speed for accuracy:
| Model | Notes |
|---|---|
tiny | Fastest, lowest accuracy - fine for short, clear notes |
base (default) | Good balance of speed and accuracy |
small | More accurate, slower |
medium | High accuracy, noticeably slower |
large | Best accuracy, heaviest - needs more RAM/CPU |
The model is downloaded to data/models/ on first use and reused across restarts.
{
"voice": {
"enabled": false,
"provider": "local", // currently the only provider
"model": "base", // tiny | base | small | medium | large
"language": null, // null = auto-detect, or an ISO code like "en"
"max_audio_bytes": 25000000, // 25 MB cap; larger files are skipped
"timeout_seconds": 180 // give up after this long
}
}
| Key | Default | Description |
|---|---|---|
enabled | false | Turn local voice transcription on or off |
provider | local | Transcription backend (only local today) |
model | base | faster-whisper model tier |
language | null | Force a language, or auto-detect when null |
max_audio_bytes | 25000000 | Maximum audio size accepted (bytes) |
timeout_seconds | 180 | Transcription timeout before giving up |
language explicitly (e.g. "en") - it is slightly faster and more accurate than auto-detection.Give Memtrix a mailbox and it can read your mail over IMAP and send over SMTP - on demand, or reactively the moment new mail arrives. Off by default, main-agent only, and screened for prompt injection.
email.enabled is true.| Tool | Description |
|---|---|
email_check | Fetch recent messages (unread only by default) with sender, subject, date, body, and a stable UID. Marks them read after retrieval unless mark_read is false |
email_mark_unread | Restore one or more messages to unread by UID |
email_send | Send a plain-text email (to, subject, body, optional cc/bcc) - always asks for confirmation first |
Configure the mailbox on the Email page of the Web Control Panel, or edit the email block in config.json by hand. The mailbox password is never stored in config - it is supplied as the EMAIL_PASSWORD secret (from .env, the managed data/secrets.env, or Bitwarden) and resolved at startup.
{
"email": {
"enabled": false,
"imap_host": "imap.example.com",
"imap_port": 993,
"imap_ssl": true,
"smtp_host": "smtp.example.com",
"smtp_port": 587,
"smtp_security": "starttls", // starttls | ssl | none
"username": "[email protected]",
"password": "$EMAIL_PASSWORD",
"from_address": "[email protected]",
"from_name": "Memtrix",
"mailbox": "INBOX",
"auto_mark_read": true,
"max_fetch": 10,
"max_body_chars": 4000,
"react_to_mail": false,
"poll_interval_seconds": 60,
"trusted_senders": []
}
}
| Key | Default | Description |
|---|---|---|
enabled | false | Load the email tools (main agent only) |
imap_host | (required) | IMAP server hostname for reading mail |
imap_port | 993 | IMAP port |
imap_ssl | true | Use implicit TLS for IMAP |
smtp_host | (required) | SMTP server hostname for sending mail |
smtp_port | 587 | SMTP port |
smtp_security | starttls | SMTP transport security - starttls, ssl, or none |
username | (required) | Mailbox login username |
password | $EMAIL_PASSWORD | Mailbox password as a $PLACEHOLDER secret - never a literal value |
from_address | (username) | Address outgoing mail is sent from; defaults to username |
from_name | (empty) | Display name on outgoing mail |
mailbox | INBOX | IMAP folder to read |
auto_mark_read | true | Mark messages read after email_check retrieves them |
max_fetch | 10 | Maximum messages returned per check |
max_body_chars | 4000 | Per-message body truncation cap |
react_to_mail | false | Watch the mailbox and act on new mail proactively (see below) |
poll_interval_seconds | 60 | How often the reactive poller checks (minimum 15s) |
trusted_senders | [] | Allowlist of sender addresses; empty allows all (see below) |
By default Memtrix only touches your mailbox when you ask it to. Turn on react_to_mail and it watches the inbox in the background, acting on mail the moment it arrives:
poll_interval_seconds (default 60s, clamped to a 15s minimum).email_check still sees it as new.For a tighter security boundary, set trusted_senders to a list of addresses. When the list is non-empty, Memtrix only ever sees mail from those senders - every other message is filtered out at the tool level and never reaches the agent, the reasoning memory, or the reactive poller.
Name <addr> form; only the address part is kept. Invalid entries are dropped so a malformed allowlist can never silently widen access.EMAIL_PASSWORD secret through the panel's Secrets page - never paste it into config.json.Memtrix can act as a sysadmin on your remote hosts. It opens a persistent interactive session and works inside it across many commands - so cd and environment changes persist, just like a human at a terminal - then closes it.
ssh.enabled is true.Ask Memtrix to generate its SSH key (ssh_gen_key). It creates its own ed25519 keypair; the private key is written 0600 and never disclosed.
Memtrix returns its public key (ssh_get_pub_key). Add it to the remote host's ~/.ssh/authorized_keys. Authentication is key-only - no passwords.
Tell Memtrix the host details (ssh_add_host): a short alias, hostname, username, port, and optionally a sudo password. The alias must match ^[A-Za-z0-9._-]{1,64}$.
Memtrix opens a session (ssh_connect) - confirming the host key fingerprint on first use - then runs commands (ssh_run) that share working directory and environment, copies files in or out with ssh_scp, and finally closes it (ssh_disconnect).
| Tool | Description |
|---|---|
ssh_gen_key | Generate Memtrix's own ed25519 key; returns the public key |
ssh_get_pub_key | Return the public key to install in authorized_keys |
ssh_add_host | Register a host (alias, hostname, username, port, optional sudo password) |
ssh_remove_host | Unregister a host alias |
ssh_get_remote_hosts | List registered hosts and their connection status |
ssh_connect | Open a persistent session (trust-on-first-use host-key confirmation) |
ssh_run | Run a command in the open session; state persists; optional sudo |
ssh_scp | Copy a file to or from the host over SFTP (upload from / download into the workspace) |
ssh_disconnect | Close the open session |
Pass sudo as a parameter to ssh_run - never embed it in the command string (if you do, the tool auto-corrects and warns). When sudo=true:
sudo -n. If passwordless sudo (NOPASSWD) is configured, the command runs immediately - no prompt.data/ssh/known_hosts.rm, dd, mkfs, shutdown/reboot, recursive chmod/chown, block-device writes and similar require explicit approval.conduit, chroma, searxng, memtrix) and to loopback/link-local addresses is refused; private LAN hosts are allowed.max_output_chars (default 20,000).{
"ssh": {
"enabled": true,
"connect_timeout": 15, // seconds to open a connection
"command_timeout": 120, // seconds per command
"max_output_chars": 20000 // output cap returned to the model
}
}
Set enabled to false to remove the SSH capability entirely - the tools are never loaded.
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/.
Three provider types ship out of the box. Most setups never need a custom one:
| Type | Parameters | Use for |
|---|---|---|
ollama | base_url | Local models served by Ollama |
openrouter | api_key | 200+ cloud models via the OpenRouter gateway |
openai_compatible | base_url, optional api_key | Any endpoint speaking the OpenAI chat-completions API - llama.cpp, vLLM, LM Studio, an OpenAI-shim, a self-hosted gateway, or OpenAI itself |
The openai_compatible API key is optional, so key-less local servers work without one. When supplied it is sent as a standard Authorization: Bearer header and can be stored as a $SECRET reference. In the control panel, the Models page can Discover the model identifiers any provider exposes (Ollama's installed models, OpenRouter's catalogue, or an OpenAI-compatible /models route) so you pick from a list rather than typing names. Discovery resolves secret references server-side, so keys are never exposed to the browser.
Vision-capable models can see images the user sends in chat. Set "vision": true on the model (or tick the Vision checkbox on the Models page) and incoming pictures - PNG, JPG, GIF, WebP - are delivered to the model as actual images instead of a file path, so it can describe, read, or reason over them. The image is expanded into each backend's native multimodal format at send time: Ollama's images field, or OpenAI-style image_url data URLs for OpenRouter and OpenAI-compatible endpoints. Received images stay in context across turns so you can ask follow-ups, bounded to the most recent few (up to 4 images, 10 MB each) to keep requests lean. Leave the flag off for text-only models and nothing changes.
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 separately - in .env, the managed data/secrets.env file, or Bitwarden - and injected at runtime. You can edit everything by hand, or use the Web Control Panel.
{
"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"
},
"my-openai-compatible": {
"type": "openai_compatible",
"base_url": "http://host.docker.internal:8000/v1",
"api_key": "$OPENAI_API_KEY"
}
},
"models": {
"my-model": {
"provider": "my-ollama",
"model": "llama3",
"think": true,
"vision": false,
"input_cost": 0.15,
"output_cost": 0.60
}
},
"channels": {
"matrix": {
"type": "matrix",
"homeserver": "http://conduit:6167",
"user_id": "@memtrix:memtrix.local",
"access_token": "$MATRIX_ACCESS_TOKEN"
}
},
"memory": {
"backend": "native",
"recall_mode": "hybrid",
"write_frequency": "async",
"reasoning_level": "low",
"reasoning_model": null,
"batch_tokens": 1000,
"peer_card_max_chars": 1500,
"inject_top_k": 5
},
"voice": {
"enabled": false,
"provider": "local",
"model": "base",
"language": null,
"max_audio_bytes": 25000000,
"timeout_seconds": 180
},
"ssh": {
"enabled": true,
"connect_timeout": 15,
"command_timeout": 120,
"max_output_chars": 20000
},
"skills": {
"enabled": true
},
"agent": {
"max_iterations": 25,
"max_history": 60
},
"secrets": {
"backend": "env"
}
}
memory block configures the reasoning-memory system - every key is explained on the Reasoning Memory page. The voice block controls local Matrix audio transcription. The secrets block selects where secrets are read from - see Secrets & Bitwarden.Values starting with $ are resolved at startup from one of three backends: the .env / managed data/secrets.env file, the process environment, or Bitwarden Secrets Manager. For file/env backends, the lookup 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. Full details, including the Bitwarden backend, are on the Secrets & Bitwarden page.
| 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 |
vision | boolean | Let the model see images sent in chat - see Custom Providers |
input_cost | number | Optional. USD per 1M prompt tokens. When set, /costs reports a token-based spend estimate for this model |
output_cost | number | Optional. USD per 1M completion tokens |
| Key | Type | Description |
|---|---|---|
type | string | matrix or cli |
homeserver | string | Homeserver URL - http://conduit:6167 for the bundled server, or any external URL such as https://matrix.org |
user_id | string | The bot's full Matrix ID (e.g. @memtrix:memtrix.local or @mybot:matrix.org) |
access_token | string | The bot's access token, stored as a $PLACEHOLDER secret |
The memory block tunes the reasoning-memory system (backend, recall mode, derivation depth, peer cards, and more). Every key is documented on the Reasoning Memory page.
The optional ssh block controls the remote-administration tools. Omit it to run on the defaults below.
| Key | Default | Description |
|---|---|---|
enabled | true | Load the SSH tools. Set to false to remove the capability entirely. |
connect_timeout | 15 | Seconds to wait when opening a connection. |
command_timeout | 120 | Seconds to wait for a single command to finish. |
max_output_chars | 20000 | Cap on command output returned to the model. |
The optional skills block controls the agent's self-authored skills. Omit it to run on the defaults below.
| Key | Default | Description |
|---|---|---|
enabled | true | Load the skill_manage tool and inject the skill catalog. Set to false to remove the capability entirely. |
The optional agent block tunes the core tool-calling loop. Each request runs the model, executes any tools, and repeats until a final answer is returned; this cap bounds how many rounds a single request may take. Omit it to run on the default below.
| Key | Default | Description |
|---|---|---|
max_iterations | 25 | Maximum tool-call rounds per request before the agent is forced to produce a final answer. |
max_history | 60 | Maximum messages kept in a session before the oldest turns are trimmed (the system prompt is always preserved), bounding context growth on long conversations. |
The optional email block connects a mailbox over IMAP/SMTP and is off by default. Full key-by-key details, reactive mail, and the trusted-sender allowlist are on the Email & Mail page.
The panel is configured through environment variables on the web service, not config.json:
| Variable | Default | Description |
|---|---|---|
MEMTRIX_WEB_HOST | 0.0.0.0 | Bind address inside the container |
MEMTRIX_WEB_PORT | 8800 | Listen port |
MEMTRIX_WEB_TOKEN | (unset) | Bearer token required for the panel API |
CHROMA_URL | http://chroma:8000 | Shared vector store URL |
See the Web Control Panel guide for full details.
API keys and access tokens never live in config.json. They are stored as $PLACEHOLDER references and resolved at boot from one of three backends - a managed file, the environment, or Bitwarden Secrets Manager.
Anywhere a secret is needed, the config holds a reference like $OPENROUTER_API_KEY instead of the real value:
"api_key": "$OPENROUTER_API_KEY"
At startup Memtrix resolves every $PLACEHOLDER, then scrubs the secrets from the process environment so they can't be read back via /proc or a subprocess.
For each placeholder, Memtrix looks in this order:
$MATRIX_ACCESS_TOKEN → the MATRIX_ACCESS_TOKEN secret)MEMTRIX_SECRET_<NAME> (loaded from data/secrets.env or the real environment)MEMTRIX_SECRET_. So $REGISTRATION_TOKEN is read from MEMTRIX_SECRET_REGISTRATION_TOKEN. The one exception is the Bitwarden access token itself, which uses BWS_ACCESS_TOKEN unprefixed.By default, secrets are kept in data/secrets.env - a simple KEY=value file holding the prefixed variables:
MEMTRIX_SECRET_MATRIX_ACCESS_TOKEN=syt_...
MEMTRIX_SECRET_OPENROUTER_API_KEY=sk-or-...
MEMTRIX_SECRET_REGISTRATION_TOKEN=...
You rarely edit this by hand - onboarding writes it, and the Web Control Panel lets you set or rotate values safely (write-only; values are never echoed back).
For a centralized, auditable secret store, point Memtrix at Bitwarden Secrets Manager. Add a secrets block to config.json:
{
"secrets": {
"backend": "bitwarden",
"organization_id": "<org-uuid>",
"project_id": "<project-uuid>",
"api_url": "https://api.bitwarden.eu",
"identity_url": "https://identity.bitwarden.eu"
}
}
| Key | Required | Description |
|---|---|---|
backend | yes | bitwarden to enable, env for the managed file |
organization_id | yes | Your Bitwarden organization UUID |
project_id | optional | Scope secrets to a specific project |
api_url | optional | API endpoint - defaults to the EU region (https://api.bitwarden.eu) |
identity_url | optional | Identity endpoint - defaults to https://identity.bitwarden.eu |
api_url to https://api.bitwarden.com and identity_url to https://identity.bitwarden.com.Authentication uses a Bitwarden machine-account access token, provided via the BWS_ACCESS_TOKEN environment variable (it is the one secret not prefixed with MEMTRIX_SECRET_). Memtrix logs in with it, fetches all secrets in the project, and resolves placeholders by name.
$) - e.g. a secret named OPENROUTER_API_KEY resolves $OPENROUTER_API_KEY. Use the panel's Test Bitwarden button to verify the token and organization before applying.data/secrets.env and .env out of version control (they are git-ignored)config.json - always use a $PLACEHOLDERSlash 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. |
/new | - | Alias for /clear - begins a new conversation in the current room. |
/stop | - | Stop the current run immediately. Interrupts LLM reasoning, tool execution, and any ongoing operations. Session history is preserved; send the next message to continue. |
/verbose | on | off | Toggle real-time tool execution notifications. Persists to config. |
/reasoning | on | off | Toggle display of model reasoning/thinking. Persists to config. |
/costs | - | Show cost usage - live OpenRouter credit usage (today, this week, this month, all-time) plus a token-based estimate for any non-OpenRouter model that defines per-token pricing. Available when an OpenRouter provider is configured or any model has a cost defined. |
/consolidate | - | Trigger a memory-consolidation pass now - distills reasoning conclusions into a smaller, cleaner set. Only available when reasoning memory is enabled. |
/help | - | List all available commands. |
/verbose and /reasoning are per-agent - using them in a sub-agent's room only affects that agent. /costs appears when at least one OpenRouter provider is set up, or when any model defines input_cost / output_cost.Run /costs in any room to see what you've spent. For OpenRouter providers it reports live credit usage (US dollars); if multiple providers share the same API key, they are grouped and counted once:
You: /costs
Memtrix: OpenRouter usage (main):
Today (UTC): $0.4213
This week: $2.8740
This month: $9.1502
All-time: $41.2280
Limit: $50.0000 ($8.7720 remaining)
OpenRouter reports real spend through its API. For every other provider - Ollama, OpenAI-compatible endpoints, self-hosted gateways - Memtrix can still estimate cost by counting tokens. Give a model a price and /costs adds a token-based estimate for it. Set input_cost and/or output_cost on the model (USD per 1 million tokens), either in config.json or via the Input cost / Output cost fields on the Models page:
You: /costs
Memtrix: Local usage (token-based estimate since first run):
gpt-4o-mini:
Prompt: 1,204,320 tokens
Completion: 186,540 tokens
Est. cost: $0.2926 (in $0.15/M, out $0.60/M)
Total estimate: $0.2926
data/usage.json) and are keyed by the raw model identifier, so two model entries pointing at the same underlying model share one total. Prices are per one million tokens. Models without a price simply don't appear in the estimate.All 40 built-in tools, auto-discovered at startup. Some are gated by config (reasoning memory, SSH, skills) or by channel (Matrix-only). Drop a new file in src/tools/ to add your own - see Custom Tools.
| Tool | Description |
|---|---|
get_current_time | Returns the current date and time |
| Tool | Description |
|---|---|
read_core_file | Reads a core persona file (BEHAVIOR, SOUL, USER) |
write_core_file | Updates a writable persona file (BEHAVIOR, SOUL). Enforces read-before-write; rejects writes to the deriver-owned USER.md profile card |
| Tool | Description |
|---|---|
search_memory | Recall past conversations by meaning (query) and/or by date (date, or start_date+end_date) |
Available when reasoning memory is enabled (recall_mode of hybrid or tools). See Reasoning Memory.
| Tool | Description |
|---|---|
memory_profile | Returns the compact profile card - durable facts about the user. Fast, no search |
memory_search | Searches reasoned conclusions and returns the most relevant excerpts |
memory_context | Answers a natural-language question grounded in reasoned memory |
memory_conclude | Stores a high-signal durable fact in reasoned memory |
memory_event | Logs, lists, or cancels a dated event (e.g. a birthday or appointment), optionally linked to a person |
Always on for the main agent and every sub-agent - lets Memtrix research its own docs. See Memory for how the index is built.
| Tool | Description |
|---|---|
search_docs | Searches the Memtrix documentation and returns matching sections with citations. No LLM call |
ask_docs | Synthesizes a direct, grounded answer about how Memtrix works from its own documentation, with sources |
| 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) |
str_replace_editor | Views and edits text files with targeted edits — view, create, str_replace, insert (overwrite/str_replace are confirmed/exact-once) |
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 | Runs any git command in the workspace (status, branch, commit, rebase, clone, pull, push, …) over HTTPS or SSH; push requires confirmation |
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 |
spawn_worker | Spawn an ephemeral background worker to complete a task without blocking; result delivered automatically when done | Main agent only |
Memtrix can administer remote hosts over SSH through a persistent interactive session: it opens a connection and works inside it across many commands (so cd and environment changes persist), then closes it. These tools are available to the main agent only and load only when ssh.enabled is true.
| Tool | Description |
|---|---|
ssh_gen_key | Generate Memtrix's own ed25519 SSH key; returns the public key (private key never disclosed) |
ssh_get_pub_key | Return the public key to install in a host's authorized_keys |
ssh_add_host | Register a remote host under a short alias |
ssh_remove_host | Unregister a remote host alias |
ssh_get_remote_hosts | List registered hosts and their connection status |
ssh_connect | Open a persistent session (trust-on-first-use host-key confirmation) |
ssh_run | Run a command in the open session; state persists between calls; optional sudo |
ssh_scp | Copy a single file to or from the host over SFTP; direction is upload or download (max 100 MB) |
ssh_disconnect | Close an open SSH session |
rm, dd, mkfs, shutdown, recursive chmod/chown, block-device writes) require confirmation; sudo passwords are kept in memory only and never written to disk. Full details on the SSH Remote Admin page.Available only when email.enabled is true (main agent only). Reads a mailbox over IMAP and sends mail over SMTP. Connection settings are configured in the control panel; the mailbox password is supplied as the EMAIL_PASSWORD secret (.env or Bitwarden).
| Tool | Description |
|---|---|
email_check | Fetch recent messages (unread only by default) with sender, subject, date, body and a stable UID; marks them read after retrieval unless mark_read is false |
email_mark_unread | Restore one or more messages to unread by UID |
email_send | Send a plain-text email (to, subject, body, optional cc/bcc); requires confirmation |
Turn on react_to_mail (off by default) to let Memtrix act on mail the moment it arrives instead of only when asked. A background poller checks the mailbox every poll_interval_seconds (default 60s, minimum 15s) and, when genuinely new mail appears, pings the agent with a system notification — the same in-process mechanism that delivers finished background-worker results — so it can read, triage, and only message you when something needs your attention. The poll never marks mail read, and an existing unread backlog (or mail that arrived while Memtrix was offline) is never announced, so enabling it won't trigger a flood. Configure it on the Email page of the control panel.
For a tighter security boundary, set trusted_senders (a list of addresses) on the Email page. When non-empty, Memtrix only ever sees mail from those senders: every other message is filtered out at the tool level and never reaches the agent, the reasoning memory, or the reactive poller. Matching is on the exact parsed From address (case-insensitive), never the display name, so a message that merely puts a trusted address in its display name cannot slip through. Leave the list empty to allow mail from all senders.
Available to every agent when skills.enabled is true. Lets the agent author and reuse its own task workflows. See Skills.
| Tool | Description |
|---|---|
skill_manage | Create, view, list, edit, patch, or delete the agent's own reusable skills (actions: create, view, list, edit, patch, delete) |
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 imagelocalhost:8800web control-panel service shares the same hardening (read-only root, non-root, dropped capabilities) and is gated by MEMTRIX_WEB_TOKEN. See Web Control Panel → Security.The LLM has no shell access on the host container. There is no local run_command tool - every action goes through a purpose-built tool with its own validation.
The optional SSH tools do let the agent run commands on remote hosts you have explicitly registered, but never on the Memtrix container itself. That capability is gated: it only loads when ssh.enabled is true, authenticates with a key you install, pins host keys trust-on-first-use, requires confirmation for destructive commands, and keeps any sudo password in memory only. See Tools Reference → Remote Administration.
All outbound tools (fetch_url, download_file, git) validate URLs against:
conduit, searxng, chroma, 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 toolsweb_search and fetch_url) — arbitrary content straight from external sites — is screened by ProtectAI's DeBERTa prompt-injection detector, a local classifier that runs entirely inside the container (openly licensed — no HuggingFace token required). If the content is flagged as a prompt-injection or jailbreak attempt, the tool result is replaced with a tool-error so the malicious text never enters the conversation, and the model is told the source is untrustedos.path.basename() with auto-increment on collisionScreening is controlled by the prompt_guard config block: enabled (default true), model (a short name like deberta, or any full HuggingFace repo id of a prompt-injection sequence classifier), threshold (malicious-probability cutoff, default 0.5), max_chars (per-result cap), and fail_closed (block untrusted content if the classifier cannot run; default false fails open). The model downloads once to data/models/ and is reused across restarts.
config.json - in .env, the managed data/secrets.env, or Bitwarden - and are injected at container startupSee Secrets & Bitwarden for the full resolution model.
A small set of services orchestrated by Docker Compose - all on a private internal network, with only the Web Control Panel published to localhost.
| Service | Image | Purpose |
|---|---|---|
memtrix | Built from Dockerfile | The agent. Non-root, read-only root, all caps dropped. |
web | Built from Dockerfile | FastAPI backend + React control panel. Published on localhost:8800. |
chroma | chromadb/chroma:latest | Shared vector store for memory. Internal only, reached via CHROMA_URL. |
conduit | matrixconduit/matrix-conduit:latest | Matrix homeserver. Port 6167 exposed. Optional with an external homeserver. |
searxng | searxng/searxng:latest | Web search engine. Internal only. |
chroma service gives the agent and the web panel a single, consistent vector store - avoiding SQLite single-writer corruption that would occur if both processes opened an embedded database. Both connect via CHROMA_URL=http://chroma:8000.| Mount | Container path | Content |
|---|---|---|
./workspace | /home/memtrix/workspace | Core files, memory, attachments |
./agents | /home/memtrix/agents | Sub-agent workspaces |
./data | /home/memtrix/data | Config, secrets.env, sessions, and downloaded models (embedding, prompt-guard, whisper) under data/models/ |
./data/cache | /home/memtrix/.cache | ChromaDB client cache & general HuggingFace cache |
| chroma volume | /chroma | Persistent vector data for the chroma service |
python:3.13-slimgit only (minimal attack surface)memtrix (uid/gid 1000)web service)PYTHONUNBUFFERED=1 for real-time log output# Start all services
docker compose up -d
# View agent logs
docker compose logs -f memtrix
# View web control panel logs
docker compose logs -f web
# Restart the agent only
docker compose restart memtrix
# Stop everything
docker compose down
# Rebuild after code changes
docker compose build && docker compose up -d
Memtrix is designed to connect and start responding within seconds, then do the heavy lifting - embedding, indexing, reasoning - in the background, off the request path.
Rather than waiting 40-90 seconds for the embedding model and indexes before accepting messages, the agent connects to Matrix and starts responding right away. The expensive work is deferred:
The local embedding model (nomic-embed-text-v1.5) runs in-process. To keep the agent responsive while it indexes:
MEMTRIX_EMBED_THREADS environment variable.Conversation indexing is incremental and survives restarts via a per-chunk content-hash cache (.chunk-hashes.json stored alongside the index):
The documentation index is content-hashed too - it only rebuilds when the docs actually change. Both run a background sync roughly every 300 seconds.
| Worker | What it does |
|---|---|
| Heartbeat | Writes agent liveness every ~10s so the Web Control Panel can tell if the agent is up |
| Deriver | Reasoning-memory extraction and daily consolidation (see Reasoning Memory) |
| Conversation sync | Embeds new conversation chunks on a periodic interval |
| Docs sync | Keeps the documentation index current |
data/models/ and, for voice, the whisper model into data/models/ on first use. The agent stays responsive throughout.MEMTRIX_EMBED_THREADS keeps the agent snappier during large reindex passes at the cost of slower indexing. On a dedicated box you can raise it to index faster.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 (or /new) - resets the current sessiondata/ as JSON files organized by dateworkspace/memory/chroma service - browse, edit, or export them from the Web Control Paneldocker compose ps webdocker compose logs -f weblsof -i :8800), or change MEMTRIX_WEB_PORTMEMTRIX_WEB_TOKEN the panel asks fordocker compose ps chromamemtrix and web point at CHROMA_URL=http://chroma:8000docker compose logs -f chromadocker compose down
rm -rf data/ workspace/ agents/ .env
./setup.sh
./onboard.sh
docker compose up -d
Every release, newest first. This page is rendered live from CHANGELOG.md in the repository - the single source of truth.
Loading changelog…