Недавно я рассказал, как держу долговременную память агента на обычных markdown‑файлах, без вектор‑базы и эмбеддингов. За кадром той статьи осталось главное: показал, к чему пришел, а вот от чего ушел и почему — нет. И еще одно, о чем стоит сказать прямо: у Claude Code есть своя встроенная память почти того же вида. Я про нее знаю — и сознательно держу свою, в проекте. Дальше объясню, почему.

Если совсем коротко, вот к чему сводится разница:

  • у Claude Code уже есть родная память того же устройства — индекс плюс файлы‑заметки;

  • живет она в системной папке пользователя, вне репозитория, и по умолчанию перехватывает «запомни»;

  • я держу память в проекте под git — ради сохранности, переносимости и контроля над тем, как она устроена;

  • за это отвечает одна строчка в настройках плюс пара правил в конфиге.

Дальше — по порядку. Если нужен только рецепт «как поднять у себя», прыгайте сразу к последнему разделу.

Что было в прошлой статье (перечитывать не обязательно)

Если в двух словах: память агента у меня — это папка с md‑файлами, где каждый факт лежит отдельным файлом. Плюс один всегда загружаемый индекс‑оглавление (MEMORY.md), плюс пара правил в конфиге проекта, которые велят модели читать индекс на старте и дописывать новые факты отдельными файлами. Никакой вектор‑базы и никакого сервиса поверх модели — только файлы и договоренность, как с ними обращаться. Подробности — в прошлой статье.

Прошлая статья описала пункт назначения, но не дорогу к нему. Разберу две развилки: почему я ушел от вектор‑поиска, и чем мой подход отличается от памяти, которую Claude Code дает из коробки.

От чего ушел: вектор‑база и RAG

Классический способ дать модели «память» — сложить тексты в векторную базу и на каждый запрос доставать похожие куски по смыслу. Для памяти агента и для кода я от этого отказался, и вот почему.

Вектор ищет по «смыслу», а «смысл» он считает по кускам‑чанкам. Для сплошного текста это работает. Но код — жестко детерминированный синтаксис: там не нужен приблизительный смысл, там нужно точное совпадение, и обычный grep с регулярками находит его точнее, чем вектор по нарезанным фрагментам. Смысл куска кода, вырванного из функции, — вообще странная величина. То же и с моей памятью: фактов немного, у каждого есть короткий заголовок в индексе, и модель открывает нужный файл по оглавлению — примерно как человек открывает документацию по содержанию, а не листает всю базу разом.

Здесь честно: формального замера «RAG против грепа» я не гонял. Выбор идет не из бенчмарка, а из практики — на моих объемах оглавление плюс grep стабильно находят нужное, без отдельной инфраструктуры и без риска, что нарезка порвет смысл. Вектор я держу в уме для другого класса задач — большие слабоструктурированные тексты, где нет ни оглавления, ни жесткого синтаксиса. Это просто разные инструменты, и для памяти и кода я по умолчанию к вектору не иду.

Чем моя память отличается от встроенной

А теперь то, чего в прошлой статье не было вовсе. У Claude Code есть своя встроенная память, и включена она по умолчанию. Устроена почти так же, как моя: индекс MEMORY.md плюс файлы‑заметки с теми же заголовками. Я про нее знаю — и все равно держу свою. Причина сводится к одному слову: место.

Встроенная память живет в системном каталоге пользователя — ~/.claude/…, вне репозитория, привязанная к конкретной машине. Моя лежит в проекте под git. И разница тут не косметическая, а по двум осям.

Сохранность и история. Память в репозитории видно в pull‑request, она едет вместе с кодом, переживает смену машины, и каждое ее изменение хранится в коммитах. Системная память ничего этого не дает — она в домашней папке одного пользователя, невидима в проекте и не путешествует с ним.

Контроль над устройством. У встроенной памяти всегда‑загружаемый индекс — это один файл MEMORY.md с фиксированным потолком: первые ~200 строк или 25 КБ, что раньше наступит. Поднять этот порог настройкой нельзя — в документации такого ключа нет; когда индекс перерастает лимит, лишнее не грузится, а Claude Code просит переписать оглавление короче. У меня индекс под моим контролем: держу его тонким и, когда фактов становится много, делю на под‑индексы — по отдельным проектам, по крупным темам. Так структура оглавления растет вместе с базой, а не упирается в один фиксированный файл.

Здесь же снимается частое опасение про командную работу: раз индекс общий и лежит в git, не будет ли он вечно конфликтовать при параллельной правке? На практике — не чаще любого другого общего файла. MEMORY.md это обычный текст, он мержится построчно, а дробление на под‑индексы разводит правки по разным файлам, так что пересечений становится еще меньше.

Именно поэтому я сознательно держу память в проекте, а встроенную — выключаю. Отключается она одной строкой: autoMemoryEnabled: false в настройках проекта. Это не мелочь: по умолчанию встроенная память перехватывает «запомни» первой и уносит запись в свою системную папку, мимо вашего проекта. С выключенной встроенной памятью «запомни» проваливается в обычную инструкцию, и уже мой протокол кладет факт туда, где ему место — в репозиторий.

Резонный вопрос: почему выключать, а не просто перенаправить встроенную память в папку проекта? Потому что ее путь задается настройкой autoMemoryDirectory, которая принимает только абсолютный путь или путь от домашней папки, но не относительный. Указать на./memory внутри проекта переносимо не выйдет — получится путь, привязанный к конкретной машине, и вся идея git‑tracked памяти рассыпается. Поэтому чище выключить встроенную и вести свою.

Для личного проекта на одной машине встроенной памяти вполне достаточно, и это честно стоит признать. Но как только память должна быть общей, переносимой и настраиваемой под рост — ей место в репозитории.

Что вообще стоит помнить

Еще одно, что стоит прояснить сразу: память не сохраняет все подряд, что вы пишете. Иначе индекс распухнет и перестанет быть полезным.

Запись случается по двум поводам: по явному «запомни» или когда агент сам узнает долгоживущий факт — ваше предпочтение, ограничение проекта, исправление. Обычный ход разговора, разовые указания под конкретную задачу и все, что и так лежит в коде или в истории git, в память не идет. Ценность памяти именно в том, что она не журнал, а короткий выверенный список того, к чему стоит возвращаться.

И раз память лежит в git, важно, чего туда не писать: секреты и персональные данные. Однажды закоммиченное остается в истории, поэтому паролям, ключам и чужим данным в памяти не место — она для фактов и договоренностей, а чувствительное держим вне репозитория.

Как поднять у себя

Самый быстрый путь — отдать агенту готовый промпт из репозитория, он развернет все сам: выключит встроенную память, заведет папку memory/ со стартовыми фактами по вашему проекту и подключит индекс. Репозиторий с примером, протоколом и промптом — github.com/mngerasimenko/keepware‑llm‑lab. Там же — подробный разбор и FAQ.

Отдельно стоит разобрать коллизии: что делать, когда на одну тему претендуют сразу два‑три файла — общее правило и что‑то специфичное под проект. Автоматического разрешения тут нет и не задумано. Индекс — это оглавление, а не уникальный ключ; какой файл открыть, решает сама модель по заголовкам, и на стыке общего и частного она обычно открывает оба и сводит их: частное уточняет общее, «победителя» механизм не выбирает. Чтобы такие пересечения не плодились, при записи держу простое правило — одно понятие, один файл, и перед созданием нового проверяю, нет ли уже подходящего. Это ручная аккуратность, а не автоматика, и это, пожалуй, главная цена подхода против векторной базы, которая ранжирует за вас.

За контекст можно не переживать: на старте грузится только индекс, а сами файлы подтягиваются по запросу. Открыть два‑три соседних файла — это маленькое точечное чтение, а не заливка всего в контекст. Когда индекс разрастается, я делю его на несколько частей по темам.

Итог

Прошлая статья показала пункт назначения — память на файлах вместо вектор‑базы. Эта показала развилки и цену: почему не вектор, чем встроенная память отличается от проектной и что вообще стоит в память писать. Родная память Claude Code проще — ноль настройки. Моя требует одной строки в конфиге, но взамен живет под git, переносима, общая для команды и настраивается под рост так, как мне нужно. Выбор зависит от того, нужна ли памяти жизнь за пределами одной машины.

И, пожалуй, главное: память — это не только «где искать факт», но и «что вообще стоит запомнить и как это хранить». Первое неплохо решает и встроенный механизм. Второе я предпочел держать в своих руках.