Skip to main content

Исследование долговременной памяти в OpenClaw

Research date: 2026-03-25 Purpose: Understand openclaw memory architecture for porting to Veles

Table of Contents

  1. Architecture Overview
  2. Two Plugin Systems
  3. Core Memory Module (src/memory/)
  4. Memory Tools Exposed to Agent
  5. System Prompt Integration
  6. Indexing & Sync Pipeline
  7. Search Flow (Hybrid BM25 + Vector)
  8. Conversation History Persistence
  9. Session Memory Hook (Dated Files)
  10. Pre-Compaction Memory Flush
  11. LanceDB Plugin (Independent Long-Term Memory)
  12. Configuration Reference
  13. Data Flow Summary
  14. Key Files Map
  15. Porting Considerations for Veles

1. Architecture Overview

OpenClaw’s memory is a multi-layered system with three independent mechanisms that work together:
Key principle: Memory is plain Markdown files on disk. The vector index is a derived cache — if you delete it, it rebuilds from the files.

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_search and memory_get tools
  • Uses SQLite + FTS5 + sqlite-vec for indexing and search
  • Indexes MEMORY.md + memory/*.md files
  • 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_forget tools
  • 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 at src/memory/. This is the engine behind memory-core.

Class hierarchy

Storage: SQLite

Database location: <stateDir>/memory/<agentId>.sqlite Tables:

Memory file discovery

The system indexes these files:
  1. MEMORY.md or memory.md in workspace root
  2. All *.md files recursively under memory/ directory
  3. Additional paths from memorySearch.extraPaths config
  4. 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: Auto-selection (provider: "auto"):
  1. Try local model if modelPath configured
  2. Try remote in order: openai → gemini → voyage → mistral
  3. 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, from line, lines count
  • 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 via api.registerMemoryPromptSection(buildPromptSection). This injects a ## Memory Recall section into the system prompt that:
  • Tells the agent about memory_search and memory_get tools
  • 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
Key file: 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)

  1. Memory files sync: List all MEMORY.md + memory/*.md, compare hashes against DB, re-index changed files
  2. Session files sync: List session JSONL files, extract user/assistant messages, build text entries, index them
  3. Atomic reindex: If embedding model/provider changes, wipe all chunks and re-index everything
  4. Embedding batching: Chunks grouped by byte size (max 8000 tokens/batch), sent to provider in parallel (concurrency: 4)
  5. Embedding cache: SHA-256 content hash as cache key — unchanged content is never re-embedded

Sync triggers


7. Search Flow

MemoryIndexManager.search():

FTS-only mode (no embedding provider)

  1. Extract keywords from query
  2. Run BM25 full-text search via FTS5 MATCH
  3. Merge and deduplicate

Hybrid mode (default)

  1. Keyword search: FTS5 BM25 on chunks_fts table
  2. Vector search: Embed query → vec_distance_cosine() on chunks_vec (or in-memory cosine fallback)
  3. Merge: score = 0.7 * vectorScore + 0.3 * textScore
  4. Optional temporal decay: Exponential, configurable half-life (default 30 days, disabled by default)
  5. Optional MMR re-ranking: Jaccard similarity diversity, lambda 0.7 (disabled by default)
  6. Filter: minScore ≥ 0.35
  7. 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 sessions
  • agent:<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: /new or /reset commands
  • Rotation: when transcript exceeds rotateBytes
  • Pruning: session.maintenance.pruneAfter for old sessions

Session transcripts as searchable memory (experimental)

When memorySearch.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 creates memory/YYYY-MM-DD-slug.md files.

Files

  • src/hooks/bundled/session-memory/handler.ts
  • src/hooks/bundled/session-memory/transcript.ts

Trigger

Fires on /new or /reset commands (session end/rotation).

Process

  1. Find the previous session’s transcript JSONL file
  2. Read the last N messages (default: 15, configurable)
  3. Send messages to LLM to generate a descriptive filename slug (e.g., “api-design”, “vendor-pitch”)
  4. Write memory/YYYY-MM-DD-slug.md with:

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.ts
  • src/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

  1. Read token usage from session transcript
  2. Project next input size
  3. Run a dedicated LLM agent turn with system prompt instructing it to write durable memories
  4. Target file: memory/YYYY-MM-DD.md (uses user’s timezone)
  5. Instructions to LLM: append only, never overwrite, never edit root memory files

Default prompt

“Pre-compaction memory flush. Store durable memories only in memory/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)

  1. Embed the incoming user message
  2. Search top 3 memories (minScore: 0.3) from LanceDB
  3. Inject as <relevant-memories> XML block into context

Auto-capture (agent_end hook)

  1. Scan user messages for capturable content
  2. Rule-based trigger detection:
    • Preference patterns (“I prefer…”, “I like…”)
    • Contact info
    • Decision language
    • Explicit “remember” instructions
  3. Category detection: preference, fact, decision, entity, other
  4. Duplicate check: skip if >0.95 cosine similarity with existing memory
  5. 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 content
  • looksLikePromptInjection() 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)

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 plugin
  • extensions/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 operations
  • src/memory/manager-embedding-ops.ts — embedding batch operations
  • src/memory/manager-search.ts — vector + keyword search implementation
  • src/memory/internal.ts — file listing, chunking, cosine similarity
  • src/memory/hybrid.ts — hybrid search merge logic
  • src/memory/mmr.ts — Maximal Marginal Relevance re-ranking
  • src/memory/temporal-decay.ts — recency-aware scoring
  • src/memory/memory-schema.ts — SQLite schema definitions
  • src/memory/session-files.ts — session transcript indexing
  • src/memory/search-manager.ts — factory with QMD fallback
  • src/memory/prompt-section.ts — system prompt injection

Embedding providers

  • src/memory/embeddings.ts — factory + local provider
  • src/memory/embeddings-openai.ts
  • src/memory/embeddings-gemini.ts
  • src/memory/embeddings-voyage.ts
  • src/memory/embeddings-mistral.ts
  • src/memory/embeddings-ollama.ts

Session memory hook

  • src/hooks/bundled/session-memory/handler.ts — creates dated memory files
  • src/hooks/bundled/session-memory/transcript.ts — reads JSONL transcripts

Pre-compaction flush

  • src/auto-reply/reply/memory-flush.ts — flush trigger logic
  • src/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 type
  • src/config/types.memory.ts — MemoryConfig, QMD types

Documentation

  • docs/concepts/memory.md — conceptual overview
  • docs/reference/memory-config.md — full config reference (712 lines)
  • docs/cli/memory.md — CLI reference

15. Porting Considerations for Veles

What’s worth porting (ranked by impact vs effort)

High value, moderate effort:
  1. 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.
  2. Pre-compaction memory flush — before context compaction, run a dedicated LLM turn to extract durable memories. Needs: token counting, compaction event hook, LLM call.
  3. memory_search tool — let the agent search its own memory files. Needs: FTS at minimum, vector search is a bonus.
Medium value, lower effort: 4. Memory file convention — 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_get to 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)