qmemory
v0.5.0
Published
Graph memory for AI agents — context engine for OpenClaw + MCP server for Claude Code/Claude.ai
Downloads
3,314
Maintainers
Readme
🧠 Qmemory
Graph memory for AI agents — the agent that remembers everything, connects everything, and never forgets.
What it is: A graph-based context engine that gives AI agents persistent, cross-session intelligence. Every fact is a node. Every relationship is an edge. Nothing is forgotten.
Why it exists: The built-in context engine stores memories as flat files with no cross-session recall. Qmemory plugs into the same context-engine slot and adds graph relationships, salience scoring, temporal validity, and 4-tier hybrid search — so your agent remembers across sessions, topics, and channels.
Three ways to use it:
- 🔌 OpenClaw context engine — deepest integration, full session lifecycle management
- 🖥️ MCP server for Claude Code — stdio transport, add to
~/.claude.json- 🌐 MCP server for Claude.ai — HTTP transport, connect via remote MCP
📋 Table of Contents
- The Problem
- The Solution
- What the Agent Sees
- How It Works
- Features
- Comparison with Other Solutions
- Installation
- Configuration
- Tools Reference
- Graph Viewer UI
- Agent Memory Management
- For AI Agents — SKILL.md
- Graph Schema
- Migration
- How Memory Updates
- External References
- Community Issues Solved
- Troubleshooting
- Development
- Wiki / Reference Files
- License
- Credits
😤 The Problem
Every AI agent session is an island. Your agent learns things in one conversation — decisions, preferences, project context — and then forgets everything the moment the session ends.
┌─────────────────────────────────────────────────────────────────────┐
│ THE FILING CABINET WITH LOCKED DRAWERS │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Topic 7 │ │ Topic 9 │ │ Cron │ │ Subagent │ │
│ │ │ │ │ │ Job │ │ Task │ │
│ │ "Budget │ │ "What │ │ │ │ │ │
│ │ approved│ │ was the │ │ "Check │ │ "Deploy │ │
│ │ at 500K"│ │ budget?"│ │ Railway │ │ to │ │
│ │ │ │ 🤷 ??? │ │ status" │ │ prod" │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ 🔒 🔒 🔒 🔒 │
│ │
│ Each drawer is LOCKED. Session 9 cannot see what Session 7 knew. │
│ The cron job has no idea about the budget. The subagent is blind. │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Topic 12 │ │ DM │ ← Even direct messages are isolated │
│ │ "Railway │ │ "User │ │
│ │ is on │ │ prefers │ │
│ │ us-east" │ │ Arabic" │ │
│ └──────────┘ └──────────┘ │
│ 🔒 🔒 │
│ │
│ 6 sessions. 6 locked drawers. Zero shared knowledge. │
└─────────────────────────────────────────────────────────────────────┘The result? You repeat yourself. The agent asks the same questions. Decisions made in Topic 7 are invisible in Topic 9. Your cron jobs operate without context. Subagents start from scratch every time.
This is how every AI assistant works by default — including OpenClaw's built-in context engine (which uses flat markdown files with no cross-session recall).
✨ The Solution
Qmemory turns the filing cabinet into a connected graph. Every fact, every decision, every preference becomes a node — and they're all connected through typed relationships.
┌─────────────────────────────────────────────────────────────────────┐
│ THE CONNECTED GRAPH — QMEMORY IN ACTION │
│ │
│ ┌──────────┐ ┌──────────────────────┐ ┌──────────┐ │
│ │ Topic 7 │────────▶│ 🧠 Qmemory Graph │◀──────│ Topic 12 │ │
│ │ "Budget │ │ │ │ "Railway │ │
│ │ = 500K" │ │ ┌──────────────┐ │ │ us-east" │ │
│ └──────────┘ │ │ memory:m001 │ │ └──────────┘ │
│ │ │ "Budget 500K │ │ │
│ ┌──────────┐ │ │ by team lead"│ │ ┌──────────┐ │
│ │ Topic 9 │────────▶│ └──────┬───────┘ │◀──────│ Cron │ │
│ │ │ │ │supports │ │ Job │ │
│ │ ✅ Knows │ │ ┌──────▼───────┐ │ └──────────┘ │
│ │ budget! │ │ │ memory:m042 │ │ │
│ └──────────┘ │ │ "Railway on │ │ ┌──────────┐ │
│ │ │ us-east-1" │ │◀──────│ Subagent │ │
│ ┌──────────┐ │ └──────────────┘ │ │ │ │
│ │ DM │────────▶│ │ │ ✅ Knows │ │
│ │ "Prefers │ │ All nodes linked. │ │ context! │ │
│ │ Arabic" │ │ All sessions see. │ └──────────┘ │
│ └──────────┘ └──────────────────────┘ │
│ │
│ Topic 9 asks about the budget → Qmemory recalls it from Topic 7 │
│ Subagent deploys → Qmemory tells it "Railway is on us-east-1" │
│ Cron job runs → it knows the budget, the server, the preferences │
└─────────────────────────────────────────────────────────────────────┘Every session sees every memory. Sorted by importance. Filtered by relevance. Connected through relationships the agent itself creates.
👁️ What the Agent Sees
When Qmemory is active, it automatically injects relevant memories into the agent's system prompt before every response. Here's exactly what gets added:
## Cross-Session Memory (Qmemory)
_5 memories recalled, sorted by importance_
- [decision!] Budget approved at 500K SAR by team lead — final, no renegotiation
↳ Related: email in HEY (hey:inv-2026-003), task in Reminders
- [context!] Railway deployment on us-east-1, SurrealDB port must be 8000
↳ Related: entity "Railway" (deployment), entity "SurrealDB" (system)
- [preference] User prefers Arabic-first (RTL) with Cairo font across all projects
- [context] Acme CRM has 3 active clients: Waqf Fund, SRCA, Tamheer
↳ Scope: project:r-crm
- [decision] Use Hotwire (not React) for all new frontends — DHH philosophy
↳ Expires: 2027-01-01Key details:
- The
!marker means salience ≥ 0.8 (critical facts — always recalled first) - Memories are sorted by importance, not by date
- External references (emails, tasks) are linked, not duplicated
- Scope filtering means project-specific memories only appear in the right context
- Expired facts are automatically filtered out
- The injection respects a token budget (default 15% of context window) — it won't flood your prompt
🏗️ How It Works
Three Entry Points, One Core
┌─────────────────────────────────────────────────────────────────┐
│ ENTRY POINTS │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ OpenClaw │ │ Claude Code │ │ Claude.ai │ │
│ │ Plugin │ │ (stdio MCP) │ │ (HTTP MCP) │ │
│ │ │ │ │ │ │ │
│ │ Context │ │ 4 tools via │ │ 4 tools via │ │
│ │ engine + │ │ FastMCP │ │ FastMCP │ │
│ │ 5 tools + │ │ │ │ httpStream │ │
│ │ linker + │ │ │ │ │ │
│ │ graph UI │ │ │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ SHARED CORE (src/core/) │ │
│ │ │ │
│ │ recall search save correct link │ │
│ │ extract dedup embeddings migrate │ │
│ └──────────────────────┬─────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ SurrealDB 3.0+ │ │
│ │ │ │
│ │ ┌─────┐ ┌─────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │sess-│ │ message │ │ memory │ │ entity │ │ │
│ │ │ion │ │ │ │ │ │ │ │ │
│ │ └──┬──┘ └────┬─────┘ └───┬────┘ └───┬────┘ │ │
│ │ │ │ │ │ │ │
│ │ has_message extracted_from prev_version │ │
│ │ relates (dynamic — ANY type) │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘The Graph Schema
Qmemory's brain is a graph with 7 table types and 5 edge types:
┌──────────┐ ┌──────────┐
│ session │──has_message──▶│ message │
│ │ │ │
│ channel │ │ role │
│ chat_type│ │ content │
│ scope │ │ tokens │
└──────────┘ └─────┬────┘
│
extracted_from
│
┌──────────┐ ┌─────▼────┐
│ entity │◀──relates───▶ │ memory │
│ │ (dynamic) │ │
│ name │ │ content │
│ type │ │ category │
│ aliases │ │ salience │──prev_version──▶ (old memory)
│ external │ │ scope │
│ _source │ │ validity │
│ _id │ │ active? │
│ _url │ │ embedding│
└──────────┘ └──────────┘
7 tables: session, message, memory, entity, tool_call, scratchpad, metrics
3 structural edges: has_message, extracted_from, prev_version (auto-created)
1 dynamic edge: relates (agent creates ANY relationship type)The relates edge is special — it accepts ANY relationship type as a string: supports, contradicts, manages, blocks, depends_on, caused_by, reports_to, or anything the agent decides fits. This means the graph grows organically based on your actual work, not a rigid predefined ontology.
🚀 Features
Memory & Recall
- 4-tier hybrid recall — graph traversal → BM25 full-text → vector similarity → recent fallback
- Salience scoring (0.0–1.0) — critical facts (
salience ≥ 0.8) are always recalled first - 7 memory categories —
style,preference,context,decision,idea,feedback,domain - Temporal validity — facts can have
valid_fromandvalid_untildates; expired facts auto-filtered - Memory scoping —
global,project:xxx, ortopic:xxx— right memories in the right context
Deduplication & Accuracy
- LLM-driven dedup — every new fact is checked against existing memories (ADD / UPDATE / NOOP)
- Version chains — corrections create a
prev_versionedge; originals are never deleted (soft-delete only) - Contradiction resolution — the reflect service auto-detects conflicting facts and deactivates the outdated one
Graph Intelligence
- Dynamic relationships — the agent creates ANY edge type between any two nodes (
supports,contradicts,blocks, etc.) - Connection hints in search — top 5 search results show graph edges (type, target name, reason) so the agent sees and follows connections like a mind map
- Post-save nudges — after saving a memory, shows 2-3 nearby memories with
qmemory_link()suggestions to build connections - Background linker (every 5 min) — finds unlinked memories, asks the LLM to discover relationships, creates edges
- Salience decay (every 5 min) — memories older than 7 days get salience *= 0.95 (floor 0.1), so old facts naturally fade unless recalled
- Background reflect (every 30 min) — synthesizes insights across memories, resolves contradictions (inspired by Hindsight)
- Auto graph structure — channel/topic/session hierarchy built automatically on bootstrap (no agent action needed)
- 12 lifecycle hooks — captures tool calls, cron outcomes, token usage, subagent relationships, sender identity, delivery routing, session lifecycle
- Entity extraction — people, projects, orgs, concepts, and systems become first-class graph nodes
Book Library Ingestion
- PDF + EPUB processing — standalone Python script ingests entire book libraries into the memory graph
- Smart OCR — PyMuPDF native text first, Gemini Vision OCR fallback for scanned documents (Arabic-optimized)
- Arabic-aware chunking — ~900 tokens per chunk, 15% overlap, sentence boundary detection (. ؟ ! \n\n)
- Graph integration — books and authors become entity nodes, chunks become domain memories, all connected via
relatesedges - 📚 Library section in graph map — books shown separately in context injection with connection counts and search nudges
OpenClaw Deep Integration
- Pre-compaction memory flush — at 70% context usage, Qmemory extracts memories before compaction fires (fixes OpenClaw #19488)
- Post-compaction amnesia fix — high-salience memories are re-injected into the summary after compaction
- Subagent memory sharing — when a subagent spawns, it receives relevant memories from the parent session
- Token budget control — memory injection capped at 15% of context window (configurable)
External References
- Linked entities — emails (HEY), tasks (Apple Reminders), Smartsheet rows, Railway deployments, calendar events
- External IDs and URLs — each entity can reference its source system (
hey:12345,smartsheet:row:789)
Visualization & Tools
- Graph viewer UI — interactive vis.js visualization at
/qmemory/graph(dark theme, Arabic-friendly) - Graph API — JSON endpoint at
/qmemory/api/graphwith filters (category, scope, date range) - Migration tool — import existing
MEMORY.mdandmemory/*.mdfiles from OpenClaw workspaces - CLI —
qmemory status,qmemory schema,qmemory serve,qmemory serve-http
📊 Comparison with Other Solutions
| Feature | Qmemory | OpenClaw Built-in | Mem0 | LanceDB-pro | Hindsight |
|---|---|---|---|---|---|
| Storage | SurrealDB graph | Flat markdown files | Managed cloud | LanceDB (vector) | Custom graph |
| Cross-session recall | ✅ 4-tier hybrid | ❌ Same-session only | ✅ Vector search | ✅ Vector search | ✅ Graph + vector |
| Graph relationships | ✅ Dynamic (any type) | ❌ None | ❌ None | ❌ None | ✅ Fixed types |
| Deduplication | ✅ LLM-driven | ❌ None | ✅ Rule-based | ❌ None | ✅ LLM-driven |
| Salience scoring | ✅ 0.0–1.0 + decay | ❌ No ranking | ❌ No scoring | ❌ No scoring | ✅ Importance |
| Temporal validity | ✅ valid_from/until | ❌ No expiry | ❌ No expiry | ❌ No expiry | ✅ Time-aware |
| Background linking | ✅ Every 5 min | ❌ None | ❌ None | ❌ None | ✅ Periodic |
| Reflection/synthesis | ✅ Every 30 min | ❌ None | ❌ None | ❌ None | ✅ Periodic |
| Version history | ✅ prev_version chain | ❌ Overwrite | ❌ Overwrite | ❌ Overwrite | ✅ Versioned |
| External references | ✅ Email, tasks, etc. | ❌ None | ❌ None | ❌ None | ❌ None |
| Self-hosted | ✅ SurrealDB local | ✅ Local files | ❌ Cloud only | ✅ Local | ❌ Cloud only |
| OpenClaw integration | ✅ Context engine plugin | ✅ Built-in | ❌ MCP only | ❌ MCP only | ❌ Not compatible |
| MCP support | ✅ stdio + HTTP | ❌ None | ✅ stdio | ✅ stdio | ❌ None |
| LongMemEval accuracy | — (pending) | ~40% | ~65% | ~60% | 91.4% (highest) |
| Dependencies | 4 (surrealdb, fastmcp, typebox, zod) | 0 (built-in) | Managed service | 1 (lancedb) | Managed service |
| License | MIT | MIT | Proprietary | Apache 2.0 | Proprietary |
| Price | Free | Free | $$$$ | Free | $$$$ |
Why Qmemory over the others?
- vs Built-in: Qmemory plugs into the same context-engine slot and adds cross-session recall, graph relationships, salience, and temporal validity — the built-in default stores flat files per session
- vs Mem0: Qmemory is self-hosted, graph-based, and integrates as a full context engine — Mem0 is cloud-only vector search
- vs LanceDB-pro: Qmemory has typed relationships and dedup — LanceDB is pure vector with no graph intelligence
- vs Hindsight: Comparable features, but Qmemory is open-source, self-hosted, and runs as an OpenClaw context-engine plugin — Hindsight is proprietary cloud
📦 Installation
Prerequisites
- Node.js 22+ —
node --version - SurrealDB 3.0+ — Install SurrealDB
Option 1: OpenClaw Plugin (Full Power) 🔌
This is the deepest integration. Qmemory registers as a context-engine plugin and manages the full session lifecycle.
Step 1: Install SurrealDB 3.0+
Important: Qmemory requires SurrealDB v3.0 or later. Older versions (v2.x) have incompatible syntax for schemas, FULLTEXT indexes, and record IDs. Check your version with
surreal version.
# macOS (installs latest v3)
brew install surrealdb/tap/surreal
# Linux (installs latest v3)
curl -sSf https://install.surrealdb.com | sh
# Verify version — must be 3.x
surreal version
# Expected: surreal 3.0.0 or higher
# Start the server (data persists to disk)
surreal start --user root --pass root file:~/.qmemory/data.dbStep 2: Install the plugin
# From npm (recommended)
npm install -g qmemory
# OR directly through OpenClaw
openclaw plugins install qmemory
# Install dependencies (required after openclaw plugins install)
cd ~/.openclaw/extensions/qmemory && npm install --omit=devStep 3: Set Qmemory as your context engine
openclaw config set plugins.slots.contextEngine "qmemory"Step 4: Enable plugin tools
The coding tool profile only includes core tools by default. Choose one:
# Minimal — only Qmemory tools
openclaw config set tools.alsoAllow '["qmemory"]'
# Recommended — all plugin tools (if you have other plugins too)
openclaw config set tools.alsoAllow '["group:plugins"]'Without this step, Qmemory's 6 tools will not appear in the agent's tool list.
"group:plugins"is recommended because it lets the agent use tools from ALL installed plugins — useful when Qmemory works alongside other plugins in group chats and topics.
Step 5: (Optional) Configure the plugin
# Set SurrealDB password (default: root)
openclaw config set plugins.config.qmemory.surrealdb_pass "your-password"
# Enable vector search (uses your existing OpenClaw embedding key)
openclaw config set plugins.config.qmemory.embedding_provider "auto"Step 6: Restart the gateway
openclaw gateway restartStep 7: Verify
openclaw plugins inspect qmemory
# Check the graph is working
qmemory statusYou should see:
Qmemory: connected
SurrealDB: ws://localhost:8000
Namespace: qmemory/main
Memories: 0
Entities: 0
Edges: 0
Sessions: 0Important: SurrealDB must be running before the OpenClaw gateway starts. If SurrealDB is down when the gateway boots, Qmemory runs in degraded mode (no memory operations, but no crash).
Option 2: Claude Code (MCP Server — stdio) 🖥️
Add Qmemory as an MCP server in your Claude Code configuration.
Step 1: Install
npm install -g qmemoryStep 2: Start SurrealDB 3.0+
# Install if needed (see Option 1 above for full instructions)
surreal version # Must be 3.x
surreal start --user root --pass root file:~/.qmemory/data.dbStep 3: Apply the schema
qmemory schemaStep 4: Add to Claude Code config
Add this to your ~/.claude.json (or your project's .claude.json):
{
"mcpServers": {
"qmemory": {
"command": "qmemory",
"args": ["serve"],
"env": {
"QMEMORY_SURREALDB_URL": "ws://localhost:8000",
"QMEMORY_SURREALDB_USER": "root",
"QMEMORY_SURREALDB_PASS": "root"
}
}
}
}Step 5: Restart Claude Code
Claude Code will now have 4 tools: qmemory_search, qmemory_save, qmemory_correct, qmemory_link.
Note: In Claude Code mode, you get 4 tools (no
qmemory_import). Auto-injection of memories into the system prompt requires the OpenClaw plugin mode.
Option 3: Claude.ai (MCP Server — HTTP) 🌐
Run Qmemory as an HTTP MCP server and connect it to Claude.ai's remote MCP feature.
Step 1: Install and start
npm install -g qmemory
surreal start --user root --pass root file:~/.qmemory/data.db
qmemory schemaStep 2: Start the HTTP server
# Default port: 3777
qmemory serve-http
# Custom port
qmemory serve-http 4000You should see:
Qmemory MCP server running on http://localhost:3777/mcpStep 3: Connect from Claude.ai
In Claude.ai settings, add a remote MCP server pointing to http://localhost:3777/mcp (or your public URL if deployed).
Tip: For production, deploy Qmemory behind a reverse proxy with HTTPS. The MCP endpoint is at
/mcp.
⚙️ Configuration
OpenClaw Plugin Config
All options are set via openclaw config set plugins.config.qmemory.<key> <value>:
| Option | Type | Default | Description |
|---|---|---|---|
| surrealdb_url | string | ws://localhost:8000 | SurrealDB connection URL |
| surrealdb_user | string | root | SurrealDB username |
| surrealdb_pass | string | root | SurrealDB password |
| namespace | string | qmemory | SurrealDB namespace |
| database | string | main | SurrealDB database name |
| context_threshold | number | 0.75 | Trigger compaction at this % of context window (0–1) |
| fresh_tail_count | integer | 32 | Number of recent messages protected from compaction |
| memory_budget_pct | number | 0.15 | Max % of context window for memory injection (0–1) |
| embedding_provider | string | auto | Embedding provider: auto (reuse OpenClaw's key), voyage, openai, gemini, or none (BM25 only) |
| embedding_api_key | string | — | Override API key for embeddings (leave empty to use OpenClaw's) |
| embedding_model | string | — | Override embedding model (leave empty to use OpenClaw's) |
| embedding_dimension | integer | 1024 | Embedding vector dimension |
| linker_interval_ms | integer | 300000 | Background linker interval (default: 5 min) |
| reflect_interval_ms | integer | 1800000 | Background reflection interval (default: 30 min) |
| min_salience_recall | number | 0.3 | Minimum salience to include in recall results |
| subagent_model | string | zai/glm-5 | Model for background LLM tasks (dedup, extract, link). Use cheap models like zai/glm-5 or google-gemini-cli/gemini-3-flash-preview to reduce costs |
| extraction_mode | string | balanced | Adaptive extraction preset: economy (Lite plans, saves tokens), balanced (Pro plans, normal), aggressive (Team/Unlimited, no limits) |
| debug | boolean | false | Enable debug logging |
Environment Variables (CLI / MCP mode)
When running as a standalone MCP server, configure via environment variables:
| Variable | Default | Description |
|---|---|---|
| QMEMORY_SURREALDB_URL | ws://localhost:8000 | SurrealDB connection URL |
| QMEMORY_SURREALDB_USER | root | SurrealDB username |
| QMEMORY_SURREALDB_PASS | root | SurrealDB password |
| QMEMORY_NAMESPACE | qmemory | SurrealDB namespace |
| QMEMORY_DATABASE | main | SurrealDB database name |
| QMEMORY_EMBEDDING_PROVIDER | none | voyage, openai, or none |
| QMEMORY_EMBEDDING_API_KEY | — | API key for embedding provider |
| QMEMORY_DEBUG | false | Enable debug logging |
🔧 Tools Reference
Qmemory exposes 6 tools in OpenClaw mode and 4 tools in MCP mode.
qmemory_search
Search cross-session memory AND tool call history. Top 5 results are enriched with graph connection hints — the agent sees linked books, people, and other memories, encouraging exploration through the knowledge graph.
| Parameter | Type | Required | Description |
|---|---|---|---|
| query | string | No | Search by meaning (BM25 full-text + vector similarity) |
| categories | string[] | No | Filter: style, preference, context, decision, idea, feedback, domain |
| scope | string | No | Filter: global, project:xxx, topic:xxx |
| limit | number | No | Max results, 1–50 (default: 10) |
| include_tool_calls | boolean | No | Also search the tool_call table across all sessions |
| tool_name | string | No | Filter tool calls by name (e.g., "exec", "qmemory_save") |
Examples:
qmemory_search({ query: "Railway deployment", categories: ["context", "decision"], limit: 5 })
qmemory_search({ include_tool_calls: true, tool_name: "exec" })
qmemory_search({ query: "Railway", include_tool_calls: true })Returns:
[mem001] [context, salience:0.9] Railway deployment on us-east-1, SurrealDB port must be 8000
[mem042] [decision, salience:0.8] Use Railway free tier for staging, upgrade for productionqmemory_save
Save a fact to cross-session memory. Runs dedup automatically.
| Parameter | Type | Required | Description |
|---|---|---|---|
| content | string | Yes | The fact to remember (one clear statement) |
| category | string | Yes | style, preference, context, decision, idea, feedback, or domain |
| salience | number | No | Importance 0.0–1.0 (default: 0.5). Use 0.8+ for critical facts |
| scope | string | No | global (default), project:xxx, or topic:xxx |
Example:
qmemory_save({
content: "Budget approved at 500K SAR by team lead — final decision",
category: "decision",
salience: 0.9,
scope: "project:acme"
})Returns: ADD: memory:mem1710864000abc [decision, salience:0.9]
If a similar memory already exists, the system may return UPDATE (merged) or NOOP (already known).
qmemory_correct
Fix, update, delete, or unlink memories. 4 actions available.
| Parameter | Type | Required | Description |
|---|---|---|---|
| memory_id | string | Yes | The memory ID (e.g., memory:mem1234abcd) |
| action | string | Yes | correct, update, delete, or unlink |
| new_content | string | Only for correct | The corrected content |
| salience | number | Only for update | New importance score (0.0–1.0) |
| scope | string | Only for update | New scope (global, project:xxx, topic:xxx) |
| valid_until | string | Only for update | Expiry date (ISO 8601) |
| edge_id | string | Only for unlink | The relates edge ID to remove |
Examples:
// Fix wrong content (creates version chain)
qmemory_correct({ memory_id: "memory:mem001", action: "correct", new_content: "Actually 600K not 500K" })
// Update metadata (in-place, no new version)
qmemory_correct({ memory_id: "memory:mem001", action: "update", salience: 1.0 })
// Soft-delete
qmemory_correct({ memory_id: "memory:mem001", action: "delete" })
// Remove a relationship edge
qmemory_correct({ memory_id: "memory:mem001", action: "unlink", edge_id: "relates:r789" })Returns: Corrected: memory:mem001 → memory:mem002 (for correct action)
See Agent Memory Management for detailed explanation of all 4 actions.
qmemory_link
Create a relationship between any two nodes in the graph.
| Parameter | Type | Required | Description |
|---|---|---|---|
| from_id | string | Yes | Source node ID (e.g., memory:mem123, entity:ent456) |
| to_id | string | Yes | Target node ID |
| type | string | Yes | Any relationship: supports, contradicts, blocks, depends_on, caused_by, manages, etc. |
| reason | string | No | Why this relationship exists |
Example:
qmemory_link({
from_id: "memory:mem001",
to_id: "entity:ent_railway",
type: "deployed_on",
reason: "The application is hosted on Railway us-east-1"
})Returns: Linked: memory:mem001 —[deployed_on]→ entity:ent_railway (relates:r789)
qmemory_import (OpenClaw only)
Import a markdown file into the memory graph.
| Parameter | Type | Required | Description |
|---|---|---|---|
| file_path | string | Yes | Absolute path to the markdown file |
Example:
qmemory_import({ file_path: "~/.openclaw/workspace/memory/2026-03-14.md" })Returns: Imported: 12 facts extracted, 9 new memories created
🖥️ Graph Viewer UI
Qmemory includes an interactive graph visualization that shows your entire memory network — every fact, entity, and relationship as a live, explorable graph.
Access it at: http://localhost:<gateway-port>/qmemory/graph
For the default OpenClaw setup: http://127.0.0.1:18789/qmemory/graph
The viewer shows:
- Memory nodes — facts with salience coloring (brighter = more important)
- Entity nodes — people, projects, systems
- Relationship edges — typed connections (supports, manages, blocks, etc.)
- Filters — filter by category, scope, or date range
API endpoint: GET /qmemory/api/graph returns JSON { nodes, edges } with optional query params:
?category=context— filter by memory category?scope=global— filter by scope?from=2026-01-01&to=2026-03-18— filter by date range
No authentication needed on localhost. The graph viewer is read-only.
🧹 Agent Memory Management
Qmemory gives the agent (and you) full control over its memory — not just saving and searching, but editing, updating, expiring, deleting, and unlinking memories. The qmemory_correct tool supports 4 distinct actions:
1. Correct — Fix wrong content (version chain)
When a fact is wrong, correct creates a new version while preserving the old one via a prev_version edge. Nothing is ever truly lost.
qmemory_correct({
memory_id: "memory:mem001",
action: "correct",
new_content: "Budget approved at 600K SAR (revised up from 500K)"
})Result: Old memory soft-deleted → new memory created → prev_version edge links them.
2. Update — Change metadata (no new version)
Update salience, scope, or expiry without creating a new version. Use this when the fact is right, but its importance or context changed.
// Make something critical
qmemory_correct({ memory_id: "memory:mem001", action: "update", salience: 1.0 })
// Set an expiry date
qmemory_correct({ memory_id: "memory:mem001", action: "update", valid_until: "2026-06-01T00:00:00Z" })
// Change scope from topic-specific to global
qmemory_correct({ memory_id: "memory:mem001", action: "update", scope: "global" })3. Delete — Soft-delete a memory
Marks the memory as is_active = false. It stays in the graph (for audit) but no longer appears in recall results.
qmemory_correct({ memory_id: "memory:mem001", action: "delete" })4. Unlink — Remove a relationship
Remove a relates edge between two nodes. The nodes themselves remain — only the connection is severed.
qmemory_correct({ memory_id: "memory:mem001", action: "unlink", edge_id: "relates:r789" })Summary Table
| Action | What Changes | Creates New Version? | Old Data Preserved? |
|---|---|---|---|
| correct | Content | Yes (new node + prev_version edge) | Yes (soft-deleted, linked) |
| update | Salience, scope, expiry | No (in-place edit) | No (overwritten) |
| delete | is_active → false | No | Yes (still in graph) |
| unlink | Removes edge | No | No (edge deleted) |
🤖 For AI Agents — SKILL.md
Qmemory ships with a SKILL.md file — a comprehensive guide that teaches AI agents when and how to use Qmemory effectively. It covers:
- When to save — decision guide ("Would this be useful in a FUTURE conversation?")
- Salience scoring — what 0.3 vs 0.5 vs 0.8 vs 1.0 means with real examples
- Category selection — which of the 7 categories fits each type of information
- When to link — signals that indicate a relationship should be created
- When to correct — user feedback signals ("That's wrong", "That's not important anymore", "Forget that")
- Freeform relationship types — the agent can use any string as a relationship type
- Background processes — what happens automatically so the agent doesn't need to do it
How to Use SKILL.md
Add a reference to SKILL.md in your agent's system prompt, SOUL.md, or configuration:
For OpenClaw — add to your agent's SOUL.md:
## Memory
This agent uses Qmemory for cross-session graph memory.
See SKILL.md in the Qmemory plugin for the full memory management guide.For Claude Code — add to your project's .claude/CLAUDE.md:
## Memory
This project uses Qmemory (MCP server) for persistent memory.
When the user shares important facts, decisions, or preferences, save them with qmemory_save.
When you need past context, search with qmemory_search.
See the Qmemory SKILL.md for detailed usage guidelines.The SKILL.md teaches the agent to be a responsible memory manager — saving important things, skipping trivial exchanges, and using the right category and salience for each fact.
🕸️ Graph Schema
The full SurrealDB schema lives in schema/qmemory.surql. Here's the visual overview:
┌─────────────────────────── NODES ───────────────────────────┐
│ │
│ SESSION MESSAGE MEMORY │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ session_key │ │ role │ │ content │ │
│ │ channel │ │ content │ │ category │ │
│ │ chat_type │ │ tool_calls │ │ salience │ │
│ │ topic_id │ │ tool_name │ │ valid_from│ │
│ │ group_id │ │ token_count │ │ valid_until│ │
│ │ scope │ │ created_at │ │ scope │ │
│ │ last_active │ └──────────────┘ │ is_active │ │
│ └──────────────┘ │ confidence│ │
│ │ source_type│ │
│ ENTITY │ embedding │ │
│ ┌──────────────┐ └───────────┘ │
│ │ name │ │
│ │ type │ Types: person, project, org, concept, │
│ │ aliases[] │ system, email, task, event, document, │
│ │ external_id │ smartsheet, deployment │
│ │ external_url │ │
│ │ external_src │ │
│ │ embedding │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────── EDGES ───────────────────────────┐
│ │
│ STRUCTURAL (auto-created by the system): │
│ │
│ session ──[has_message]──▶ message │
│ memory ──[extracted_from]──▶ message │
│ memory ──[prev_version]──▶ memory (version chain) │
│ │
│ DYNAMIC (created by agent, linker, reflect, or compact): │
│ │
│ (any) ──[relates]──▶ (any) │
│ │
│ relates.type: ANY string │
│ "supports", "contradicts", "elaborates", │
│ "depends_on", "caused_by", "blocks", │
│ "manages", "reports_to", "deployed_on", │
│ "synthesized_from", "follows", ... │
│ │
│ relates.confidence: 0.0–1.0 │
│ relates.created_by: "agent"|"linker"|"compact"|"reflect"│
│ relates.reason: optional explanation │
│ │
└─────────────────────────────────────────────────────────────┘Indexes
- BM25 full-text search on
memory.content(primary search method) - Standard indexes on
is_active,category,salience,scope - Vector index (HNSW, optional) on
memory.embeddingwhen embeddings are enabled - Unique index on
session.session_key - Composite index on
entity.external_source + external_id
🔄 Migration (Import Old Memories)
Already have memory files from OpenClaw? Qmemory can import them.
Import an entire workspace
# Via the agent (OpenClaw mode — uses LLM for smart extraction)
> Import all my memory files into Qmemory
# The agent will call qmemory_import for each fileImport a single file
qmemory_import({ file_path: "/path/to/MEMORY.md" })How migration works
┌─────────────────────────────────────────────────────┐
│ MIGRATION PIPELINE │
│ │
│ ~/.openclaw/workspace/ │
│ ├── MEMORY.md ← Long-term curated │
│ └── memory/ │
│ ├── 2026-03-01.md ← Daily snapshot │
│ ├── 2026-03-02.md │
│ └── 2026-03-14.md │
│ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Smart Extract │ (with LLM) │
│ │ OR Simple Extract │ (without LLM — fallback) │
│ └─────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Dedup Pipeline │ ADD / UPDATE / NOOP │
│ └─────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Graph Nodes │ + chronological edges │
│ │ (memory, entity) │ (file A → file B) │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────┘Two modes:
- Smart import (with LLM): Extracts structured facts, categories, salience scores, and entities using a subagent
- Simple import (without LLM): Each non-empty line becomes a memory with heuristic categorization
Files are processed oldest → newest, with follows edges linking them chronologically.
⚡ How Memory Updates
Qmemory has 6 trigger points where memories are created or updated:
┌────────────────────────────────────────────────────────────────────┐
│ 6 MEMORY UPDATE TRIGGERS │
│ │
│ ① afterTurn │
│ After every agent response, extract facts from the last │
│ few messages. Runs in the background (non-blocking). │
│ │
│ ② Pre-compaction flush (70% context) │
│ When context hits 70%, urgently extract memories │
│ from older messages BEFORE compaction drops them. │
│ Fixes OpenClaw #19488. │
│ │
│ ③ compact() │
│ When context exceeds threshold (default 75%), extract │
│ memories from old messages and drop them from context. │
│ High-salience facts are re-injected (prevents amnesia). │
│ │
│ ④ qmemory_save (agent tool) │
│ The agent explicitly saves a fact (e.g., user says │
│ "remember this"). Goes through full dedup pipeline. │
│ │
│ ⑤ Background Linker (every 5 min) │
│ Finds unlinked memories, asks LLM for relationships, │
│ creates `relates` edges. Makes the graph smarter over time. │
│ │
│ ⑥ Background Reflect (every 30 min) │
│ Reviews recent memories, synthesizes insights, │
│ resolves contradictions. Creates new "insight" memories │
│ and deactivates outdated facts. │
│ │
└────────────────────────────────────────────────────────────────────┘🔗 External References
Entities in Qmemory can reference external systems. This means your agent can connect memories to real-world objects without duplicating data.
┌─────────────────────────────────────────────────────────────────┐
│ EXTERNAL REFERENCES │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ entity:ent001 │ │ memory:mem042 │ │
│ │ │ │ "Invoice sent │ │
│ │ name: "Invoice" │◀─────│ to team lead" │ │
│ │ type: "email" │ └─────────────────┘ │
│ │ external_source:│ │
│ │ "hey" │ ← Source system (HEY email) │
│ │ external_id: │ │
│ │ "hey:inv-003" │ ← ID in source system │
│ │ external_url: │ │
│ │ "https://app. │ ← Direct link to the email │
│ │ hey.com/..." │ │
│ └─────────────────┘ │
│ │
│ Supported sources: │
│ ┌──────────────────────────────────────────┐ │
│ │ hey → HEY email │ │
│ │ apple-reminders → Apple Reminders tasks │ │
│ │ smartsheet → Smartsheet rows │ │
│ │ railway → Railway deployments │ │
│ │ calendar → Calendar events │ │
│ └──────────────────────────────────────────┘ │
│ │
│ The agent doesn't copy email content into memory. │
│ It creates a LINK: "This decision relates to that email." │
└─────────────────────────────────────────────────────────────────┘🐛 Community Issues Solved
Qmemory was built to fix real problems reported in the OpenClaw community:
| Issue | Problem | How Qmemory Fixes It |
|---|---|---|
| #19488 | Pre-compaction flush never fires — memories lost during compaction | Pre-compaction flush at 70% context extracts facts before they're dropped |
| #19148 | Post-compaction amnesia — agent forgets critical context after compaction | High-salience memories (≥0.8) are re-injected into the summary |
| #18932 | No cross-session memory — each topic is isolated | Full graph-based recall across all sessions, topics, and channels |
| #18744 | Memory duplicates pile up — same fact stored 20 times | LLM-driven dedup pipeline: ADD / UPDATE / NOOP on every save |
| #18201 | No way to correct wrong memories | qmemory_correct with soft-delete + version chain |
| #17956 | Subagents have no context from parent | prepareSubagentSpawn() shares relevant memories with child sessions |
🔍 Troubleshooting
Agent can't see Qmemory tools
Symptom: Plugin loads (logs show "Qmemory plugin loaded") but agent lists only core tools (read, write, exec, etc.) — no qmemory_* tools.
Cause: OpenClaw's tools.profile: "coding" has a hardcoded allowlist of core tools. Plugin tools are filtered out unless explicitly allowed.
Fix:
openclaw config set tools.alsoAllow '["group:plugins"]'
openclaw gateway restartThis adds ALL plugin tools (not just Qmemory) to the agent's available tools. You only need to do this once.
SurrealDB not running
Symptom: Plugin loads but no memories are saved or recalled. Logs show "Cannot connect to SurrealDB" or agent runs in "degraded mode."
Fix:
# Start SurrealDB
surreal start --user root --pass root file:~/.qmemory/data.db
# Verify
qmemory status
# (Optional) Auto-start on macOS login
bash scripts/setup-surrealdb-launchagent.shSurrealDB must be running before the OpenClaw gateway starts. If you start SurrealDB after, restart the gateway:
openclaw gateway restart
Context engine not active
Symptom: Tools work but memories are not auto-injected into conversations. No compaction. No cross-session recall.
Cause: The context engine slot is not set to Qmemory.
Fix:
openclaw config set plugins.slots.contextEngine "qmemory"
openclaw gateway restartWrong SurrealDB version
Symptom: Schema fails to apply, FULLTEXT index errors, Cannot execute CREATE statement errors, or type::record parse errors.
Cause: SurrealDB v2.x has incompatible syntax. Qmemory requires v3.0+.
Fix:
# Check version
surreal version
# If 2.x, upgrade:
# macOS
brew upgrade surrealdb/tap/surreal
# Linux
curl -sSf https://install.surrealdb.com | sh
# Verify
surreal version
# Must show: 3.0.0 or higherSchema not applied
Symptom: SurrealDB is running but queries fail with "table not found" errors.
Fix: The schema is auto-applied on the first session (bootstrap()). If you need to apply it manually:
qmemory schema
# or
npx tsx src/cli.ts schemaPlugin not loaded
Symptom: No "Qmemory plugin loaded" message in logs.
Fix:
# Check plugin status
openclaw plugins list | grep qmemory
# Ensure it's enabled
openclaw config set plugins.entries.qmemory.enabled true
# Ensure it's in the allowlist
openclaw config set plugins.allow '["qmemory"]'
# Restart
openclaw gateway restartTools registered but returning errors
Symptom: Agent sees qmemory_* tools but they fail with connection errors.
Fix: Check that SurrealDB is running and accessible:
qmemory status
# Should show: Qmemory: connectedIf it shows "disconnected", restart SurrealDB and the gateway.
Quick diagnostic checklist
# 1. Is SurrealDB running?
qmemory status
# 2. Is the plugin loaded?
openclaw plugins list | grep qmemory
# 3. Is the context engine set?
openclaw config get plugins.slots.contextEngine
# Should show: "qmemory"
# 4. Are plugin tools allowed?
openclaw config get tools.alsoAllow
# Should include: "group:plugins"
# 5. Check logs for errors
openclaw logs --follow | grep -i "qmemory\|surreal\|error"🛠️ Development
Setup
git clone https://github.com/QusaiiSaleem/qmemory.git
cd qmemory
npm install
npm run buildLocal development
# Start SurrealDB
surreal start --user root --pass root file:~/.qmemory/data.db
# Apply schema
npx tsx src/cli.ts schema
# Check status
npx tsx src/cli.ts status
# Run MCP server (stdio — for testing with Claude Code)
npx tsx src/cli.ts serve
# Run MCP server (HTTP — for testing with Claude.ai)
npx tsx src/cli.ts serve-http 3777
# Interactive MCP inspector (great for debugging tools)
npm run dev
# Link as OpenClaw plugin (dev mode — no npm publish needed)
openclaw plugins install -l /path/to/qmemory
openclaw config set plugins.slots.contextEngine "qmemory"
openclaw config set tools.alsoAllow '["group:plugins"]'
openclaw gateway restartTesting
npm test # Run tests once
npm run test:watch # Watch modeDebugging
# OpenClaw logs (filter for Qmemory)
openclaw logs --follow | grep qmemory
# Direct SurrealDB queries
surreal sql -e http://localhost:8000 -u root -p root \
--namespace qmemory --database mainProject structure
src/
├── core/ ← SHARED logic (used by all entry points)
│ ├── recall.ts ← 4-tier hybrid recall pipeline
│ ├── search.ts ← BM25 + vector + graph search
│ ├── save.ts ← Save with dedup pipeline
│ ├── correct.ts ← Fix or soft-delete memories
│ ├── link.ts ← Create dynamic `relates` edges
│ ├── extract.ts ← LLM-driven fact extraction
│ ├── dedup.ts ← LLM-driven dedup (ADD/UPDATE/NOOP)
│ ├── embeddings.ts ← Embedding generation + config resolution
│ └── migrate.ts ← Import old memory files
├── db/
│ ├── client.ts ← SurrealDB connection + parameterized queries
│ └── queries.ts ← Reusable query helpers
├── openclaw/ ← ENTRY 1: Context engine plugin
│ ├── index.ts ← Plugin registration (6 tools + engine + linker)
│ ├── engine.ts ← Full context engine (bootstrap/ingest/assemble/compact)
│ └── linker.ts ← Background services (link + reflect)
├── mcp/
│ └── server.ts ← ENTRY 2: FastMCP server (4 tools, stdio + HTTP)
├── cli.ts ← ENTRY 3: CLI (serve, serve-http, status, schema)
├── config.ts ← Types, constants, formatMemories()
└── ui/
├── graph-handler.ts ← HTTP handler for graph viewer + API
└── graph.html ← vis.js interactive graph (dark theme)Dependencies
Only 4 runtime dependencies — keeping it simple:
| Package | Why |
|---|---|
| surrealdb | Official SurrealDB JavaScript SDK |
| fastmcp | MCP server framework (stdio + HTTP transports) |
| @sinclair/typebox | OpenClaw tool parameter schemas |
| zod | MCP tool parameter schemas |
⛓️ Relationship Chains — The Power Feature
The relates edge connects ANY node to ANY node. The agent builds chains that map real-world workflows:
User says: "Ahmed needs the Q2 report by Thursday. Send it to his work email."
Agent builds this chain:
Session (Topic 9)
│ has_message
▼
Message: "Ahmed needs the Q2 report by Thursday"
│ extracted_from
▼
Memory: "Ahmed needs Q2 report by Thursday" (salience: 0.8)
│ relates (assigned_to)
▼
Entity: "Ahmed" (type: person)
│ has_identity
├──▶ Contact: WhatsApp 966501234567
├──▶ Contact: gmail [email protected] ← send here
└──▶ Contact: Smartsheet user:12345More Real-World Chains
Decision Chain:
memory:"Budget approved" → approved_by → entity:manager
→ communicated_via → entity:approval_email
→ depends_on → memory:"Board meeting March 5"
Incident Chain:
memory:"Prod down 2 hours" → caused_by → memory:"Friday deploy broke auth"
→ affected → entity:railway_prod
→ resolved_by → entity:on_call_engineer
Task Chain:
entity:hire_developer → depends_on → memory:"Budget approved"
→ blocks → memory:"Need 2 devs for Q3"
→ assigned_to → entity:hr_managerPeople with Multiple Identities
qmemory_person({
name: "Ahmed",
aliases: ["أحمد"],
contacts: [
{ source: "whatsapp", id: "966501234567" },
{ source: "gmail", id: "[email protected]" },
{ source: "telegram", id: "ahmed_k" },
{ source: "smartsheet", id: "user:12345" }
]
})Later: qmemory_person({ name: "Ahmed", action: "find" }) returns the person + ALL contacts + ALL linked memories from any session.
Common Relationship Types
| Type | Use For |
|------|---------|
| assigned_to | Task → Person |
| approved_by | Decision → Person |
| caused_by | Incident → Root cause |
| depends_on | Task → Prerequisite |
| blocks | Blocker → Blocked item |
| has_identity | Person → Contact (auto) |
| communicated_via | Fact → Communication channel |
| managed_by | Project → Manager |
| monitors | Session → System |
| solved_using | Problem → Tool/Skill |
These are NOT fixed — use any word that describes the relationship.
📚 Wiki / Reference Files
| File | What It Is | When to Read | |---|---|---| | SKILL.md | Agent guide — teaches AI agents when/how to save, search, link, correct, and manage memory | Setting up a new agent, customizing memory behavior | | CLAUDE.md | Developer reference — architecture, key patterns, gotchas, quick commands | Contributing to Qmemory, debugging, understanding the codebase | | schema/qmemory.surql | Full SurrealDB schema — 7 tables, 5 edge types, indexes, analyzers | Understanding the data model, writing custom queries | | openclaw.plugin.json | Plugin manifest — all config options with types, defaults, and UI hints | Configuring the OpenClaw plugin, understanding available settings | | package.json | npm package — scripts, dependencies, entry points | Installing, building, understanding the project structure |
📄 License
MIT — use it however you want.
🙏 Credits
Created by Qusai Abu Shanab.
Powered by SurrealDB — the multi-model database that makes graph + document + vector queries feel natural.
Built for the OpenClaw community — the open-source AI agent platform.
MCP integration via FastMCP — the simplest way to build MCP servers.
