🏠 Home ← Docs Index
Open Beta 📖 Interactive Reader Raw .md llms.txt

Memory Engine Architecture

The Memory Engine is the core component of GoClaw responsible for persistent long-term knowledge, session-based context management, and semantic retrieval. It provides a hybrid search system that blends traditional keyword search with vector-based semantic retrieval.

This page covers the engine's internals. Memory scopes and who may write them, what goes into the prompt, the search settings and the memory lifecycle are described in Memory & RAG.

System Components

1. Multi-Tiered Storage

The memory system uses a combination of structured and unstructured storage:

2. Hybrid Search Engine (RRF)

The engine uses Reciprocal Rank Fusion (RRF) to combine results from two distinct search algorithms:

  1. Vector Search (Semantic): Uses AI-generated embeddings (OpenAI, Gemini, or Ollama) to find conceptually similar content. It calculates Cosine Similarity between query and chunk vectors.
  2. Keyword Search (BM25-like): A pure Go implementation of keyword-based scoring with IDF (Inverse Document Frequency) and tokenization.

The final score for each chunk is calculated as: RRF Score = 1 / (60 + rank_vector) + 1 / (60 + rank_keyword).

Rank Enhancement Logic

To ensure the most relevant and "fresh" information surfaces:

3. Chunking Strategy

4. Summarization Pipeline (Session Summarizer)

The engine automatically handles context window limitations through the session summarizer (session_summarizer.go), which runs after a turn:

  1. Trigger: When the session's history exceeds memory.session_memory.max_tokens (default 40,000) or, with that set to 0, memory.session_memory.messages (default 40). While the memory flush is on, the history kept is also held to half its threshold (sessionTokenLimit): a turn starts with that history, and one that started past the threshold would be flushed at its first call and answered without any of it. The limit is computed over the whole history, so a session however far over it is back within it after one summarize.
  2. Summarization: An LLM (typically Gemini) generates a summary of at most 500 characters of the oldest part of the conversation.
  3. Compaction: The session history is trimmed to the newest messages within 60% of the limit (historyTrimShare), and the summary is carried into later turns as PREVIOUS CONTEXT. The turns after add to the history at its end until it passes the limit again, so the start of the conversation, from which every provider's prompt cache reads, holds for those turns; trimmed to the limit itself, it moved after every turn, and every turn ran a summarize. Each trim adds a paragraph to the summary; it keeps its newest 4,000 characters (session.MaxSummaryRunes), so it stays within the memory budget of the prompt.
  4. Archival: The full text of the trimmed messages is saved to the session archive (memory/archive/) for later retrieval. When every trimmed message was the current user's, the summary is also saved to their personal memory; a shared thread's summary is not, since it is of what everyone asked.
  5. Fact extraction: Each person in the trimmed messages is read on their own lines, under their own role (see Memory Formation Policy).

Memory Flush (memory_flush.go) is separate and works inside one request: when the messages of a single agent loop exceed memory.auto_flush_tokens (default 80,000), the older messages are handed to the model to extract the facts worth keeping, which are saved to the agent's daily memory file, and the loop goes on with the system prompt, the current user message and the last 6 messages. The model is shown each turn labelled Person, Assistant or Tool, and keeps what a person said and what a tool returned; the agent's own replies can be wrong and are not facts, so one is left out or, when a person relied on it, saved behind Unverified, the agent's own earlier answer:. Without a model, the flush writes what people said and what tools returned, never the agent's replies.

Both limits count tokens the same way (estimateTextTokens): a digit or a non-Latin letter is one token, other text four characters a token. Counting bytes undercounted Cyrillic (two bytes a letter, about one token) and figures (Gemini spends a token on each digit) by about half.

5. Memory Job Queue & Background Processing

Heavy memory operations are decoupled from the main execution thread via a persistent Job Queue:

  1. Serialization: Tasks like fact extraction, belief revision and knowledge graph synchronization are queued as Job records in the state database.
  2. MemoryWorker: A dedicated background worker (internal/agent/memory_worker.go) processes these jobs sequentially.
  3. Stability: Sequential processing prevents SQLite "database is locked" errors and ensures LLM API rate limits are respected.

6. Auto-Learning & Knowledge Graph

Implementation Details

Developer Notes