Дайте кодовому агенту хорошую модель и хорошие инструменты — и он всё равно спросит вас, четвёртый раз за неделю, зачем существует обёртка с ретраями. Он не тупит. Он действительно не знает, потому что ничего из его мира не пережило конец прошлой сессии.
Очевидное решение — файл с заметками, и очевидное решение протухает примерно за три недели. Этот пост о том, почему оно протухает и что строить вместо. Он вырос из слоя памяти в Hilum Tools, который я ежедневно гоняю против мультирепозиторного воркспейса — включая тот, в котором живёт этот сайт.
Режим отказа не тот, о котором вы думаете
Проектируя хранилище памяти, люди оптимизируют recall: найдёт ли агент заметку? Это не та тревога. Провал выборки громкий — агент спрашивает то, на что вы уже отвечали, вы это замечаете, отвечаете снова, жизнь продолжается. Раздражает, но ограничено и самокорректируется.
По-настоящему больно бьёт тихий отказ. Агент находит заметку; заметка неверна, устарела или является одной из пяти почти-копий, которые друг другу противоречат, — и агент действует с полной уверенностью. Вопроса вы не получаете. Вы получаете pull request, построенный на конвенции, от которой отказались два месяца назад, и узнаёте об этом на ревью. Или не узнаёте.
Почти всё это производят два механизма:
Почти-дубликаты дробят выборку. Пять слегка разных заметок про одну конвенцию разрывают сигнал. Каждая по отдельности выглядит авторитетно и несёт свою деталь. Та, что выиграет ранжирование, станет истиной этой сессии, и какая именно — вопрос подбрасывания монетки.
Устаревшие записи обгоняют актуальные. Заметка, написанная по свежим следам решения, подробна, хорошо сформулирована и богата ровно той лексикой, которая есть в вопросе. Её замена лаконична, потому что к тому моменту это уже все знали. Старая выигрывает по любой метрике похожести, какую ни назови. Свежесть — единственный сигнал, который бы вас спас, и именно ему большинство хранилищ даёт наименьший вес.
Оба отказа создаются на записи и лишь проявляются на чтении. Одно это наблюдение задаёт всю конструкцию.
Правило первое: правила работают на записи
Если вы наводите гигиену при чтении, вы пытаетесь чинить уже испорченный корпус — на горячем пути и с бюджетом по задержке. Это не работает и дорого стоит даже в попытке.
Наводите её на записи, где у вас есть время, весь корпус и автор ещё в контуре:
- Ловить почти-дубликат в момент записи и говорить о нём. Не молча сливать — сообщать. «Это на 0.91 похоже на запись от июня; может, обновить её?» Автор здесь и знает, что из этого правда. Слияние, решённое косинусной близостью, — это догадка, сделанная наименее компетентным для этого компонентом.
- Архивировать, никогда не удалять. Заменённая запись всё ещё отвечает на вопрос «почему раньше делали иначе» — а это ровно тот вопрос, который всплывает, когда кто-то предлагает снова сделать по-старому. Архивные записи уходят с дефолтного пути выборки и остаются доступными по запросу.
- Требовать тип. Бестиповый мешок текста не поддаётся рассуждению. Четырёх типов на практике хватает почти на всё, и у каждого свой срок жизни и свой вес в выборке.
| Тип | Что несёт | Срок жизни |
|---|---|---|
| Решение | Сделанный выбор, альтернативы и почему. Почему — это и есть полезная нагрузка: решение без него просто факт. | Долгий. Заменяется, не удаляется. |
| Грабли | Ловушка, за которую уже заплатили, и симптом, по которому она узнаётся. | Долгий. Самый ценный класс на байт. |
| Конвенция | Как здесь принято, там, где код этого не показывает сам. | Средний. Дрейфует, требует пересмотра. |
| Состояние | Что в работе, что last-green, что заблокировано. | Короткий. Обязано истекать, иначе становится ложью. |
С типом «состояние» ошибаются чаще всего. Запись «текущий статус» без срока годности становится активно вредной за считаные дни: это уверенное, конкретное и неправильное описание мира, и ровно на таком агент действует не задумываясь. Либо у неё есть срок, либо она выводится из ground truth (git, CI, трекер), а не хранится.
Правило второе: чтение автоматическое, запись осознанная
Асимметрия здесь и есть суть.
Чтение должно быть бесплатным. Если агенту нужно решать, обращаться ли к памяти, он пропустит её ровно тогда, когда будет под давлением, — то есть ровно тогда, когда она бы помогла. Брифинг приходит на старте сессии, спрашивали его или нет: действующие конвенции, недавние решения, известные грабли в области, которую трогают, текущее состояние. Один вызов, один ответ в бюджете.
Запись должна быть решением. Агент, пишущий заметку после каждой задачи, производит корпус, состоящий преимущественно из пересказанных постановок задач, — и такой корпус хуже, чем никакого: он размывает сигнал и раздувает каждую выборку. Планка записи — один вопрос: ошибётся ли без этого компетентный инженер, пришедший завтра? Ответ «нет» — не пишем.
Отсюда получается короткий список того, чего хранить не нужно никогда, и этот список делает для качества больше, чем любое улучшение ранжирования:
- Всё, что уже записано в репозитории. Структура кода, раскладка файлов, список зависимостей, что делает функция. Для этого есть кодовая интеллектуальность; дублирование в памяти гарантирует, что копия протухнет, а оригинал — нет.
- Всё, что уже записано в git. Кто что менял, когда и с каким сообщением.
- Нарратив сессии. «Попробовал X, не вышло, попробовал Y» — это контекст текущего разговора, а не знание. Оно умирает вместе с сессией, и правильно делает.
- Всё, что уже лежит в файле правил или в ADR. Ссылаться, а не пересказывать. Пересказанный факт — второй источник истины, а второй источник истины — будущее противоречие.
Правило третье: область — часть записи
Заметка про «нашу конвенцию сообщений коммита» истинна в одном репозитории и ложна в соседнем. Заметка про «как я люблю, чтобы было устроено ревью» истинна во всех репозиториях, к которым этот человек прикасается. Хранение обеих в одной недифференцированной куче означает, что одна из них утечёт в неверный контекст и будет там неправа с полной уверенностью.
Трёх областей достаточно:
- Репозиторий — едет вместе с репо, разделяется со всеми, кто его клонирует.
- Хост-глобальная — эта машина, этот оператор, все проекты. Предпочтения, особенности окружения, личный рабочий процесс.
- Команда / организация — общая между участниками и репозиториями; единственная область, которой нужен контроль доступа.
Ошибитесь здесь — получите две классические жалобы на агентскую память: «он тащит сюда правила другого проекта» и «мне приходится учить его одному и тому же в каждом репозитории».
Правило четвёртое: хранилище отчитывается о собственном здоровье
Память — та единственная подсистема, где доверие и есть продукт. Значит, её нужно уметь инспектировать, и инспекция должна быть достаточно дешёвой, чтобы её реально делали.
Считайте и показывайте: количество записей по типам и областям; число кластеров почти-дубликатов; отношение архивных к активным; распределение по возрасту — в частности, какая доля записей состояния просрочена; и счётчик противоречий — пары активных записей, утверждающих противоположное об одном и том же.
Последнее — это сигнал тревоги. Хранилище с противоречиями — не хранилище с небольшой проблемой качества; это хранилище, каждый ответ которого теперь подбрасывание монетки, и отдавать эти ответы оно будет тем же уверенным тоном, что и раньше.
Что мерить
Честные метрики слоя памяти — поведенческие, а не внутренние:
- Доля повторных вопросов. Как часто агент спрашивает то, на что корпус уже отвечает. Падает быстро, когда память начинает работать; самый лёгкий выигрыш для демонстрации.
- Доля повторных ошибок. Как часто наступают на уже записанные грабли. Двигается медленнее, стоит намного больше.
- Токены холодного старта. Токены между началом сессии и первой полезной правкой. Брифинг должен схлопывать это кратно — в этом весь экономический аргумент.
- Счётчик противоречий. Должен быть нулём, и любое ненулевое значение обязано быть видно без похода на поиски.
Коротко
- Проектируйте против уверенной неправоты, а не против «не нашлось». Провал выборки громкий и самокорректирующийся; устаревшая уверенность тиха и накапливается.
- Наводите гигиену на записи, при живом авторе. Чтение — уже поздно и слишком дорого.
- Сообщайте о почти-дубликатах вместо слияния. Архивируйте вместо удаления.
- Типизируйте каждую запись. Состоянию — срок годности или вывод из ground truth.
- Читать автоматически, писать осознанно. Никогда не хранить то, что уже записано в репозитории или в git.
- Проставляйте область каждой записи: репо, хост, команда.
- Заставьте хранилище отчитываться о своём здоровье и считайте противоречия аварией.
Память закрывает то, что вообще не должно было попадать в поиск. Материал, который в поиск попадать обязан, — тема поста про выборку под бюджет токенов; запуск нескольких агентов на одном репозитории без взаимных наступаний — следующая задача в этом ряду. Если хотите такой слой в собственном стеке — это направление AI-разработка.
