Память агента: файлы, дневники и векторный поиск

Каждое утро агент просыпается с пустой головой. Не потому, что он глупый: модель та же, веса на месте. Просто контекст обнулился. Вчера мы полдня чинили маршруты на роутере, а сегодня он впервые слышит про этот роутер. Ноль. Амнезия, как после удачной пятницы.
Контекстное окно — это не память. Об этом была отдельная статья, но повторю главное: можно впихнуть в окно хоть миллион токенов, и это всё равно будет оперативная память, которая очищается при перезагрузке. Настоящая память — это то, что лежит на диске и переживает рестарт. И тут у агента есть выбор: либо каждый раз начинать с чистого листа и наступать на те же грабли, либо что-то записывать.
У меня в лабе память агента — это три слоя. Не одна база, не один файл, а три, потому что задачи у них разные.
Слой 1. Дневники: memory/YYYY-MM-DD.md
Самый грязный и самый честный слой. Сырой лог дня: что делали, что сломалось, что починили, что осталось висеть. Файл на день, имя по дате. Никакой структуры — сплошной поток.
Примерно так выглядит запись хорошего дня:
Снёс контейнер с генерацией картинок — жрал память и падал по OOM. Бэкап оставил. Освободившийся адрес 198.51.100.18 отдал под видеонаблюдение.
Это не литература. Это черновик, который пишется на бегу, прямо в процессе. Ценность дневника — в сырости: он фиксирует то, что в моменте кажется очевидным, а через неделю уже нет. Какой порт у сервиса, что меняли, какой контейнер удалили и почему.
Обратная сторона: дневники быстро превращаются в свалку. Через месяц там куча записей, половина из которых устарела. Дневник — это не источник истины, это сырьё.
Слой 2. Кураторская память: MEMORY.md
Вот это уже источник истины. Один файл, в который периодически сворачивается всё значимое из дневников. Принцип простой: дневники — raw-лог, MEMORY.md — выжимка. То, что стоит помнить дольше недели.
Что туда попадает:
- решения — «локальные модели только для простых вопросов, для анализа — облачные»;
- грабли — «этот провайдер из-за рубежа отдаёт 403, не суй его в fallback-цепочку»;
- правила — «новый сервис сразу в мониторинг, не откладывай»;
- инвентарь — какой контейнер где живёт, какой адрес у какого сервиса.
В отличие от дневника, MEMORY.md чистится и переписывается. Устаревшее удаляется, дубли схлопываются. Это работа дворника: не копить, а выкидывать.
Главное правило, которое я вывел за пару месяцев: ментальные заметки не переживают рестарт. Всё, что агент «запомнил» внутри диалога, исчезает, как только сессия закрылась. Отсюда жёсткая формула: записал — значит помню. Не записал — значит не помню. Если что-то важно, оно должно оказаться в файле прямо сейчас, а не «потом». Потом не наступит.
Слой 3. Векторный поиск: «а что мы тогда делали с X?»
Первые два слоя отвечают на вопрос «что я знаю». Третий — на вопрос «где у меня это лежит». Потому что памяти накапливается больше, чем можно перечитать глазами.
Здесь в игру вступает векторный поиск. Идея знакомая по RAG: каждый кусок текста превращается в вектор (эмбеддинг), и похожие по смыслу куски оказываются рядом в векторном пространстве. У меня это работает через memory_search: задаёшь запрос «что мы делали с видеонаблюдением», а тебе возвращаются релевантные куски из MEMORY.md и дневников — независимо от совпадения слов.
Это принципиально. Обычный grep найдёт «видеонаблюдение» по точному вхождению. Векторный поиск найдёт и «камеру», и «NVR», и «RTSP», и «стрим с крыльца» — всё, что по смыслу про одно и то же, но написано разными словами. Для агента, который пишет память сам себе и не всегда одними словами, это спасает.
Эмбеддинги считает локальная модель — та самая bge-m3, про которую была отдельная статья в серии. Никакого облака, всё внутри лабы: и текст, и векторы, и поиск. Для домашнего сетапа это ещё и вопрос приватности: память агента содержит всё про твою инфраструктуру, отдавать её наружу не хочется.
Иерархия получается такая: дневник — сырьё, MEMORY.md — выжимка, векторный индекс — навигация по всему этому. Дневник пишется каждый день, выжимка обновляется раз в несколько дней, индекс пересчитывается при изменении файлов.
Зачем это на практике
Самый показательный кейс — про удалённый контейнер и заменённый роутер.
Представь: неделю назад снесли контейнер, который генерил картинки — падал по OOM, толку ноль. Снесли, бэкап оставили, домен из реверс-прокси убрали, адрес освободили. Прошло время, и в диалоге всплывает задача «запустить генерацию картинок». Агент без памяти полезет искать, жив ли старый контейнер, постучится по старому адресу, начнёт переустанавливать то, что мы сознательно удалили. Агент с памятью откроет заметку, увидит «удалён, бэкап лежит там-то, адрес занят другим» — и сразу предложит правильный вариант.
То же с роутером. Заменили старую коробку на новую, со старой всё сняли и затёрли креды. Если в памяти нет записи «старая коробка списана, к ней больше не ходить», агент рано или поздно попробует зайти на неё по старому адресу и будет недоумевать, почему молчит.
Тут важна не сама заметка, а её свежесть и достоверность. Память, которая врёт, хуже, чем её отсутствие. Поэтому записи о ликвидации я помечаю жирно: удалён, списан, больше не существует. Чтобы при быстром чтении глаз (и векторный поиск) сразу цеплялся за «НЕ ТРОГАТЬ», «УДАЛЁН», «ЗАМЕНЁН».
Чего в память класть не надо
Секреты. Пароли, токены, ключи — им место не в MEMORY.md, а в отдельном файле секретов, который не попадает в память и не участвует в поиске. В память кладём только указатель: «ключ от такого-то сервиса лежит там-то». Само значение — никогда, без явной просьбы владельца.
Логика простая: память — это то, что агент перечитывает и по чему ищет. Чем шире доступ к памяти, тем меньше туда должно попадать ценного. Если память когда-нибудь утечёт (а она утечёт — бэкапы, синхронизация, чужая сессия), пароли не должны утечь вместе с ней.
Итог
Память агента — это не фича из коробки, а дисциплина. Три слоя: дневник (сырой лог дня), MEMORY.md (выжимка значимого), векторный поиск (навигация). Правило одно: записал — значит помню. Остальное — тайм-менеджмент для файлов: не копить пустые плейсхолдеры, раз в несколько дней сворачивать дневники в выжимку, удалять устаревшее и никогда не тащить секреты в память.
Звучит скучно, но именно это отделяет агента, который наступает на одни грабли дважды, от агента, который помнит, что эти грабли мы уже убрали на прошлой неделе.