OpenClaw Long-Term Memory System — Full Research
Research date: 2026-03-25 Purpose: Understand openclaw memory architecture for porting to nanobot
Table of Contents
- Architecture Overview
- Two Plugin Systems
- Core Memory Module (src/memory/)
- Memory Tools Exposed to Agent
- System Prompt Integration
- Indexing & Sync Pipeline
- Search Flow (Hybrid BM25 + Vector)
- Conversation History Persistence
- Session Memory Hook (Dated Files)
- Pre-Compaction Memory Flush
- LanceDB Plugin (Independent Long-Term Memory)
- Configuration Reference
- Data Flow Summary
- Key Files Map
- Porting Considerations for Nanobot
1. Architecture Overview
OpenClaw’s memory is a multi-layered system with three independent mechanisms that work together:2. Two Plugin Systems
Memory is provided via a plugin slot (plugins.slots.memory). Two plugins exist:
2a. memory-core (default, built-in)
- Provides
memory_searchandmemory_gettools - Uses SQLite + FTS5 + sqlite-vec for indexing and search
- Indexes
MEMORY.md+memory/*.mdfiles - Hybrid BM25 + vector search
- Multiple embedding providers with auto-selection
- Entry point:
extensions/memory-core/index.ts
2b. memory-lancedb (optional, install-on-demand)
- Completely independent vector DB system using LanceDB
- Provides
memory_recall,memory_store,memory_forgettools - Auto-recall: injects relevant memories before agent starts
- Auto-capture: analyzes user messages after agent ends, stores important facts
- Uses OpenAI embeddings (
text-embedding-3-small) - DB stored at
~/.openclaw/memory/lancedb - Entry point:
extensions/memory-lancedb/index.ts
Plugin selection
3. Core Memory Module
Located atsrc/memory/. This is the engine behind memory-core.
Class hierarchy
Storage: SQLite
Database location:<stateDir>/memory/<agentId>.sqlite
Tables:
| Table | Purpose |
|---|---|
meta | Key-value metadata (model, provider, chunk config, vector dims) |
files | Indexed file records (path, source, hash, mtime, size) |
chunks | Text chunks with embeddings (id, path, source, start/end line, hash, text, embedding JSON) |
chunks_vec | Virtual table for sqlite-vec vector search (Float32Array blobs) |
chunks_fts | FTS5 full-text search virtual table |
embedding_cache | Provider/model/hash-keyed embedding cache |
Memory file discovery
The system indexes these files:MEMORY.mdormemory.mdin workspace root- All
*.mdfiles recursively undermemory/directory - Additional paths from
memorySearch.extraPathsconfig - Session JSONL files (if
experimental.sessionMemory: true)
Document chunking
chunkMarkdown() in internal.ts:
- Splits by lines, groups into ~400-token chunks with 80-token overlap
- Each chunk:
startLine,endLine,text,hash(SHA-256) - Token estimate:
characters / 4
Embedding providers
Factory:createEmbeddingProvider() in embeddings.ts. Supports:
| Provider | Default Model | File |
|---|---|---|
| OpenAI | text-embedding-3-small | embeddings-openai.ts |
| Gemini | gemini-embedding-001 | embeddings-gemini.ts |
| Voyage | voyage-4-large | embeddings-voyage.ts |
| Mistral | mistral-embed | embeddings-mistral.ts |
| Ollama | nomic-embed-text | embeddings-ollama.ts |
| Local | embeddinggemma-300m-qat-Q8_0.gguf | via node-llama-cpp |
provider: "auto"):
- Try local model if
modelPathconfigured - Try remote in order: openai → gemini → voyage → mistral
- If all fail (missing keys) → FTS-only mode (keyword search, no vectors)
4. Memory Tools Exposed to Agent
memory_search (from memory-core)
Semantic + keyword hybrid search over memory files.
- Agent calls it with a text query
- Returns matching chunks with file path, line numbers, score, text
- Searches
MEMORY.md,memory/*.md, optionally session transcripts
memory_get (from memory-core)
Safe snippet read from memory files.
- Parameters: file path,
fromline,linescount - Returns the requested lines from a memory file
- Used for targeted follow-up reads after search
memory_recall / memory_store / memory_forget (from memory-lancedb)
Only available if lancedb plugin is active instead of memory-core.
5. System Prompt Integration
The memory-core plugin registers a prompt section builder viaapi.registerMemoryPromptSection(buildPromptSection).
This injects a ## Memory Recall section into the system prompt that:
- Tells the agent about
memory_searchandmemory_gettools - Instructs the agent to search memory before answering recall-dependent questions
- Controls citation mode (
on/off/auto) for file paths and line numbers in replies - Only appears if the tools are in
availableTools
src/memory/prompt-section.ts — singleton pattern, only one memory plugin’s prompt builder can be active.
6. Indexing & Sync Pipeline
runSync() method (in manager-sync-ops.ts)
- Memory files sync: List all
MEMORY.md+memory/*.md, compare hashes against DB, re-index changed files - Session files sync: List session JSONL files, extract user/assistant messages, build text entries, index them
- Atomic reindex: If embedding model/provider changes, wipe all chunks and re-index everything
- Embedding batching: Chunks grouped by byte size (max 8000 tokens/batch), sent to provider in parallel (concurrency: 4)
- Embedding cache: SHA-256 content hash as cache key — unchanged content is never re-embedded
Sync triggers
| Trigger | When |
|---|---|
| File watcher | chokidar on memory/ dir and MEMORY.md, debounced |
| On search | If dirty flag is set (default: sync.onSearch: true) |
| Session start | Initial sync |
| Periodic interval | Configurable sync.intervalMinutes |
| Session delta | When accumulated session changes > 100KB or 50 messages |
| Post-compaction | Forced sync after context compaction |
7. Search Flow
MemoryIndexManager.search():
FTS-only mode (no embedding provider)
- Extract keywords from query
- Run BM25 full-text search via FTS5
MATCH - Merge and deduplicate
Hybrid mode (default)
- Keyword search: FTS5 BM25 on
chunks_ftstable - Vector search: Embed query →
vec_distance_cosine()onchunks_vec(or in-memory cosine fallback) - Merge:
score = 0.7 * vectorScore + 0.3 * textScore - Optional temporal decay: Exponential, configurable half-life (default 30 days, disabled by default)
- Optional MMR re-ranking: Jaccard similarity diversity, lambda 0.7 (disabled by default)
- Filter:
minScore ≥ 0.35 - Limit:
maxResults = 6
8. Conversation History Persistence
Session transcripts (JSONL)
Every conversation is stored as a JSONL file per session:- Location:
~/.openclaw/agents/<agentId>/sessions/<SessionId>.jsonl - Each line: JSON record with
type: "message", full message object (role, content, usage) - Session metadata:
~/.openclaw/agents/<agentId>/sessions/sessions.json
Session scoping
Sessions are scoped by key format:agent:<agentId>:<mainKey>— DM sessionsagent:<agentId>:<channel>:group:<id>— group sessions- DM scope modes:
main,per-peer,per-channel-peer,per-account-channel-peer
Session lifecycle
- Daily reset: at 4 AM local time
- Idle reset: after configurable idle timeout
- Manual reset:
/newor/resetcommands - Rotation: when transcript exceeds
rotateBytes - Pruning:
session.maintenance.pruneAfterfor old sessions
Session transcripts as searchable memory (experimental)
WhenmemorySearch.experimental.sessionMemory: true and sources: ["memory", "sessions"]:
- Session JSONL files are parsed, user/assistant messages extracted
- Content is chunked, embedded, and indexed alongside memory files
- Becomes searchable via
memory_search
9. Session Memory Hook (Dated Files)
This is the mechanism that createsmemory/YYYY-MM-DD-slug.md files.
Files
src/hooks/bundled/session-memory/handler.tssrc/hooks/bundled/session-memory/transcript.ts
Trigger
Fires on/new or /reset commands (session end/rotation).
Process
- Find the previous session’s transcript JSONL file
- Read the last N messages (default: 15, configurable)
- Send messages to LLM to generate a descriptive filename slug (e.g., “api-design”, “vendor-pitch”)
- Write
memory/YYYY-MM-DD-slug.mdwith:
Transcript reading (transcript.ts)
- Reads JSONL files, parses
type: "message"entries - Extracts user and assistant messages (skips
/commands) - Supports fallback to
.reset.rotated transcript files - Finds previous session files by session ID, topic variants, or most recent
10. Pre-Compaction Memory Flush
Automatically captures memories before the context window fills up and gets compacted.Files
src/auto-reply/reply/memory-flush.tssrc/auto-reply/reply/agent-runner-memory.ts
Trigger conditions (shouldRunMemoryFlush)
- Session tokens exceed
contextWindow - reserveTokensFloor - softThresholdTokens(default soft threshold: 4000 tokens) - OR transcript file size exceeds
forceFlushTranscriptBytes(default: 2MB) - Has not already flushed for the current compaction cycle
Execution
- Read token usage from session transcript
- Project next input size
- Run a dedicated LLM agent turn with system prompt instructing it to write durable memories
- Target file:
memory/YYYY-MM-DD.md(uses user’s timezone) - Instructions to LLM: append only, never overwrite, never edit root memory files
Default prompt
“Pre-compaction memory flush. Store durable memories only inmemory/YYYY-MM-DD.md. If nothing to store, reply with[silent].”
Configuration
11. LanceDB Plugin
An alternative/complementary long-term memory system.Auto-recall (before_agent_start hook)
- Embed the incoming user message
- Search top 3 memories (minScore: 0.3) from LanceDB
- Inject as
<relevant-memories>XML block into context
Auto-capture (agent_end hook)
- Scan user messages for capturable content
- Rule-based trigger detection:
- Preference patterns (“I prefer…”, “I like…”)
- Contact info
- Decision language
- Explicit “remember” instructions
- Category detection: preference, fact, decision, entity, other
- Duplicate check: skip if >0.95 cosine similarity with existing memory
- Store up to 3 memories per conversation
Safety
shouldCapture()filters: length 10-500 chars, skip injected memory context, skip system content, skip markdown-heavy output, skip emoji-heavy contentlooksLikePromptInjection()protection- HTML escaping in memory injection
Storage
- LanceDB table
"memories"with fields: id, text, vector, importance, category, createdAt - L2 distance converted to similarity:
1 / (1 + distance) - DB path:
~/.openclaw/memory/lancedb
12. Configuration Reference
Core memory search config (agents.defaults.memorySearch)
| Setting | Default | Description |
|---|---|---|
enabled | true | Enable memory search |
provider | "auto" | Embedding provider |
sources | ["memory"] | What to index ("memory", "sessions") |
extraPaths | [] | Additional markdown paths to index |
store.driver | "sqlite" | Storage backend |
store.vector.enabled | true | Enable sqlite-vec acceleration |
chunking.tokens | 400 | Chunk size in tokens |
chunking.overlap | 80 | Overlap between chunks |
query.maxResults | 6 | Max search results |
query.minScore | 0.35 | Min similarity threshold |
query.hybrid.enabled | true | Enable hybrid search |
query.hybrid.vectorWeight | 0.7 | Vector search weight |
query.hybrid.textWeight | 0.3 | Keyword search weight |
query.hybrid.mmr.enabled | false | MMR diversity re-ranking |
query.hybrid.temporalDecay.enabled | false | Recency-aware scoring |
cache.enabled | true | Embedding cache |
sync.onSearch | true | Sync before search |
sync.watch | true | File watcher |
fallback | "none" | Fallback provider |
Memory backend config (memory)
Plugin config
13. Data Flow Summary
Write paths (how memories get created)
Read paths (how memories get recalled)
Persistence across sessions
14. Key Files Map
Plugin entry points
extensions/memory-core/index.ts— default memory pluginextensions/memory-lancedb/index.ts— LanceDB alternative
Core memory engine
src/memory/manager.ts— MemoryIndexManager (877 lines, main class)src/memory/manager-sync-ops.ts— file/session sync operationssrc/memory/manager-embedding-ops.ts— embedding batch operationssrc/memory/manager-search.ts— vector + keyword search implementationsrc/memory/internal.ts— file listing, chunking, cosine similaritysrc/memory/hybrid.ts— hybrid search merge logicsrc/memory/mmr.ts— Maximal Marginal Relevance re-rankingsrc/memory/temporal-decay.ts— recency-aware scoringsrc/memory/memory-schema.ts— SQLite schema definitionssrc/memory/session-files.ts— session transcript indexingsrc/memory/search-manager.ts— factory with QMD fallbacksrc/memory/prompt-section.ts— system prompt injection
Embedding providers
src/memory/embeddings.ts— factory + local providersrc/memory/embeddings-openai.tssrc/memory/embeddings-gemini.tssrc/memory/embeddings-voyage.tssrc/memory/embeddings-mistral.tssrc/memory/embeddings-ollama.ts
Session memory hook
src/hooks/bundled/session-memory/handler.ts— creates dated memory filessrc/hooks/bundled/session-memory/transcript.ts— reads JSONL transcripts
Pre-compaction flush
src/auto-reply/reply/memory-flush.ts— flush trigger logicsrc/auto-reply/reply/agent-runner-memory.ts— flush execution
Configuration
src/config/zod-schema.agent-runtime.ts— MemorySearchSchema (lines 587-734)src/config/types.tools.ts— MemorySearchConfig typesrc/config/types.memory.ts— MemoryConfig, QMD types
Documentation
docs/concepts/memory.md— conceptual overviewdocs/reference/memory-config.md— full config reference (712 lines)docs/cli/memory.md— CLI reference
15. Porting Considerations for Nanobot
What’s worth porting (ranked by impact vs effort)
High value, moderate effort:- Dated memory files (
memory/YYYY-MM-DD-slug.md) — session end hook that summarizes conversation into a dated file. Needs: session end event, LLM call for slug generation, file write. - Pre-compaction memory flush — before context compaction, run a dedicated LLM turn to extract durable memories. Needs: token counting, compaction event hook, LLM call.
memory_searchtool — let the agent search its own memory files. Needs: FTS at minimum, vector search is a bonus.
MEMORY.md as curated long-term + memory/*.md as daily logs. Zero code needed, just a convention.
5. System prompt memory section — inject recall instructions into system prompt when memory tools are available.
Nice to have, higher effort:
6. Hybrid BM25 + vector search — requires SQLite FTS5 + embedding provider.
7. Auto-recall/auto-capture (LanceDB style) — requires lifecycle hooks + embedding pipeline.
Minimal integration points needed
- Session end event → write dated memory file
- Pre-compaction event → run memory flush LLM turn
- Tool registration → add
memory_search/memory_getto agent - System prompt builder → add memory recall instructions
- File watcher (optional) → re-index on memory file changes
What to skip
- QMD backend (experimental, complex)
- Multimodal memory (niche)
- sqlite-vec (FTS-only is a good starting point)
- LanceDB plugin (separate concern, memory-core is sufficient)
