Memory

Das OpenClaw Memory besteht aus einfachen Markdown-Dateien im Agent Workspace. Diese Dateien sind die einzige Wahrheit — das Modell „erinnert” sich nur an das, was auf die Festplatte geschrieben wird.

Memory-Suchtools werden vom aktiven Memory-Plugin bereitgestellt (Standard: memory-core). Du kannst Memory-Plugins mit plugins.slots.memory = "none" deaktivieren.

Memory-Dateien (Markdown)

Das Standard-Workspace-Layout verwendet zwei Memory-Ebenen:

  • memory/YYYY-MM-DD.md
    • Tägliches Log (nur anhängen).
    • Liest heute + gestern beim Session-Start.
  • MEMORY.md (optional)
    • Kuratiertes Langzeitgedächtnis.
    • Wird nur in der privaten Hauptsession geladen (nie in Gruppenkontext).

Diese Dateien liegen im Workspace (agents.defaults.workspace, Standard: ~/.openclaw/workspace). Siehe Agent Workspace für das vollständige Layout.

Wann Memory schreiben

  • Entscheidungen, Präferenzen und dauerhafte Fakten gehören in MEMORY.md.
  • Tägliche Notizen und laufender Kontext gehören in memory/YYYY-MM-DD.md.
  • Wenn jemand sagt „merk dir das”, schreib es auf (nicht im RAM behalten).
  • Dieser Bereich entwickelt sich noch. Es hilft, das Modell daran zu erinnern, Erinnerungen zu speichern — es weiß dann, was zu tun ist.
  • Wenn du willst, dass etwas bleibt, bitte den Bot, es ins Memory zu schreiben.

Automatischer Memory-Flush (Pre-Compaction Ping)

Wenn eine Session kurz vor der Auto-Compaction steht, löst OpenClaw einen stillen, agentischen Turn aus, der das Modell daran erinnert, dauerhafte Erinnerungen zu speichern, bevor der Context komprimiert wird. Die Standard-Prompts sagen explizit, dass das Modell antworten darf, aber normalerweise ist NO_REPLY die richtige Antwort, sodass der Nutzer diesen Turn nie sieht.

Das wird über agents.defaults.compaction.memoryFlush gesteuert:

{
  agents: {
    defaults: {
      compaction: {
        reserveTokensFloor: 20000,
        memoryFlush: {
          enabled: true,
          softThresholdTokens: 4000,
          systemPrompt: "Session nearing compaction. Store durable memories now.",
          prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store.",
        },
      },
    },
  },
}

Details:

  • Soft Threshold: Der Flush wird ausgelöst, wenn die geschätzte Token-Anzahl der Session contextWindow - reserveTokensFloor - softThresholdTokens überschreitet.
  • Standardmäßig still: Prompts enthalten NO_REPLY, sodass nichts ausgegeben wird.
  • Zwei Prompts: Ein User-Prompt plus ein System-Prompt fügen die Erinnerung hinzu.
  • Ein Flush pro Compaction-Zyklus (wird in sessions.json getrackt).
  • Workspace muss beschreibbar sein: Wenn die Session sandboxed mit workspaceAccess: "ro" oder "none" läuft, wird der Flush übersprungen.

Für den vollständigen Compaction-Lebenszyklus siehe Session-Management + Compaction.

Vektor-Memory-Suche

OpenClaw kann einen kleinen Vektor-Index über MEMORY.md und memory/*.md (plus zusätzliche Verzeichnisse oder Dateien, die du hinzufügst) erstellen, sodass semantische Abfragen verwandte Notizen finden können, auch wenn die Formulierung unterschiedlich ist.

Standardeinstellungen:

  • Standardmäßig aktiviert.
  • Überwacht Memory-Dateien auf Änderungen (mit Debounce).
  • Verwendet standardmäßig Remote-Embeddings. Wenn memorySearch.provider nicht gesetzt ist, wählt OpenClaw automatisch:
    1. local, wenn ein memorySearch.local.modelPath konfiguriert ist und die Datei existiert.
    2. openai, wenn ein OpenAI-Key aufgelöst werden kann.
    3. gemini, wenn ein Gemini-Key aufgelöst werden kann.
    4. Andernfalls bleibt die Memory-Suche deaktiviert, bis sie konfiguriert wird.
  • Der lokale Modus verwendet node-llama-cpp und erfordert möglicherweise pnpm approve-builds.
  • Verwendet sqlite-vec (wenn verfügbar), um die Vektorsuche in SQLite zu beschleunigen.

Remote-Embeddings erfordern einen API-Key für den Embedding-Provider. OpenClaw löst Keys aus Auth-Profilen, models.providers.*.apiKey oder Umgebungsvariablen auf. Codex OAuth deckt nur Chat/Completions ab und erfüllt nicht die Anforderungen für Embeddings bei der Memory-Suche. Für Gemini verwende GEMINI_API_KEY oder models.providers.google.apiKey. Bei einem benutzerdefinierten OpenAI-kompatiblen Endpoint setze memorySearch.remote.apiKey (und optional memorySearch.remote.headers).

Zusätzliche Memory-Pfade

Wenn du Markdown-Dateien außerhalb des Standard-Workspace-Layouts indexieren willst, füge explizite Pfade hinzu:

agents: {
  defaults: {
    memorySearch: {
      extraPaths: ["../team-docs", "/srv/shared-notes/overview.md"]
    }
  }
}

Hinweise:

  • Pfade können absolut oder workspace-relativ sein.
  • Verzeichnisse werden rekursiv nach .md-Dateien durchsucht.
  • Nur Markdown-Dateien werden indexiert.
  • Symlinks werden ignoriert (Dateien oder Verzeichnisse).

Gemini Embeddings (nativ)

Setze den Provider auf gemini, um die Gemini Embeddings API direkt zu verwenden:

agents: {
  defaults: {
    memorySearch: {
      provider: "gemini",
      model: "gemini-embedding-001",
      remote: {
        apiKey: "YOUR_GEMINI_API_KEY"
      }
    }
  }
}

Hinweise:

  • remote.baseUrl ist optional (Standard ist die Gemini API Base-URL).
  • Mit remote.headers kannst du bei Bedarf zusätzliche Header hinzufügen.
  • Standard-Modell: gemini-embedding-001.

Wenn du einen benutzerdefinierten OpenAI-kompatiblen Endpoint (OpenRouter, vLLM oder einen Proxy) verwenden willst, kannst du die remote-Konfiguration mit dem OpenAI-Provider nutzen:

agents: {
  defaults: {
    memorySearch: {
      provider: "openai",
      model: "text-embedding-3-small",
      remote: {
        baseUrl: "https://api.example.com/v1/",
        apiKey: "YOUR_OPENAI_COMPAT_API_KEY",
        headers: { "X-Custom-Header": "value" }
      }
    }
  }
}

Wenn du keinen API-Key setzen willst, verwende memorySearch.provider = "local" oder setze memorySearch.fallback = "none".

Fallbacks:

  • memorySearch.fallback kann openai, gemini, local oder none sein.
  • Der Fallback-Provider wird nur verwendet, wenn der primäre Embedding-Provider fehlschlägt.

Batch-Indexierung (OpenAI + Gemini):

  • Standardmäßig für OpenAI- und Gemini-Embeddings aktiviert. Setze agents.defaults.memorySearch.remote.batch.enabled = false zum Deaktivieren.
  • Das Standardverhalten wartet auf den Batch-Abschluss; passe remote.batch.wait, remote.batch.pollIntervalMs und remote.batch.timeoutMinutes bei Bedarf an.
  • Setze remote.batch.concurrency, um zu steuern, wie viele Batch-Jobs parallel eingereicht werden (Standard: 2).
  • Der Batch-Modus gilt, wenn memorySearch.provider = "openai" oder "gemini" ist und verwendet den entsprechenden API-Key.
  • Gemini-Batch-Jobs verwenden den asynchronen Embeddings-Batch-Endpoint und erfordern die Verfügbarkeit der Gemini Batch API.

Warum OpenAI Batch schnell + günstig ist:

  • Für große Backfills ist OpenAI typischerweise die schnellste Option, die wir unterstützen, weil wir viele Embedding-Anfragen in einem einzigen Batch-Job einreichen und OpenAI sie asynchron verarbeiten lassen können.
  • OpenAI bietet vergünstigte Preise für Batch-API-Workloads, sodass große Indexierungsläufe normalerweise günstiger sind als das synchrone Senden derselben Anfragen.
  • Siehe die OpenAI Batch API-Dokumentation und Preise für Details:

Konfigurationsbeispiel:

agents: {
  defaults: {
    memorySearch: {
      provider: "openai",
      model: "text-embedding-3-small",
      fallback: "openai",
      remote: {
        batch: { enabled: true, concurrency: 2 }
      },
      sync: { watch: true }
    }
  }
}

Tools:

  • memory_search — gibt Snippets mit Datei + Zeilenbereichen zurück.
  • memory_get — liest Memory-Dateiinhalt nach Pfad.

Lokaler Modus:

  • Setze agents.defaults.memorySearch.provider = "local".
  • Gib agents.defaults.memorySearch.local.modelPath an (GGUF oder hf:-URI).
  • Optional: Setze agents.defaults.memorySearch.fallback = "none", um Remote-Fallback zu vermeiden.

Wie die Memory-Tools funktionieren

  • memory_search durchsucht semantisch Markdown-Chunks (~400 Token Ziel, 80-Token Überlappung) aus MEMORY.md + memory/**/*.md. Es gibt Snippet-Text (max. ~700 Zeichen), Dateipfad, Zeilenbereich, Score, Provider/Modell und ob wir von lokal auf Remote-Embeddings zurückgefallen sind zurück. Es wird kein vollständiger Dateiinhalt zurückgegeben.
  • memory_get liest eine bestimmte Memory-Markdown-Datei (workspace-relativ), optional ab einer Startzeile und für N Zeilen. Pfade außerhalb von MEMORY.md / memory/ sind nur erlaubt, wenn sie explizit in memorySearch.extraPaths aufgeführt sind.
  • Beide Tools sind nur aktiviert, wenn memorySearch.enabled für den Agent true ergibt.

Was indexiert wird (und wann)

  • Dateityp: Nur Markdown (MEMORY.md, memory/**/*.md, plus alle .md-Dateien unter memorySearch.extraPaths).
  • Index-Speicherung: Pro-Agent SQLite unter ~/.openclaw/memory/<agentId>.sqlite (konfigurierbar über agents.defaults.memorySearch.store.path, unterstützt {agentId}-Token).
  • Aktualität: Watcher auf MEMORY.md, memory/ und memorySearch.extraPaths markiert den Index als dirty (Debounce 1,5s). Sync wird beim Session-Start, bei der Suche oder in einem Intervall geplant und läuft asynchron. Session-Transkripte verwenden Delta-Schwellenwerte, um Hintergrund-Sync auszulösen.
  • Reindex-Auslöser: Der Index speichert den Embedding-Provider/Modell + Endpoint-Fingerprint + Chunking-Parameter. Wenn sich einer davon ändert, setzt OpenClaw automatisch zurück und reindexiert den gesamten Store.

Hybride Suche (BM25 + Vektor)

Wenn aktiviert, kombiniert OpenClaw:

  • Vektor-Ähnlichkeit (semantischer Match, Formulierung kann unterschiedlich sein)
  • BM25-Keyword-Relevanz (exakte Tokens wie IDs, Umgebungsvariablen, Code-Symbole)

Wenn Volltextsuche auf deiner Plattform nicht verfügbar ist, fällt OpenClaw auf reine Vektorsuche zurück.

Warum hybrid?

Vektorsuche ist großartig bei „das bedeutet dasselbe”:

  • „Mac Studio Gateway Host” vs. „die Maschine, auf der das Gateway läuft”
  • „Dateiupdates debouncen” vs. „nicht bei jedem Schreiben indexieren”

Aber sie kann schwach bei exakten, signalstarken Tokens sein:

  • IDs (a828e60, b3b9895a…)
  • Code-Symbole (memorySearch.query.hybrid)
  • Fehlerstrings („sqlite-vec unavailable”)

BM25 (Volltext) ist das Gegenteil: stark bei exakten Tokens, schwächer bei Umschreibungen. Hybride Suche ist der pragmatische Mittelweg: beide Retrieval-Signale nutzen, sodass du gute Ergebnisse sowohl für „natürliche Sprache”-Abfragen als auch für „Nadel im Heuhaufen”-Abfragen bekommst.

Wie wir Ergebnisse zusammenführen (aktuelles Design)

Implementierungsskizze:

  1. Kandidatenpool von beiden Seiten abrufen:
  • Vektor: Top maxResults * candidateMultiplier nach Kosinus-Ähnlichkeit.
  • BM25: Top maxResults * candidateMultiplier nach FTS5 BM25-Rang (niedriger ist besser).
  1. BM25-Rang in einen 0..1-ähnlichen Score umwandeln:
  • textScore = 1 / (1 + max(0, bm25Rank))
  1. Kandidaten nach Chunk-ID vereinigen und gewichteten Score berechnen:
  • finalScore = vectorWeight * vectorScore + textWeight * textScore

Hinweise:

  • vectorWeight + textWeight wird in der Config-Auflösung auf 1.0 normalisiert, sodass Gewichte sich wie Prozentsätze verhalten.
  • Wenn Embeddings nicht verfügbar sind (oder der Provider einen Null-Vektor zurückgibt), führen wir trotzdem BM25 aus und geben Keyword-Matches zurück.
  • Wenn FTS5 nicht erstellt werden kann, behalten wir die reine Vektorsuche bei (kein harter Fehler).

Das ist nicht „IR-theoretisch perfekt”, aber es ist einfach, schnell und verbessert tendenziell Recall/Precision bei echten Notizen. Wenn wir später ausgefeilter werden wollen, sind gängige nächste Schritte Reciprocal Rank Fusion (RRF) oder Score-Normalisierung (min/max oder z-Score) vor dem Mischen.

Konfiguration:

agents: {
  defaults: {
    memorySearch: {
      query: {
        hybrid: {
          enabled: true,
          vectorWeight: 0.7,
          textWeight: 0.3,
          candidateMultiplier: 4
        }
      }
    }
  }
}

Embedding-Cache

OpenClaw kann Chunk-Embeddings in SQLite cachen, sodass Reindexierung und häufige Updates (besonders Session-Transkripte) unveränderten Text nicht neu embedden.

Konfiguration:

agents: {
  defaults: {
    memorySearch: {
      cache: {
        enabled: true,
        maxEntries: 50000
      }
    }
  }
}

Session-Memory-Suche (experimentell)

Du kannst optional Session-Transkripte indexieren und sie über memory_search auffindbar machen. Das ist hinter einem experimentellen Flag versteckt.

agents: {
  defaults: {
    memorySearch: {
      experimental: { sessionMemory: true },
      sources: ["memory", "sessions"]
    }
  }
}

Hinweise:

  • Session-Indexierung ist Opt-in (standardmäßig aus).
  • Session-Updates werden gedebounct und asynchron indexiert, sobald sie Delta-Schwellenwerte überschreiten (Best-Effort).
  • memory_search blockiert nie auf Indexierung; Ergebnisse können leicht veraltet sein, bis der Hintergrund-Sync abgeschlossen ist.
  • Ergebnisse enthalten weiterhin nur Snippets; memory_get bleibt auf Memory-Dateien beschränkt.
  • Session-Indexierung ist pro Agent isoliert (nur die Session-Logs dieses Agents werden indexiert).
  • Session-Logs liegen auf der Festplatte (~/.openclaw/agents/<agentId>/sessions/*.jsonl). Jeder Prozess/Benutzer mit Dateisystemzugriff kann sie lesen, also behandle Festplattenzugriff als Vertrauensgrenze. Für strengere Isolation führe Agents unter separaten OS-Benutzern oder Hosts aus.

Delta-Schwellenwerte (Standardwerte gezeigt):

agents: {
  defaults: {
    memorySearch: {
      sync: {
        sessions: {
          deltaBytes: 100000,   // ~100 KB
          deltaMessages: 50     // JSONL-Zeilen
        }
      }
    }
  }
}

SQLite-Vektorbeschleunigung (sqlite-vec)

Wenn die sqlite-vec-Erweiterung verfügbar ist, speichert OpenClaw Embeddings in einer SQLite Virtual Table (vec0) und führt Vektor-Distanz-Abfragen in der Datenbank durch. Das hält die Suche schnell, ohne jedes Embedding in JS laden zu müssen.

Konfiguration (optional):

agents: {
  defaults: {
    memorySearch: {
      store: {
        vector: {
          enabled: true,
          extensionPath: "/path/to/sqlite-vec"
        }
      }
    }
  }
}

Hinweise:

  • enabled ist standardmäßig true; wenn deaktiviert, fällt die Suche auf In-Process-Kosinus-Ähnlichkeit über gespeicherte Embeddings zurück.
  • Wenn die sqlite-vec-Erweiterung fehlt oder nicht geladen werden kann, loggt OpenClaw den Fehler und fährt mit dem JS-Fallback fort (keine Vektor-Tabelle).
  • extensionPath überschreibt den gebündelten sqlite-vec-Pfad (nützlich für Custom Builds oder nicht-standardmäßige Installationsorte).

Lokales Embedding Auto-Download

  • Standard lokales Embedding-Modell: hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf (~0,6 GB).
  • Wenn memorySearch.provider = "local", löst node-llama-cpp den modelPath auf; wenn das GGUF fehlt, wird es automatisch heruntergeladen in den Cache (oder local.modelCacheDir falls gesetzt), dann geladen. Downloads werden bei Retry fortgesetzt.
  • Native Build-Anforderung: Führe pnpm approve-builds aus, wähle node-llama-cpp, dann pnpm rebuild node-llama-cpp.
  • Fallback: Wenn das lokale Setup fehlschlägt und memorySearch.fallback = "openai", wechseln wir automatisch zu Remote-Embeddings (openai/text-embedding-3-small falls nicht überschrieben) und protokollieren den Grund.

Beispiel für benutzerdefinierten OpenAI-kompatiblen Endpoint

agents: {
  defaults: {
    memorySearch: {
      provider: "openai",
      model: "text-embedding-3-small",
      remote: {
        baseUrl: "https://api.example.com/v1/",
        apiKey: "YOUR_REMOTE_API_KEY",
        headers: {
          "X-Organization": "org-id",
          "X-Project": "project-id"
        }
      }
    }
  }
}

Hinweise:

  • remote.* hat Vorrang vor models.providers.openai.*.
  • remote.headers werden mit OpenAI-Headern zusammengeführt; remote gewinnt bei Key-Konflikten. Lass remote.headers weg, um die OpenAI-Standardwerte zu verwenden.