Перейти к содержимому
PD
Вайб-кодинг 7 мин чтения

У вашего coding-агента амнезия: держите память проекта в репозитории, а не в чате

Coding-агент забывает проект в каждой новой сессии. Показываю систему из четырех файлов, которую я держу в каждом репозитории, чтобы решения, ограничения и недоделанная работа переживали смену сессий и моделей.

PD

Pavel Duglas

AI Automation & MVP Architect

Каждое утро я открываю новую сессию с coding-агентом, и он не знает о проекте ничего. Он не знает, почему мы выбрали Postgres advisory locks, а не Redis. Не знает, что вебхук платежей обязан быть идемпотентным. Не знает, что вчера я бросил на середине рефакторинг модуля парсера. Поэтому он додумывает. А уверенный в себе агент, который додумывает, - это аккуратный pull request с зелеными тестами, который тихо отменяет решение, принятое три недели назад по очень веской причине.

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

Почему каждая сессия начинается с нуля

Чат - это черновик. Закрыл сессию, и все, что ты объяснял час, исчезло. У некоторых инструментов есть свои функции памяти, но я на них не опираюсь, и причин три:

  • Они привязаны к одному инструменту. Я переключаюсь между агентами и моделями в зависимости от задачи и цены. Память, запертая у одного вендора, никуда со мной не переедет.
  • Их не видит команда. Подрядчик или второй агент, который работает параллельно, понятия не имеет, до чего додумался первый.
  • Они не версионируются. Их нельзя посмотреть в diff, отревьюить или откатить, когда они начинают врать.

Репозиторий уже решает все три проблемы. Он общий, переносимый, с историей и ревью. Не хватает только дисциплины записывать контекст.

Код отвечает на вопрос «что», но не «почему»

Агент прочитает всю кодовую базу за секунды. В этом и ловушка. Из кода он узнает что: функция делает три ретрая, у таблицы есть колонка для soft delete, модуль ходит в Telegram API через очередь. Но почему из кода не вытащить.

Почему три ретрая, а не пять? Потому что провайдер банит IP после шести ошибок в минуту. Почему очередь, а не прямой вызов? Потому что на рассылке мы уперлись в rate limit Telegram и потеряли сообщения. Вот это и есть самый ценный инженерный контекст в проекте. И именно его агент выбрасывает первым, когда решает что-нибудь «упростить».

Задача не в том, чтобы задокументировать все подряд. Задача - записать то, что код не может объяснить сам.

Четыре файла, которые лежат в каждом моем репозитории

Я держу четыре маленьких markdown-файла в корне проекта, иногда в папке /docs/agent. Названия не так важны. Важно разделение. Каждый файл отвечает на один вопрос. Как только начинаешь все смешивать, получается свалка, которую никто не читает, включая агента.

AGENTS.md: как здесь работать

Это инструкция по эксплуатации. Она отвечает на вопрос: как запустить, протестировать и выкатить проект и ничего не сломать?

  • Точные команды для установки, запуска, тестов и линтера.
  • Стек и версии, которые реально имеют значение.
  • Договоренности, которые не ловит линтер. Например: «все внешние HTTP-запросы идут через lib/http_client, там ретраи и логирование».
  • Жесткие правила: «никогда не править файлы в /generated», «не добавлять зависимости без согласования».
  • Ссылки на остальные три файла.

Держите его в пределах страницы. Если агенту нужно прочитать 800 строк, прежде чем запустить тесты, вы написали вики, а не инструкцию.

DECISIONS.md: журнал «почему»

Это облегченная версия architecture decision records. Каждая запись короткая:

## 2024-11-03 - Advisory locks вместо Redis для дедупликации задач
Контекст: после рестарта воркеров задачи выполнялись дважды.
Решение: Postgres advisory locks по id задачи.
Почему: на один сервис меньше, Postgres уже есть, нагрузка маленькая.
Пересмотреть, если: больше ~50 задач в секунду или уходим с Postgres.

Строка «Пересмотреть, если» - самая полезная часть. Она говорит агенту, что решение не священное, и прямо называет условие, при котором оно станет неправильным. Без нее агенты либо считают каждое старое решение законом, либо игнорируют их все.

INVARIANTS.md: то, что нельзя ломать никогда

Самый короткий и самый важный файл. Здесь свойства системы, которые должны выполняться при любом изменении:

  • Вебхуки платежей идемпотентны по event id провайдера.
  • Пользователь никогда не видит данные другого тенанта, даже в админских выгрузках.
  • Данные парсера не попадают в основную таблицу без прохождения валидации.
  • Мы не храним данные карт и полные номера паспортов.

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

STATE.md: где мы остановились

Одноразовый файл, который меняется постоянно. Текущая задача, что сделано, что сделано наполовину, что сломано и какой следующий шаг. По сути это записка, которую вы оставляете коллеге в пятницу вечером.

Сейчас: переводим парсер с BeautifulSoup на selectolax
Готово: карточка товара, страница категории
В работе: выдача поиска - селектор пагинации все еще нестабилен
Сломано намеренно: test_search_pagination пропущен, см. выше
Дальше: починить пагинацию, убрать skip, прогнать регрессию на 200 сохраненных страницах

Вот этот пропущенный тест агент без объяснений «починит» самым худшим из возможных способов.

Коротко, иначе протухнет

Главная беда памяти проекта - не нехватка информации, а устаревшая информация. Журнал решений, где написано, что мы используем Redis, хотя Redis выпилили два месяца назад, активно вводит агента в заблуждение. И агент с полной уверенностью поверит документу, а не коду.

Мои правила:

  • Один факт живет в одном месте. Если что-то написано в AGENTS.md, не дублируйте это в DECISIONS.md.
  • Удаляйте без жалости. STATE.md очищается, когда задача закрыта. Устаревшие решения помечаются как замененные со ссылкой на новую запись, а не остаются висеть молча.
  • Следите за объемом. Мой ориентир - около 150 строк на AGENTS.md и INVARIANTS.md вместе. Журнал решений может расти, но по умолчанию агенту нужны только свежие и действующие записи.

Пусть агент пишет сам

Что на самом деле заставило систему работать: файлы ведет агент, а не я. Писать документацию руками после длинной сессии - первое, что я пропускаю, когда устал.

Каждую содержательную сессию я заканчиваю одним и тем же промптом, примерно таким: «Перед тем как закончить, обнови STATE.md: где мы остановились. Если мы приняли решение, которое кто-то может потом откатить, добавь его в DECISIONS.md со строкой “Пересмотреть, если”. Если что-то из сделанного затрагивает инвариант, скажи мне».

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

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

Проверяйте, а не надейтесь

Документ - это совет. Агенты, как и люди, под давлением советы игнорируют. Поэтому каждый инвариант, который можно превратить в тест, становится тестом.

  • «Вебхуки идемпотентны» превращается в тест, который шлет одно и то же событие дважды и проверяет, что списание одно.
  • «Никаких данных чужого тенанта» превращается в тест, который делает запрос от тенанта A и проверяет, что строк тенанта B ноль.
  • «Не править сгенерированные файлы» превращается в проверку в CI, которая падает, если эти файлы изменились без запуска генератора.

В INVARIANTS.md рядом с каждым правилом я пишу название теста. Теперь агент знает правило, знает, зачем оно существует, и знает, какой именно тест упадет, если его нарушить. То, что протестировать нельзя, остается письменным правилом, и изменения рядом с ним я смотрю особенно внимательно.

Дешевое дополнение: небольшой CI-джоб, который предупреждает, если pull request трогает ключевые модули вроде биллинга, авторизации или миграций, но не трогает DECISIONS.md или STATE.md. Это только предупреждение. Но вопрос «а мы записали, почему?» начинает звучать каждый раз.

Пример из жизни

В одном моем SaaS на базе Telegram-ботов агент как-то предложил заменить очередь сообщений прямыми вызовами API, «чтобы уменьшить сложность». Код чистый, тесты зеленые, аргументация выглядела разумно. До появления файлов памяти я вполне мог бы смерджить это в загруженный день.

С DECISIONS.md агент сам заметил конфликт: там была запись о том, что прямые вызовы на рассылках теряли сообщения из-за rate limit, с пометкой «Пересмотреть, если: отказываемся от рассылок». Он спросил, актуально ли это. Было актуально. Пять минут чтения сэкономили инцидент в проде, на разбор которого ушли бы выходные.

Чего в этих файлах быть не должно

  • Секретов. Очевидно, но повторю. Эти файлы читает каждый инструмент, который вы подключаете.
  • Того, что код и так ясно говорит. Не описывайте каждую функцию. Дайте ссылку на модуль.
  • Архитектуры мечты. Описывайте то, что есть, и почему оно так. Планы - в STATE.md или в таск-трекер.
  • Длинных промпт-трюков. «Ты senior-инженер, который…» не нужно нигде. Факты и ограничения работают лучше, чем роли.

Начните сегодня

Никакой фреймворк для этого не нужен. За час можно:

  1. Создать AGENTS.md с командами запуска и тестов и пятью жесткими правилами.
  2. Записать три решения, откат которых разозлил бы вас больше всего.
  3. Выписать пять главных инвариантов и привязать каждый к тесту, а недостающие тесты дописать.
  4. Сохранить себе промпты для начала и конца сессии.

Код подешевел. Контекст вокруг него - нет. Положите этот контекст туда, где его прочитают ваши агенты и где его сможет отревьюить команда.

Вопросы и ответы

Чем это отличается от встроенной памяти в Cursor, Claude Code и других инструментах?

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

Не слишком ли это много для маленького MVP?

Для MVP хватит двух файлов: AGENTS.md с командами и жесткими правилами и STATE.md с текущей задачей. DECISIONS.md и INVARIANTS.md стоит добавить, как только появляются платежи, несколько тенантов или решения, которые вы не хотите объяснять повторно. Обычно это случается раньше, чем кажется.

Как не дать этим файлам устареть?

Поручите их обновление агенту в конце каждой сессии и проверяйте изменения как обычный diff. Очищайте STATE.md после закрытия задачи, помечайте замененные решения ссылкой на новую запись и добавьте в CI предупреждение, если pull request трогает критичные модули без обновления DECISIONS.md или STATE.md.

Похожие статьи