--- title: 'Hermes: Mnemosyne long-term memory' url: https://blog.guigpap.com/en/hermes/memoire-mnemosyne/ url_md: https://blog.guigpap.com/en/hermes/memoire-mnemosyne.md category: hermes date: '2026-08-04' maturite: production techno: - docker - telegram application: - ai - knowledge --- # Hermes: Mnemosyne long-term memory > External memory provider on local SQLite, hybrid vector + FTS5 search, installed in wrapper mode to survive image updates ## 1. What? — Definition and context An agent that forgets everything between two conversations is just a chatbot with tools. What distinguishes [Hermes](/en/hermes/) from a one-off LLM call is that it **accumulates**: what is said on Monday is still available on Thursday. [Mnemosyne](https://github.com/mnemosyne-oss/mnemosyne) is the external memory provider that delivers this persistence — a local SQLite database with hybrid vector + FTS5 search and background capture on every turn. ### What Mnemosyne replaces Hermes' native memory is a `MEMORY.md` file re-injected into the system prompt. Simple and readable, but it plateaus quickly. | | Native `MEMORY.md` | Mnemosyne | |---|---|---| | **Storage** | A Markdown file | SQLite database | | **Search** | None — everything is re-injected | Hybrid: vector + FTS5 | | **Budget** | Character cap in the prompt | Selective recall driven by the query | | **Capture** | Explicit write | Background task on every turn | | **Structure** | Free text | Episodes, temporal triples, graph | The native file is not removed for all that: it stays in place and becomes the active memory again if the external provider is disabled. ### Active configuration ```yaml memory: memory_enabled: true user_profile_enabled: true memory_char_limit: 2200 user_char_limit: 1375 provider: mnemosyne nudge_interval: 10 flush_min_turns: 6 ``` Two distinct budgets: `memory_char_limit` for what the agent has retained, `user_char_limit` for the user profile it builds up across exchanges. --- ## 2. Why? — Stakes and motivations ### Why wrapper mode rather than a custom image? This is the structuring decision of the installation. Three routes were possible: | Route | Cost | Problem | |------|------|----------| | **Custom image** | A `Dockerfile` extending the upstream image | Breaks the DIUN flow: no more automatic detection of upstream updates, manual rebuild on every version | | **Patching `/opt/hermes`** | Modifying the application tree inside the container | Overwritten on every image update | | **Wrapper mode** | Side venv + plugin, entirely under `/opt/data` | Survives recreates **and** image updates | Wrapper mode wins because `/opt/data` is a bind mount: everything living there is outside the image lifecycle. The container stays strictly the upstream image, DIUN keeps detecting new versions, and there is nothing to rebuild. > **Note - The price to pay** > > The container memory limit goes from 2 to 3 GB — the `mnemosyne-memory[embeddings]` profile bundles fastembed and its ONNX models, which run locally. On a 16 GB VPS that is the most expensive item in the ai-stack after Qdrant and CLI Ollama. In practice the container stays under 500 MiB at rest. ### Why local-first? Capture runs on every turn, over everything passing through the conversation: meeting notes, technical decisions, information about contacts. Sending that stream to an external vectorisation service would amount to continuously exporting the content of the exchanges. Mnemosyne computes its embeddings locally and stores everything in a SQLite file on the data volume. No conversation data leaves the VPS to be indexed. --- ## 3. How? — Technical implementation ### Where everything lives | Item | Path | |---------|--------| | Side venv (`mnemosyne-hermes` → `mnemosyne-memory[embeddings]`) | `/opt/data/.mnemosyne/venv` | | Plugin shim + manifest (discovery by Hermes) | `/opt/data/plugins/mnemosyne/` | | Bundled skill | `/opt/data/skills/memory/mnemosyne-memory-override/` | | SQLite database | `/opt/data/mnemosyne/data/mnemosyne.db` | Activation is a single line: `memory.provider: mnemosyne` in the config file. ### The exposed tools The plugin declares **20 tools** and **3 hooks** (`pre_llm_call`, `on_session_start`, `post_tool_call`): | Family | Tools | |---------|--------| | Memory | `mnemosyne_remember`, `mnemosyne_recall`, `mnemosyne_update`, `mnemosyne_forget`, `mnemosyne_invalidate` | | Graph and triples | `mnemosyne_triple_add`, `mnemosyne_triple_query`, `mnemosyne_graph_query`, `mnemosyne_graph_link` | | Scratchpad | `mnemosyne_scratchpad_write`, `mnemosyne_scratchpad_read`, `mnemosyne_scratchpad_clear` | | Consolidation | `mnemosyne_sleep` | | Transfer | `mnemosyne_export`, `mnemosyne_import`, `mnemosyne_sync_push`, `mnemosyne_sync_pull`, `mnemosyne_sync_status` | | Diagnostics | `mnemosyne_stats`, `mnemosyne_diagnose` | The hooks do the invisible work: `on_session_start` loads the relevant context, `pre_llm_call` injects the recall into the prompt, `post_tool_call` captures what deserves keeping. > **Tip - Consolidation through 'sleep'** > > `mnemosyne_sleep` triggers a consolidation pass: episode grouping, deduplication, promotion of recurring facts. By default it uses no LLM. The `MNEMOSYNE_HOST_LLM_ENABLED=true` option routes those calls to Hermes' Codex client — finer, but it consumes the shared ChatGPT quota, hence the choice to leave it disabled. ### Operations ```bash # Is the provider actually active? docker exec hermes hermes memory status # → Provider: mnemosyne, available # Database statistics docker exec --user hermes -e HOME=/opt/data -e HERMES_HOME=/opt/data hermes \ /opt/data/.mnemosyne/venv/bin/mnemosyne stats # Update docker exec --user hermes -e HOME=/opt/data hermes \ /opt/data/.mnemosyne/venv/bin/pip install --no-cache-dir -U mnemosyne-hermes docker compose -f ai-stack/docker-compose.yaml restart hermes ``` > **Caution - The wrapper goes stale after a Python bump** > > The side venv points at the image's interpreter. If an image update moves Python from one minor version to the next, the `site-packages` are no longer importable and the provider silently falls "stale". The repair is a full reinstall: > > ```bash > docker exec --user hermes -e HOME=/opt/data hermes \ > /opt/hermes/.venv/bin/python3 -m venv --clear /opt/data/.mnemosyne/venv > docker exec --user hermes -e HOME=/opt/data hermes \ > /opt/data/.mnemosyne/venv/bin/pip install --no-cache-dir mnemosyne-hermes > docker exec --user hermes -e HOME=/opt/data -e HERMES_HOME=/opt/data hermes \ > /opt/data/.mnemosyne/venv/bin/mnemosyne-hermes install --mode wrapper --force \ > --python /opt/data/.mnemosyne/venv/bin/python > docker compose -f ai-stack/docker-compose.yaml restart hermes > ``` > > That is the flip side of wrapper mode: it decouples the installation from the image, but not from its interpreter. > **Danger - memory off, never tools disable memory** > > To go back to native memory the command is `hermes memory off` — it disables the external provider and lets `MEMORY.md` take over. `hermes tools disable memory` does something else entirely: it removes the whole memory toolset, native included. The agent then loses every memorisation capability, not just Mnemosyne. ### Backup The database goes into the [daily backup](/en/infrastructure/database-backup/). The venv and the fastembed model cache are excluded: they are heavy and reinstall in one command. A consequence worth knowing for a restore: the database comes back as-is, but the venv has to be recreated before the provider becomes available again. --- ## 4. What if? — Outlook and limits ### Current limits | Limit | Impact | Mitigation | |--------|--------|------------| | **Memory tied to the container** | A second user would share the same memory and the same profile | One container per user rather than a widened allowlist | | **Consolidation without an LLM** | Episode grouping stays mechanical | `MNEMOSYNE_HOST_LLM_ENABLED=true`, at the cost of quota | | **Opaque content** | The SQLite database is not readable in Obsidian | Export as markdown notes, planned | | **Wrapper fragile to a Python bump** | Silent failure after an image update | Check `hermes memory status` after every update | | **No memory shared with the workstation** | What Hermes knows, the local assistant does not | Bidirectional sync, planned | ### Evolution scenarios **If memory has to become readable**: - Export memories as markdown notes into `vps-vault`, with provenance frontmatter. - The point goes beyond reading: memory versioned in Git becomes auditable and correctable by hand. - The `mnemosyne-operations` skill already covers the governance of that export. **If memory has to be shared between the agent and the workstation**: - The `mnemosyne_sync_push` / `mnemosyne_sync_pull` tools already exist on the plugin side. - What remains is choosing the source of truth on conflict, and deciding whether client-side encryption is needed. **If volume becomes a problem**: - `mnemosyne_invalidate` and `mnemosyne_forget` already allow targeted pruning. - A regular consolidation pass compresses better than brutal deletion. --- ## Related pages ### Hermes - [Hermes Agent](/en/hermes/) — The deployment and its security model - [Plugins](/en/hermes/plugins/) — Mnemosyne is one of the four active plugins - [Skills](/en/hermes/skills/) — `mnemosyne-operations`, the memory governance procedure ### Infrastructure - [Database backup](/en/infrastructure/database-backup/) — The database is backed up, the venv is not - [AI Stack](/en/infrastructure/ai-stack/) — Qdrant, the infrastructure's other vector database ### Reference - [Glossary](/en/reference/glossary/) — Long-term memory, Embeddings, Vector Database ## Métadonnées agent - Cet article est issu du blog GuiGPaP Lab. - Contexte global du blog: https://blog.guigpap.com/llms.txt - Contact auteur: https://odoo.guigpap.com/mon-cv - Licence: CC-BY-SA 4.0