🐾 claw-stack
· Orange & Qiushi Wu memory architecture AI agents

Построение системы постоянной памяти для AI-агентов

Как мы создали систему памяти Claw-Stack: MEMORY.md как индекс сессии, файлы Markdown по темам, SQLite FTS5 + поиск по векторам QMD и болезненные уроки о раздутии MEMORY.md.

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

Вот как мы построили систему памяти Claw-Stack, почему она выглядит именно так и чему мы научились в процессе.

Проблема со stateless-агентами

Каждая сессия Claude начинается с чистого листа. Модель не помнит предыдущие сессии, если вы не инъектируете этот контекст явно в начале. Для исследовательского ассистента, с которым вы общаетесь один раз, это нормально. Для автономного агента, который запускается каждый день, накапливает знания о ваших проектах и должен поддерживать последовательное поведение на протяжении недель, это фундаментальная проблема.

Наивное решение — выкинуть всё в системный промпт. Это работает, пока вы не накопили несколько сотен КБ контекста, когда происходят две вещи: вы начинаете упираться в лимиты контекста, и способность модели использовать ранние части очень длинного контекста деградирует. Агент начинает игнорировать вещи, которые вы ему рассказали три месяца назад, потому что они слишком далеко от текущего взаимодействия.

Нам нужна была система памяти с двумя свойствами: она должна была быть селективной (инъектировать только то, что актуально для текущей сессии) и читаемой для человека (нам нужно было иметь возможность проверять, редактировать и корректировать то, что агент считает правдой).

Архитектура с тремя слоями

Система памяти состоит из трёх слоёв:

Слой 1: MEMORY.md — компактный индекс, загружаемый в начале каждой сессии. Это структурированный файл Markdown с секциями для недавней активности, активных проектов, ключевых контактов и заметок об инфраструктуре. Он намеренно держится кратким — система применяет лимит на размер в байтах — чтобы он не потреблял бюджет контекста на сессиях с большими описаниями задач.

Слой 2: Файлы по темам — более длинные файлы Markdown в директории memory/, которые рассматривают конкретные темы в деталях. projects/claw-stack.md, contacts/key-people.md, infrastructure/servers.md. Они не загружаются автоматически. Агент имеет инструмент read_memory, который получает конкретный файл, когда ему нужна информация по теме.

Слой 3: SQLite + поиск по векторам QMD — база данных SQLite с полнотекстовым поиском FTS5 и индексом QMD (инструмент для векторных эмбеддингов, построенный на SQLite) для семантического поиска. Когда агент получает запрос, на который он не может ответить из MEMORY.md и файлов по темам, он запускает поиск по векторам по всему контенту памяти, чтобы найти релевантные фрагменты.

Почему не векторная база данных

Короткий ответ: для нашего масштаба и паттернов доступа операционные издержки выделенной векторной базы данных не стоят того.

Основные причины, почему мы выбрали SQLite + FTS5 вместо выделенной векторной базы данных:

  1. Прозрачность. С выделенной векторной базой данных вы не можете легко проверить, был ли поиск корректным без специальных инструментов для запроса к ней. Файл Markdown вы можете открыть в любом текстовом редакторе. Наша база данных SQLite открывается любым инструментом SQLite, и схема — это таблицы, которые мы сами написали.

  2. Операционная простота. Всё хранилище памяти — это один файл .db плюс директория файлов Markdown. Нет отдельного процесса для управления, нет миграций форматов, нет проблем с совместимостью версий между бинарником базы данных и вашими данными.

  3. Достаточно для нашего масштаба. У нас есть около 50 000 слов контента памяти во всех файлах. SQLite FTS5 может выполнять полнотекстовый поиск по этому за миллисекунды. Случаи, когда семантическое сходство значительно лучше поиска по ключевым словам, существуют, но редки настолько, что операционные издержки не стоят того.

QMD (слой семантического поиска) находится поверх SQLite. Эмбеддинги вычисляются локально с использованием небольшой квантизированной модели и сохраняются в таблице SQLite рядом с текстом. Переиндексирование занимает несколько секунд. Всё хранилище памяти — это один файл .db плюс директория файлов Markdown.

Конвейер организатора

Память не управляет собой. После каждой сессии запускается конвейер организатора:

raw session files
  → scan memory/*.md (MD5 hash check, skip unchanged)
  → extract facts per category (project updates, decisions, contacts)
  → deduplicate against existing memory
  → write updated per-topic files
  → rebuild SQLite FTS5 index
  → update MEMORY.md index

Шаг экстракции использует LLM (Gemini как первичный, Claude Haiku как резервный): он читает транскрипт сессии и производит структурированные заметки в специальном формате. Шаг дедупликации основан на правилах: если новый факт является подстрокой существующей записи, пропустить его; если он противоречит существующей записи, пометить для проверки человеком.

Конвейер запускается по расписанию cron (каждые несколько часов в периоды активной работы), а не сразу после каждой сессии. Это группирует затраты на обработку и избегает записи файлов памяти, которые будут немедленно перезаписаны последующей сессией.

Проблема раздутия MEMORY.md

Наиболее болезненный урок был о росте MEMORY.md.

Мы начали без лимита на длину MEMORY.md. Организатор продолжал добавлять к ней. Через шесть недель MEMORY.md составлял более 700 строк. Это имело предсказуемый эффект: загрузка сессии потребляла большую часть бюджета контекста до загрузки любого фактического контента задачи, и модель видимо боролась с синтезом нескольких сотен строк информационного сообщения, одновременно выполняя полезную работу.

Исправление заключалось в изменении поведения организатора и применении лимита размера. Вместо добавления новых фактов прямо в MEMORY.md организатор записывает их в файлы по темам и обновляет MEMORY.md указателями — одной строкой, которая говорит «см. projects/claw-stack.md для текущего статуса» вместо встраивания полного статуса в MEMORY.md. Система теперь применяет лимит размера в байтах на MEMORY.md, чтобы предотвратить неконтролируемый рост.

Это заставило нас переосмыслить, для чего нужен MEMORY.md. Это не резюме всего, что знает агент. Это сессионный брифинг — минимальный контекст, необходимый для ориентирования агента в начале сессии. Всё остальное получается по требованию.

После рефакторинга загрузка сессии заметно быстрее, и модель делает лучшее использование контекста, который у неё есть. Держание MEMORY.md действительно компактным — это постоянная дисциплина — мы обнаружили, что строгое ограничение по количеству строк менее полезно, чем лимит по размеру в байтах, и даже это требует от организатора быть агрессивным в использовании указателей вместо встроенного контента.

Память как читаемое и редактируемое состояние

Философия проектирования, стоящая за системой, заключается в том, что память агента должна быть читаемой для человека и редактируемой человеком. Это ограничение, которое мы наложили намеренно.

Когда агент развивает неправильные убеждения — и это происходит, иногда — мы можем найти неправильную запись в файле Markdown, отредактировать её, и исправление вступит в силу в следующей сессии. С векторной базой данных, исправление неправильного убеждения требует знания, какой эмбеддинг обновлять, удаления его, написания нового, и потенциально инвалидирования кэшированных поисков. С файлами Markdown вы открываете файл и меняете текст.

Это также упрощает проверку. Перед тем как доверить автономному агенту принимать решения от вашего имени, вам нужно иметь возможность прочитать его убеждения и проверить, что они правильны. Вся система памяти — это директория файлов Markdown. Любой текстовый редактор работает.

Компромисс заключается в том, что формат фиксирован. Наши файлы памяти следуют специфической схеме, которую организатор знает, как анализировать и обновлять. Если вы хотите добавить новую категорию памяти, вам нужно обновить как схему файла, так и организатор. Для исследовательского проекта с одним оператором это приемлемо. Для производственной системы со многими агентами и многими типами памяти вам понадобилось бы что-то более гибкое.

Что мы сделали бы иначе

Если бы мы начинали заново:

Используйте меньший MEMORY.md с дня первого. Мы потратили недели на очистку раздутия, которого можно было избежать с начальным лимитом размера. Лимит размера в байтах с записями на основе указателей — лучшая цель, чем фиксированное количество строк для ассистента, используемого ежедневно.

Разделите эпизодическую и семантическую память раньше. «Что произошло на сессии во вторник» (эпизодическая) и «что такое архитектура Claw-Stack» (семантическая) — это разные типы памяти, которые получают преимущество от разных стратегий поиска. Мы смешали их первоначально и позже потратили время на разделение.

Постройте инструменты проверки в первую очередь. Самая сложная часть поддержания системы памяти агента — это не индексирование или поиск — это знание, когда память неправильна. Мы построили представление проверки (скрипт, который показывает вам, что агент считает о данной теме) слишком поздно. Это должен был быть первый инструмент, который мы написали.

Система памяти — это одна из частей Claw-Stack, которой мы наиболее довольны. Это скучная инфраструктура, которая надёжно работает, что именно таким и должна быть память.