CodeDocs Vault

Memory System

How the agent remembers things across conversations.

Overview

OpenClaw's memory system gives the AI agent persistent, searchable knowledge that survives across sessions. The source of truth is always plain Markdown files in the workspace. These files are indexed, chunked, embedded, and searchable via hybrid vector + keyword search.

Workspace Files                    Index Layer                   Agent Interface
┌──────────────┐                ┌──────────────┐              ┌──────────────┐
│ MEMORY.md    │───────────────►│ SQLite DB    │◄────────────►│ memory_search│
│ memory/*.md  │  chunk + embed │ - files      │  hybrid      │ memory_get   │
│ sessions/*.  │───────────────►│ - chunks     │  search      │              │
│   jsonl      │                │ - embeddings │              │ (tools given │
└──────────────┘                │ - FTS5 index │              │  to the LLM) │
                                └──────────────┘              └──────────────┘

File Convention

MEMORY.md (Workspace Root)

Curated long-term memory. The agent reads and writes this file for durable facts, decisions, and preferences.

memory/*.md (Daily and Evergreen Files)

Pattern Purpose Temporal Decay
memory/YYYY-MM-DD.md Daily append-only logs (running context, notes) Yes — exponential decay with 30-day half-life
memory/topics.md Evergreen knowledge files (no date in name) No decay

Session Transcripts (~/.openclaw/agents/<agentId>/sessions/*.jsonl)

Past conversations are also indexed as a secondary memory source. The system converts JSONL message transcripts into searchable text with line-number mappings back to the original format.

Data Model

SQLite Schema (src/memory/memory-schema.ts)

-- Tracked files
CREATE TABLE files (
  path TEXT PRIMARY KEY,
  source TEXT,       -- "memory" or "sessions"
  hash TEXT,
  mtime INTEGER,
  size INTEGER
);
 
-- Text chunks with embeddings
CREATE TABLE chunks (
  id TEXT PRIMARY KEY,
  path TEXT,
  source TEXT,
  startLine INTEGER,
  endLine INTEGER,
  hash TEXT,
  model TEXT,
  text TEXT,
  embedding TEXT,    -- JSON array of floats
  updated_at INTEGER
);
 
-- Embedding cache (cross-session reuse)
CREATE TABLE embedding_cache (
  provider TEXT,
  model TEXT,
  key TEXT,
  hash TEXT,
  embedding TEXT,
  dimensions INTEGER
);
 
-- Full-text search (when available)
CREATE VIRTUAL TABLE chunks_fts USING fts5(text, content=chunks);

Storage location: ~/.openclaw/memory/<agentId>.sqlite

Chunking

Markdown files are split into overlapping chunks for embedding:

Source: chunkMarkdown() in src/memory/internal.ts

Embedding Providers

Auto-selection priority (provider: "auto"):

Priority Provider Model Dimensions Condition
1 Local embeddinggemma-300m-qat-q8_0.gguf Varies memorySearch.local.modelPath configured
2 OpenAI text-embedding-3-small 1536 API key found
3 Gemini gemini-embedding-001 Varies API key found
4 Voyage voyage-4-large Varies API key found

OpenAI also supports text-embedding-3-large (3072 dimensions) via config.

Batch Processing

Source: src/memory/manager-embedding-ops.ts

Hybrid Search (src/memory/hybrid.ts)

Combines two scoring signals:

final_score = vectorWeight × vectorScore + textWeight × textScore
Component Method Default Weight
Vector Cosine similarity (L2-normalized embeddings via sqlite-vec) 0.7
Keyword FTS5 with BM25, normalized via 1 / (1 + rank) 0.3

When embeddings are unavailable, falls back to keyword-only search with query expansion (stop-word removal, Chinese bigram tokenization).

Temporal Decay (src/memory/temporal-decay.ts)

Dated memory files (memory/YYYY-MM-DD.md) decay exponentially:

decayed_score = score × exp(-λ × age_in_days)
where λ = ln(2) / half_life_days  (default half_life = 30 days)

Evergreen files (MEMORY.md, undated files) are not affected.

Maximal Marginal Relevance (MMR) — Optional

Diversity-aware re-ranking to avoid returning near-duplicate results:

MMR_score = λ × relevance - (1 - λ) × max_similarity_to_already_selected

Default: disabled. When enabled, λ = 0.7 (biased toward relevance over diversity). Uses Jaccard token similarity for efficiency.

Result Limits

Parameter Default
Max results 6
Min score threshold 0.35
Max snippet length 700 chars
Max total injected chars (QMD) 4000

Agent Tools

Parameters: query (string), maxResults? (number), minScore? (number)
Returns: [{ path, startLine, endLine, score, snippet, source, citation? }]

Flow:

  1. Resolve config and agent ID from session key
  2. Get MemorySearchManager (builtin SQLite or QMD backend)
  3. Hybrid search with citations
  4. Clamp results to injection budget
  5. Return with provider/model metadata

memory_get

Parameters: path (string), from? (line number), lines? (count)
Returns: { path, text }

Used after memory_search to pull specific line ranges efficiently. This two-step pattern (search → get) keeps search results compact while allowing the agent to read full context when needed.

System Prompt Integration

From src/agents/system-prompt.ts:

## Memory Recall
Before answering anything about prior work, decisions, dates, people,
preferences, or todos: run memory_search on MEMORY.md + memory/*.md;
then use memory_get to pull only the needed lines.
If low confidence after search, say you checked.

The memory section only appears when:

Citations

Mode Behavior
"on" Always include Source: <path>#L<start>-L<end>
"off" Never mention file paths or line numbers
"auto" Citations in direct DMs; suppressed in groups/channels

QMD Backend (Optional)

QMD (Qualified Memory Database) is a local-first vector search sidecar:

Search Modes

Mode Description
search (default) Fast keyword + vector hybrid
vsearch Vector-only
query Full query expansion + reranking (slower, best recall)

Fallback

FallbackMemoryManager wraps both backends:

LanceDB Extension (Alternative)

extensions/memory-lancedb/ provides an alternative vector backend using LanceDB:

type MemoryEntry = {
  id: string;
  text: string;
  vector: number[];
  importance: number;
  category: "preference" | "fact" | "decision" | "entity" | "other";
  createdAt: number;
};

Uses OpenAI embeddings (text-embedding-3-small/large). Lazy-loads to keep startup fast.

Sync & Indexing

File Watching

Indexing Pipeline

File change detected
  → Debounce (1500ms)
  → Hash check (skip if unchanged)
  → chunkMarkdown() — split into overlapping chunks with line numbers
  → Load embedding cache
  → Request embeddings from provider (batch + retry)
  → Store in cache + DB chunks table
  → Update files table (hash, mtime, size)

Session Indexing

Session transcripts (JSONL) are indexed only when changes exceed thresholds:

This avoids re-indexing after every single message.

Architecture Diagram

┌─────────────────────────────────────────────────────────────┐
│                    Memory System                             │
│                                                             │
│  Sources                    Indexing              Search     │
│  ┌──────────┐              ┌──────────┐        ┌─────────┐ │
│  │MEMORY.md │──┐           │Chunk     │        │ Hybrid  │ │
│  │memory/*  │──┤  watch +  │Markdown  │        │ Search  │ │
│  │sessions/ │──┤  hash     │(400 tok  │        │         │ │
│  │  *.jsonl │──┘  detect   │ overlap) │        │ Vector  │ │
│  └──────────┘      │       └────┬─────┘        │ (0.7)   │ │
│                    │            │               │    +    │ │
│                    ▼            ▼               │Keyword  │ │
│              ┌──────────┐ ┌──────────┐         │ (0.3)   │ │
│              │ File     │ │ Embed    │         └────┬────┘ │
│              │ Watcher  │ │ Provider │              │      │
│              │(chokidar)│ │(OpenAI/  │              ▼      │
│              └──────────┘ │ Gemini/  │         ┌─────────┐ │
│                           │ Local)   │         │ Temporal│ │
│                           └────┬─────┘         │ Decay   │ │
│                                │               └────┬────┘ │
│                                ▼                    │      │
│                           ┌──────────┐              ▼      │
│                           │ SQLite   │         ┌─────────┐ │
│                           │ + FTS5   │◄────────│ MMR     │ │
│                           │ + vec    │         │(optional)│ │
│                           └──────────┘         └─────────┘ │
│                                                             │
│  Tools: memory_search (query → results)                     │
│         memory_get    (path + lines → text)                 │
└─────────────────────────────────────────────────────────────┘