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
- 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
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).
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.
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
┌─────────────────────────────────────────────────────────────────┐
│ 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) │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
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.
- 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
- 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
- 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
- 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
- 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)
- 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)
- 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
| 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
- Node.js 22+ —
node --version - SurrealDB 3.0+ — Install SurrealDB
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: 0
Important: 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).
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.
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/mcp
Step 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.
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 |
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 |
Qmemory exposes 6 tools in OpenClaw mode and 4 tools in MCP mode.
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 production
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).
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.
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)
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
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.
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:
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.
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" })
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" })
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" })
| 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) |
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
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.
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 │
│ │
└─────────────────────────────────────────────────────────────┘
- 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
Already have memory files from OpenClaw? Qmemory can import them.
# 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 fileqmemory_import({ file_path: "/path/to/MEMORY.md" })
┌─────────────────────────────────────────────────────┐
│ 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.
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. │
│ │
└────────────────────────────────────────────────────────────────────┘
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." │
└─────────────────────────────────────────────────────────────────┘
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 |
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.
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
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 restartSymptom: 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 higherSymptom: 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 schemaSymptom: 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 restartSymptom: 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.
# 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"git clone https://github.com/QusaiiSaleem/qmemory.git
cd qmemory
npm install
npm run build# 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 restartnpm test # Run tests once
npm run test:watch # Watch mode# 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 mainsrc/
├── 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)
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 |
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 ahmed@company.com ← send here
└──▶ Contact: Smartsheet user:12345
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_manager
qmemory_person({
name: "Ahmed",
aliases: ["أحمد"],
contacts: [
{ source: "whatsapp", id: "966501234567" },
{ source: "gmail", id: "ahmed@company.com" },
{ 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.
| 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.
| 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 |
MIT — use it however you want.
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.
"The best memory is the one that connects, not just stores."