Сессии и память

Память

Память OpenClaw — это обычные файлы Markdown в рабочей области агента. Файлы являются источником истины; модель «помнит» только то, что записано на диск. Инструменты поиска по памяти предоставляются активным плагином памяти (по умолчанию: memory-core). Отключите плагины памяти с помощью plugins.slots.memory = "none".

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

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

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

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

Инструменты памяти

OpenClaw предоставляет два инструмента для работы с этими файлами Markdown:

  • memory_search — семантический поиск по индексированным фрагментам.
  • memory_get — целевое чтение конкретного файла Markdown/диапазона строк.

memory_get теперь корректно обрабатывает отсутствие файла (например, сегодняшний ежедневный журнал до первой записи). И встроенный менеджер, и бэкенд QMD возвращают { text: "", path } вместо выброса ошибки ENOENT, поэтому агенты могут обработать «пока ничего не записано» и продолжить свою работу без оборачивания вызова инструмента в try/catch.

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

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

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

Когда сессия близка к автоматической компактизации, OpenClaw запускает тихий, агентский ход, который напоминает модели записать устойчивые воспоминания до компактизации контекста. Стандартные промпты явно говорят, что модель может ответить, но обычно правильным ответом является NO_REPLY, чтобы пользователь никогда не видел этот ход. Это контролируется параметром agents.defaults.compaction.memoryFlush:

{
  agents: {
    defaults: {
      compaction: {
        reserveTokensFloor: 20000,
        memoryFlush: {
          enabled: true,
          softThresholdTokens: 4000,
          systemPrompt: "Сессия приближается к компактизации. Сохраните устойчивые воспоминания сейчас.",
          prompt: "Запишите любые долговременные заметки в memory/YYYY-MM-DD.md; ответьте NO_REPLY, если нечего сохранять.",
        },
      },
    },
  },
}

Детали:

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

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

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

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

  • Включён по умолчанию.
  • Отслеживает изменения файлов памяти (с дебаунсом).
  • Настройте поиск по памяти в agents.defaults.memorySearch (не на верхнем уровне memorySearch).
  • По умолчанию использует удалённые эмбеддинги. Если memorySearch.provider не задан, OpenClaw выбирает автоматически:
    1. local, если настроен memorySearch.local.modelPath и файл существует.
    2. openai, если можно получить ключ OpenAI.
    3. gemini, если можно получить ключ Gemini.
    4. voyage, если можно получить ключ Voyage.
    5. mistral, если можно получить ключ Mistral.
    6. В противном случае поиск по памяти остаётся отключённым до настройки.
  • Локальный режим использует node-llama-cpp и может потребовать pnpm approve-builds.
  • Использует sqlite-vec (при доступности) для ускорения векторного поиска внутри SQLite.
  • Также поддерживается memorySearch.provider = "ollama" для локальных/самостоятельно размещённых эмбеддингов Ollama (/api/embeddings), но он не выбирается автоматически.

Удалённые эмбеддинги требуют API-ключа для провайдера эмбеддингов. OpenClaw получает ключи из профилей аутентификации, models.providers.*.apiKey или переменных окружения. Codex OAuth покрывает только чат/завершения и не удовлетворяет требованиям эмбеддингов для поиска по памяти. Для Gemini используйте GEMINI_API_KEY или models.providers.google.apiKey. Для Voyage используйте VOYAGE_API_KEY или models.providers.voyage.apiKey. Для Mistral используйте MISTRAL_API_KEY или models.providers.mistral.apiKey. Ollama обычно не требует реального API-ключа (заполнитель вроде OLLAMA_API_KEY=ollama-local достаточен, когда требуется локальной политикой). При использовании пользовательской конечной точки, совместимой с OpenAI, задайте memorySearch.remote.apiKey (и опционально memorySearch.remote.headers).

Бэкенд QMD (экспериментальный)

Установите memory.backend = "qmd", чтобы заменить встроенный индексатор SQLite на QMD: локальный поисковый сайдкар, сочетающий BM25 + векторы + реранкинг. Markdown остаётся источником истины; OpenClaw вызывает QMD для получения данных. Ключевые моменты: Предварительные требования

  • По умолчанию отключён. Включается в конфигурации (memory.backend = "qmd").
  • Установите CLI QMD отдельно (bun install -g https://github.com/tobi/qmd или скачайте релиз) и убедитесь, что бинарный файл qmd находится в PATH шлюза.
  • QMD требует сборку SQLite с поддержкой расширений (brew install sqlite на macOS).
  • QMD работает полностью локально через Bun + node-llama-cpp и автоматически загружает GGUF-модели с HuggingFace при первом использовании (не требуется отдельный демон Ollama).
  • Шлюз запускает QMD в автономном домашнем каталоге XDG под ~/.openclaw/agents/<agentId>/qmd/, устанавливая XDG_CONFIG_HOME и XDG_CACHE_HOME.
  • Поддержка ОС: macOS и Linux работают из коробки после установки Bun + SQLite. Windows лучше всего поддерживается через WSL2.

Как работает сайдкар

  • Шлюз создаёт автономный домашний каталог QMD под ~/.openclaw/agents/<agentId>/qmd/ (конфиг + кэш + база данных sqlite).
  • Коллекции создаются через qmd collection add из memory.qmd.paths (плюс стандартные файлы памяти рабочей области), затем qmd update + qmd embed запускаются при загрузке и с настраиваемым интервалом (memory.qmd.update.interval, по умолчанию 5 м).
  • Теперь шлюз инициализирует менеджер QMD при запуске, поэтому периодические таймеры обновления активируются ещё до первого вызова memory_search.
  • Обновление при загрузке теперь выполняется в фоне по умолчанию, чтобы не блокировать запуск чата; установите memory.qmd.update.waitForBootSync = true, чтобы сохранить предыдущее блокирующее поведение.
  • Поиск выполняется через memory.qmd.searchMode (по умолчанию qmd search --json; также поддерживает vsearch и query). Если выбранный режим не поддерживает флаги в вашей сборке QMD, OpenClaw повторяет с qmd query. Если QMD завершается сбоем или бинарный файл отсутствует, OpenClaw автоматически возвращается к встроенному менеджеру SQLite, чтобы инструменты памяти продолжали работать.
  • OpenClaw не предоставляет настройки размера батча для эмбеддингов QMD сегодня; поведение батча контролируется самим QMD.
  • Первый поиск может быть медленным: QMD может загружать локальные GGUF-модели (реранкер/расширение запроса) при первом запуске qmd query.
    • OpenClaw автоматически устанавливает XDG_CONFIG_HOME/XDG_CACHE_HOME при запуске QMD.

    • Если вы хотите предварительно загрузить модели вручную (и разогреть тот же индекс, который использует OpenClaw), выполните одноразовый запрос с каталогами XDG агента. Состояние QMD OpenClaw находится под вашим каталогом состояния (по умолчанию ~/.openclaw). Вы можете указать qmd на тот же самый индекс, экспортировав те же переменные XDG, которые использует OpenClaw:

      Копировать

      # Выберите тот же каталог состояния, который использует OpenClaw
      STATE_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
      
      export XDG_CONFIG_HOME="$STATE_DIR/agents/main/qmd/xdg-config"
      export XDG_CACHE_HOME="$STATE_DIR/agents/main/qmd/xdg-cache"
      
      # (Опционально) принудительно обновите индекс + эмбеддинги
      qmd update
      qmd embed
      
      # Разогрейте / спровоцируйте первую загрузку моделей
      qmd query "test" -c memory-root --json >/dev/null 2>&1
      

Конфигурационная поверхность (memory.qmd.*)

  • command (по умолчанию qmd): переопределить путь к исполняемому файлу.
  • searchMode (по умолчанию search): выбрать, какая команда QMD поддерживает memory_search (search, vsearch, query).
  • includeDefaultMemory (по умолчанию true): автоматически индексировать MEMORY.md + memory/**/*.md.
  • paths[]: добавить дополнительные каталоги/файлы (path, опционально pattern, опционально стабильное name).
  • sessions: включить индексирование JSONL сессий (enabled, retentionDays, exportDir).
  • update: управляет периодичностью обновления и выполнением обслуживания: (interval, debounceMs, onBoot, waitForBootSync, embedInterval, commandTimeoutMs, updateTimeoutMs, embedTimeoutMs).
  • limits: ограничивает размер полезной нагрузки при поиске (maxResults, maxSnippetChars, maxInjectedChars, timeoutMs).
  • scope: та же схема, что и session.sendPolicy. По умолчанию только личные сообщения (deny все, allow прямые чаты); ослабьте, чтобы показывать результаты QMD в группах/каналах.
    • match.keyPrefix соответствует нормализованному ключу сессии (нижний регистр, с удалением любого префикса agent:<id>:). Пример: discord:channel:.
    • match.rawKeyPrefix соответствует сырому ключу сессии (нижний регистр), включая agent:<id>:. Пример: agent:main:discord:.
    • Устаревшее: match.keyPrefix: "agent:..." всё ещё обрабатывается как префикс сырого ключа, но для ясности предпочтительнее rawKeyPrefix.
  • Когда scope запрещает поиск, OpenClaw записывает предупреждение с полученным channel/chatType, чтобы пустые результаты было легче отлаживать.
  • Фрагменты из источников вне рабочей области отображаются как qmd/<collection>/<relative-path> в результатах memory_search; memory_get понимает этот префикс и читает из настроенного корневого каталога коллекции QMD.
  • Когда memory.qmd.sessions.enabled = true, OpenClaw экспортирует очищенные транскрипты сессий (ходы Пользователя/Ассистента) в выделенную коллекцию QMD под ~/.openclaw/agents/<id>/qmd/sessions/, чтобы memory_search мог вспоминать недавние разговоры, не касаясь встроенного индекса SQLite.
  • Фрагменты memory_search теперь включают нижний колонтитул Источник: <path#line>, когда memory.citations имеет значение auto/on; установите memory.citations = "off", чтобы сохранить метаданные пути внутренними (агент всё равно получает путь для memory_get, но текст фрагмента опускает нижний колонтитул, а системный промпт предупреждает агента не цитировать его).

Пример

memory: {
  backend: "qmd",
  citations: "auto",
  qmd: {
    includeDefaultMemory: true,
    update: { interval: "5m", debounceMs: 15000 },
    limits: { maxResults: 6, timeoutMs: 4000 },
    scope: {
      default: "deny",
      rules: [
        { action: "allow", match: { chatType: "direct" } },
        // Нормализованный префикс ключа сессии (удаляет `agent:<id>:`).
        { action: "deny", match: { keyPrefix: "discord:channel:" } },
        // Сырой префикс ключа сессии (включает `agent:<id>:`).
        { action: "deny", match: { rawKeyPrefix: "agent:main:discord:" } },
      ]
    },
    paths: [
      { name: "docs", path: "~/notes", pattern: "**/*.md" }
    ]
  }
}

Цитирование и откат

  • memory.citations применяется независимо от бэкенда (auto/on/off).
  • Когда работает qmd, мы помечаем status().backend = "qmd", чтобы в диагностике было видно, какой движок обслужил результаты. Если подпроцесс QMD завершается или вывод JSON не может быть разобран, менеджер поиска записывает предупреждение и возвращает встроенный провайдер (существующие эмбеддинги Markdown), пока QMD не восстановится.

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

Если вы хотите индексировать файлы 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, voyage, mistral, ollama, local или none.
  • Провайдер отката используется только при сбое основного провайдера эмбеддингов.

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

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

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

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

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

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 или URI hf:).
  • Опционально: установите agents.defaults.memorySearch.fallback = "none", чтобы избежать удалённого отката.

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

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

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

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

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

При включении OpenClaw сочетает:

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

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

Зачем гибридный?

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

  • «Хост шлюза Mac Studio» vs «машина, на которой работает шлюз»
  • «дебаунс обновлений файлов» vs «избегать индексирования при каждой записи»

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

  • ID (a828e60, b3b9895a…)
  • символы кода (memorySearch.query.hybrid)
  • строки ошибок («sqlite-vec недоступен»)

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

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

Набросок реализации:

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

Примечания:

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

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

Постобработка

После объединения векторных и ключевых оценок два опциональных этапа постобработки уточняют список результатов перед тем, как он достигнет агента:

Векторный + Ключевой → Взвешенное объединение → Временное затухание → Сортировка → MMR → Топ-K результатов

Оба этапа по умолчанию отключены и могут быть включены независимо.

Реранкинг MMR (разнообразие)

Когда гибридный поиск возвращает результаты, несколько фрагментов могут содержать схожий или перекрывающийся контент. Например, поиск «настройка домашней сети» может вернуть пять почти идентичных фрагментов из разных ежедневных заметок, которые все упоминают одну и ту же конфигурацию маршрутизатора. MMR (Maximal Marginal Relevance) переранжирует результаты, чтобы сбалансировать релевантность с разнообразием, гарантируя, что топовые результаты охватывают разные аспекты запроса, а не повторяют одну и ту же информацию. Как это работает:

  1. Результаты оцениваются по их исходной релевантности (взвешенная оценка вектора + BM25).
  2. MMR итеративно выбирает результаты, которые максимизируют: λ × релевантность − (1−λ) × max_сходство_с_выбранными.
  3. Сходство между результатами измеряется с помощью текстового сходства Жаккара на токенизированном содержимом.

Параметр lambda контролирует компромисс:

  • lambda = 1.0 → чистая релевантность (без штрафа за разнообразие)
  • lambda = 0.0 → максимальное разнообразие (игнорирует релевантность)
  • По умолчанию: 0.7 (сбалансированный, с небольшим смещением в сторону релевантности)

Пример — запрос: «настройка домашней сети» Даны эти файлы памяти:

memory/2026-02-10.md  → "Настроил маршрутизатор Omada, установил VLAN 10 для устройств IoT"
memory/2026-02-08.md  → "Настроил маршрутизатор Omada, переместил IoT в VLAN 10"
memory/2026-02-05.md  → "Настроил AdGuard DNS на 192.168.10.2"
memory/network.md     → "Маршрутизатор: Omada ER605, AdGuard: 192.168.10.2, VLAN 10: IoT"

Без MMR — топ 3 результата:

1. memory/2026-02-10.md  (оценка: 0.92)  ← маршрутизатор + VLAN
2. memory/2026-02-08.md  (оценка: 0.89)  ← маршрутизатор + VLAN (почти дубликат!)
3. memory/network.md     (оценка: 0.85)  ← справочный документ

С MMR (λ=0.7) — топ 3 результата:

1. memory/2026-02-10.md  (оценка: 0.92)  ← маршрутизатор + VLAN
2. memory/network.md     (оценка: 0.85)  ← справочный документ (разнообразный!)
3. memory/2026-02-05.md  (оценка: 0.78)  ← AdGuard DNS (разнообразный!)

Почти дубликат от 8 февраля выпадает, и агент получает три различных фрагмента информации. Когда включать: Если вы заметили, что memory_search возвращает избыточные или почти дублирующие фрагменты, особенно с ежедневными заметками, которые часто повторяют похожую информацию изо дня в день.

Временное затухание (усиление новизны)

У агентов с ежедневными заметками со временем накапливаются сотни датированных файлов. Без затухания хорошо сформулированная заметка шестимесячной давности может обойти вчерашнее обновление на ту же тему. Временное затухание применяет экспоненциальный множитель к оценкам на основе возраста каждого результата, так что недавние воспоминания естественным образом ранжируются выше, а старые постепенно исчезают:

затухшаяОценка = оценка × e^(-λ × возрастВДнях)

где λ = ln(2) / halfLifeDays. При стандартном периоде полураспада 30 дней:

  • Сегодняшние заметки: 100% исходной оценки
  • 7 дней назад: ~84%
  • 30 дней назад: 50%
  • 90 дней назад: 12.5%
  • 180 дней назад: ~1.6%

Вечнозелёные файлы никогда не затухают:

  • MEMORY.md (корневой файл памяти)
  • Недатированные файлы в memory/ (например, memory/projects.md, memory/network.md)
  • Они содержат долговременную справочную информацию, которая всегда должна ранжироваться нормально.

Датированные ежедневные файлы (memory/YYYY-MM-DD.md) используют дату, извлечённую из имени файла. Другие источники (например, транскрипты сессий) используют время последнего изменения файла (mtime). Пример — запрос: «какой у Рода рабочий график?» Даны эти файлы памяти (сегодня 10 февраля):

memory/2025-09-15.md  → "Род работает пн-пт, стендап в 10:00, парное программирование в 14:00"  (148 дней назад)
memory/2026-02-10.md  → "У Рода стендап в 14:15, 1:1 с Зебом в 14:45"    (сегодня)
memory/2026-02-03.md  → "Род начал новую команду, стендап перенесён на 14:15"        (7 дней