Память

Память OpenClaw — это обычный Markdown в рабочем пространстве агента. Файлы являются источником истины; модель "помнит" только то, что записано на диск.

Инструменты поиска по памяти предоставляются активным плагином памяти (по умолчанию: memory-core). Отключите плагины памяти с помощью plugins.slots.memory = "none".

Файлы памяти (Markdown)

Структура рабочего пространства по умолчанию использует два уровня памяти:

  • memory/YYYY-MM-DD.md
    • Ежедневный журнал (только добавление).
    • Читаются сегодня + вчера при запуске сессии.
  • MEMORY.md (опционально)
    • Кураторская долгосрочная память.
    • Загружается только в основной приватной сессии (никогда в групповых контекстах).

Эти файлы находятся в рабочем пространстве (agents.defaults.workspace, по умолчанию ~/.openclaw/workspace). См. Рабочее пространство агента для полной структуры.

Когда записывать память

  • Решения, предпочтения и долговечные факты отправляются в MEMORY.md.
  • Ежедневные заметки и текущий контекст отправляются в memory/YYYY-MM-DD.md.
  • Если кто-то говорит "запомни это", запишите это (не храните в RAM).
  • Эта область все еще развивается. Полезно напоминать модели сохранять воспоминания; она будет знать, что делать.
  • Если вы хотите, чтобы что-то запомнилось, попросите бота записать это в память.

Автоматическая очистка памяти (предкомпактизационный пинг)

Когда сессия близка к автокомпактизации, OpenClaw запускает тихий агентный ход, который напоминает модели записать долговечную память перед компактизацией контекста. Промпты по умолчанию явно говорят, что модель может ответить, но обычно NO_REPLY является правильным ответом, поэтому пользователь никогда не видит этот ход.

Это контролируется через agents.defaults.compaction.memoryFlush:

\{
  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."
        \}
      \}
    \}
  \}
\}

Детали:

  • Мягкий порог: очистка запускается, когда оценка токенов сессии пересекает contextWindow - reserveTokensFloor - softThresholdTokens.
  • Тихая по умолчанию: промпты включают NO_REPLY, поэтому ничего не доставляется.
  • Два промпта: пользовательский промпт плюс системный промпт добавляют напоминание.
  • Одна очистка на цикл компактизации (отслеживается в sessions.json).
  • Рабочее пространство должно быть доступно для записи: если сессия выполняется в изолированной среде с workspaceAccess: "ro" или "none", очистка пропускается.

Для полного жизненного цикла компактизации см. Управление сессией + компактизация.

Векторный поиск по памяти

OpenClaw может построить небольшой векторный индекс над MEMORY.md и memory/*.md (плюс любые дополнительные каталоги или файлы, которые вы включаете), чтобы семантические запросы могли найти связанные заметки, даже когда формулировка отличается.

По умолчанию:

  • Включено по умолчанию.
  • Отслеживает изменения в файлах памяти (с задержкой).
  • Использует удаленные эмбеддинги по умолчанию. Если memorySearch.provider не установлен, OpenClaw автоматически выбирает:
    1. local, если настроен memorySearch.local.modelPath и файл существует.
    2. openai, если можно разрешить ключ OpenAI.
    3. gemini, если можно разрешить ключ Gemini.
    4. В противном случае поиск по памяти остается отключенным до настройки.
  • Локальный режим использует node-llama-cpp и может потребовать pnpm approve-builds.
  • Использует sqlite-vec (когда доступен) для ускорения векторного поиска внутри SQLite.

Удаленные эмбеддинги требуют API-ключ для провайдера эмбеддингов. OpenClaw разрешает ключи из профилей авторизации, models.providers.*.apiKey или переменных окружения. OAuth Codex покрывает только chat/completions и не удовлетворяет эмбеддингам для поиска по памяти. Для Gemini используйте GEMINI_API_KEY или models.providers.google.apiKey. При использовании пользовательской конечной точки, совместимой с OpenAI, установите memorySearch.remote.apiKey (и опциональные memorySearch.remote.headers).

Дополнительные пути памяти

Если вы хотите индексировать файлы Markdown за пределами стандартной структуры рабочего пространства, добавьте явные пути:

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

Примечания:

  • Пути могут быть абсолютными или относительно рабочего пространства.
  • Каталоги сканируются рекурсивно на наличие .md файлов.
  • Индексируются только файлы Markdown.
  • Символические ссылки игнорируются (файлы или каталоги).

Эмбеддинги Gemini (нативные)

Установите провайдер на gemini для прямого использования API эмбеддингов Gemini:

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

Примечания:

  • remote.baseUrl опционален (по умолчанию используется базовый URL API Gemini).
  • remote.headers позволяет добавлять дополнительные заголовки при необходимости.
  • Модель по умолчанию: gemini-embedding-001.

Если вы хотите использовать пользовательскую конечную точку, совместимую с OpenAI (OpenRouter, vLLM или прокси), вы можете использовать конфигурацию remote с провайдером OpenAI:

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" \}
      \}
    \}
  \}
\}

Если вы не хотите устанавливать API-ключ, используйте memorySearch.provider = "local" или установите memorySearch.fallback = "none".

Резервные варианты:

  • memorySearch.fallback может быть openai, gemini, local или none.
  • Резервный провайдер используется только когда основной провайдер эмбеддингов не работает.

Пакетное индексирование (OpenAI + Gemini):

  • Включено по умолчанию для эмбеддингов OpenAI и Gemini. Установите agents.defaults.memorySearch.remote.batch.enabled = false для отключения.
  • Поведение по умолчанию ожидает завершения пакета; настройте remote.batch.wait, remote.batch.pollIntervalMs и remote.batch.timeoutMinutes при необходимости.
  • Установите remote.batch.concurrency для контроля количества пакетных заданий, отправляемых параллельно (по умолчанию: 2).
  • Пакетный режим применяется, когда memorySearch.provider = "openai" или "gemini" и использует соответствующий API-ключ.
  • Пакетные задания Gemini используют асинхронную конечную точку пакетных эмбеддингов и требуют доступности Gemini Batch API.

Почему пакетный режим OpenAI быстрый + дешевый:

  • Для больших обратных заполнений OpenAI обычно является самым быстрым вариантом, который мы поддерживаем, потому что мы можем отправить много запросов эмбеддингов в одном пакетном задании и позволить OpenAI обрабатывать их асинхронно.
  • OpenAI предлагает скидки на рабочие нагрузки Batch API, поэтому крупные запуски индексирования обычно дешевле, чем отправка тех же запросов синхронно.
  • См. документацию и цены Batch API OpenAI для деталей:

Пример конфигурации:

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

Инструменты:

  • memory_search — возвращает фрагменты с путем к файлу + диапазонами строк.
  • memory_get — чтение содержимого файла памяти по пути.

Локальный режим:

  • Установите agents.defaults.memorySearch.provider = "local".
  • Укажите agents.defaults.memorySearch.local.modelPath (GGUF или hf: URI).
  • Опционально: установите agents.defaults.memorySearch.fallback = "none" для избежания удаленного резерва.

Как работают инструменты памяти

  • memory_search семантически ищет фрагменты Markdown (~400 токенов целевой размер, 80-токенов перекрытие) из MEMORY.md + memory/**/*.md. Возвращает текст фрагмента (ограничен ~700 символов), путь к файлу, диапазон строк, оценку, провайдера/модель и была ли использована резервная замена с local → remote эмбеддингов. Полное содержимое файла не возвращается.
  • memory_get читает конкретный файл Markdown памяти (относительно рабочего пространства), опционально с начальной строки и на N строк. Пути вне MEMORY.md / memory/ разрешены только когда явно перечислены в memorySearch.extraPaths.
  • Оба инструмента включены только когда memorySearch.enabled разрешается как true для агента.

Что индексируется (и когда)

  • Тип файла: только Markdown (MEMORY.md, memory/**/*.md, плюс любые .md файлы в memorySearch.extraPaths).
  • Хранилище индекса: SQLite для каждого агента в ~/.openclaw/memory/<agentId>.sqlite (настраивается через agents.defaults.memorySearch.store.path, поддерживает токен \{agentId\}).
  • Актуальность: наблюдатель на MEMORY.md, memory/ и memorySearch.extraPaths помечает индекс устаревшим (задержка 1.5s). Синхронизация планируется при запуске сессии, при поиске или по интервалу и выполняется асинхронно. Транскрипты сессий используют пороги дельты для запуска фоновой синхронизации.
  • Триггеры переиндексации: индекс хранит эмбеддинг провайдер/модель + отпечаток конечной точки + параметры разбиения на фрагменты. Если любой из них изменяется, OpenClaw автоматически сбрасывает и переиндексирует все хранилище.

Гибридный поиск (BM25 + вектор)

Когда включено, OpenClaw комбинирует:

  • Векторное сходство (семантическое совпадение, формулировка может отличаться)
  • Релевантность ключевых слов BM25 (точные токены, такие как ID, env переменные, символы кода)

Если полнотекстовый поиск недоступен на вашей платформе, OpenClaw возвращается к векторному поиску.

Зачем гибрид?

Векторный поиск отлично справляется с "это означает то же самое":

  • "Mac Studio gateway host" vs "машина, работающая на шлюзе"
  • "debounce file updates" vs "избегайте индексации при каждой записи"

Но он может быть слабым для точных высокосигнальных токенов:

  • ID (a828e60, b3b9895a…)
  • символы кода (memorySearch.query.hybrid)
  • строки ошибок ("sqlite-vec unavailable")

BM25 (полный текст) — наоборот: силен в точных токенах, слабее в перефразировках. Гибридный поиск — это прагматичная золотая середина: используйте оба сигнала извлечения, чтобы вы получали хорошие результаты как для запросов на "естественном языке", так и для запросов "иголка в стоге сена".

Как мы объединяем результаты (текущий дизайн)

Эскиз реализации:

  1. Извлечь пул кандидатов с обеих сторон:
  • Вектор: топ maxResults * candidateMultiplier по косинусному сходству.
  • BM25: топ maxResults * candidateMultiplier по рангу FTS5 BM25 (ниже лучше).
  1. Преобразовать ранг BM25 в оценку 0..1-ish:
  • textScore = 1 / (1 + max(0, bm25Rank))
  1. Объединить кандидатов по ID фрагмента и вычислить взвешенную оценку:
  • finalScore = vectorWeight * vectorScore + textWeight * textScore

Примечания:

  • vectorWeight + textWeight нормализуется к 1.0 в разрешении конфигурации, поэтому веса ведут себя как проценты.
  • Если эмбеддинги недоступны (или провайдер возвращает нулевой вектор), мы все равно выполняем BM25 и возвращаем совпадения ключевых слов.
  • Если FTS5 не может быть создан, мы сохраняем векторный поиск (без жесткого отказа).

Это не "IR-теория совершенна", но это просто, быстро и обычно улучшает полноту/точность на реальных заметках. Если мы хотим стать более фантазийными позже, обычные следующие шаги — это Reciprocal Rank Fusion (RRF) или нормализация оценки (min/max или z-score) перед смешиванием.

Конфигурация:

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

Кэш эмбеддингов

OpenClaw может кэшировать эмбеддинги фрагментов в SQLite, чтобы переиндексация и частые обновления (особенно транскрипты сессий) не переэмбеддировали неизмененный текст.

Конфигурация:

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

Поиск по памяти сессий (экспериментально)

Вы можете опционально индексировать транскрипты сессий и показывать их через memory_search. Это защищено экспериментальным флагом.

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

Примечания:

  • Индексация сессий включается по желанию (выключена по умолчанию).
  • Обновления сессий задерживаются и индексируются асинхронно, как только они пересекают пороги дельты (по мере возможности).
  • memory_search никогда не блокируется на индексации; результаты могут быть слегка устаревшими, пока фоновая синхронизация не завершится.
  • Результаты все еще включают только фрагменты; memory_get остается ограниченным файлами памяти.
  • Индексация сессий изолирована для каждого агента (индексируются только журналы сессий этого агента).
  • Журналы сессий находятся на диске (~/.openclaw/agents/<agentId>/sessions/*.jsonl). Любой процесс/пользователь с доступом к файловой системе может их прочитать, поэтому относитесь к доступу к диску как к границе доверия. Для более строгой изоляции запускайте агентов под отдельными пользователями ОС или хостами.

Пороги дельты (показаны значения по умолчанию):

agents: \{
  defaults: \{
    memorySearch: \{
      sync: \{
        sessions: \{
          deltaBytes: 100000,   // ~100 KB
          deltaMessages: 50     // строки JSONL
        \}
      \}
    \}
  \}
\}

Ускорение векторов SQLite (sqlite-vec)

Когда расширение sqlite-vec доступно, OpenClaw хранит эмбеддинги в виртуальной таблице SQLite (vec0) и выполняет запросы векторного расстояния в базе данных. Это сохраняет поиск быстрым без загрузки каждого эмбеддинга в JS.

Конфигурация (опционально):

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

Примечания:

  • enabled по умолчанию true; когда отключено, поиск возвращается к косинусному сходству внутри процесса над сохраненными эмбеддингами.
  • Если расширение sqlite-vec отсутствует или не загружается, OpenClaw логирует ошибку и продолжает с резервным вариантом JS (без векторной таблицы).
  • extensionPath переопределяет путь к встроенному sqlite-vec (полезно для пользовательских сборок или нестандартных мест установки).

Автозагрузка локальных эмбеддингов

  • Локальная модель эмбеддингов по умолчанию: hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf (~0.6 GB).
  • Когда memorySearch.provider = "local", node-llama-cpp разрешает modelPath; если GGUF отсутствует, он автоматически загружается в кэш (или local.modelCacheDir, если установлен), затем загружается. Загрузки возобновляются при повторной попытке.
  • Требование нативной сборки: запустите pnpm approve-builds, выберите node-llama-cpp, затем pnpm rebuild node-llama-cpp.
  • Резервный вариант: если локальная настройка не удалась и memorySearch.fallback = "openai", мы автоматически переключаемся на удаленные эмбеддинги (openai/text-embedding-3-small, если не переопределено) и записываем причину.

Пример пользовательской конечной точки, совместимой с OpenAI

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"
        \}
      \}
    \}
  \}
\}

Примечания:

  • remote.* имеет приоритет над models.providers.openai.*.
  • remote.headers объединяются с заголовками OpenAI; remote выигрывает при конфликтах ключей. Опустите remote.headers, чтобы использовать значения OpenAI по умолчанию.