
Часть 1 из 3 — «Память для AI-агентов» Разбираем по косточкам миф про long-term memory в LLM-системах
3 часа ночи. Третья ночь подряд я отлаживаю AI-агента. Стою на кухне с кружкой чая, смотрю в дифф и тихо матерюсь. Агент уверенно переписал auth-функцию — на основе чанка, принадлежащего ветке, удалённой из репозитория два месяца назад.
Чанк живёт в Qdrant. Косинусная похожесть к моему запросу — высокая. Top-1 в retrieval. Агент честно его взял, честно вшил в промпт, честно сгенерировал «правильный» патч. Против кода из другой реальности.
Закрываю ноутбук и думаю: окей, у меня есть RAG. У меня есть векторы. У меня есть long-term memory. У меня есть всё, что обещает каждый AI-конференц-дек последние два года. Почему мой агент только что предложил фикс по коду, которого больше не существует?
Потому что у моего агента нет памяти. У моего агента есть результаты поиска с косинусом вместо BM25. И между этими двумя предложениями — вся разница между «AI, которому можно доверять в проде» и «AI, за которым надо нянчиться на каждой строке».
Этот текст — про эту разницу. И про то, почему мы, инженеры, виноваты в том, что перестали её видеть.
Девальвация слова «память»
Давай честно. Что такое типичная «память» AI-агента в 2026-м?
текст → разбивка на чанки 512–1024 токена
→ embedding (bge / text-embedding-3 / openai)
→ векторная БД (Qdrant / pgvector / Chroma / Pinecone)
→ cosine similarity top-k
→ конкатенация в промпт
Это не память. Это поиск. Это старый добрый Lucene из 2003-го, перекрашенный в нейронные цвета. Cosine вместо TF-IDF. Эмбеддинги вместо инвертированного индекса. Одно и то же.
Если бы это так и называли — «векторный поиск», «семантический retrieval» — у меня бы не было претензий. Назови Lucene Lucene, без проблем. Но когда это продают под флагом «мой AI имеет долговременную память» — извини. У моего AI одновременно дежавю и амнезия.
Это не придирка к терминологии. Это вопрос ожиданий. Когда инженер слышит «память», он представляет систему, которая помнит: кто что сказал, когда, в каком контексте, что было правдой тогда и что — сейчас. Когда инженер получает RAG, он получает Ctrl+F. И вместо того чтобы строить честную архитектуру вокруг этого Ctrl+F — с честными ограничениями — строит замок из песка и удивляется, почему агент путает прошлое с настоящим.
Три дыры, в которые проедет грузовик
Три конкретных провала. Каждый я ловил в проде. Не теория.
Дыра №1: чанк не знает, что он чанк.
Возьми обычное декларативное утверждение из design-документа:
«Перешли на JWT, потому что opaque-сессии не масштабировались под наш профиль трафика. Альтернативой были stateful-сессии с Redis-кластером, но мы их отклонили из-за audit-требований клиента — он не разрешает хранение состояния сессий вне периметра. JWT решает оба, но добавляет сложности с инвалидацией, которую мы митигируем коротким TTL и refresh-токенами».
Чанкер режет это на четыре куска по 512 токенов. На retrieval приходит запрос: «почему мы выбрали JWT?» Top-3 возвращает три фрагмента одного и того же решения. Без причинности. Без альтернативы, которую отбросили. Без trade-off, на который пошли.
Решение, которое было целым, превращается в три параллельных «фактоида». Модель честно сшивает их в правдоподобный текст — и выдумывает недостающие связки. Потому что её работа — генерировать правдоподобный текст. И она это сделает, не моргнув глазом.
Это не баг чанкера. Это архитектурное свойство всего подхода. Любая декларация решения превращается в порошок и пересобирается со структурными потерями. Каждый раз.
Дыра №2: в памяти нет структуры. Только косинус.
Когда человек объясняет тебе проект, он говорит:
- вот цель
- вот варианты, которые рассматривали
- вот что выбрали и почему
- вот что сломалось через два месяца
- вот что изменили, и теперь это решение перекрывает старое
В RAG ничего этого нет. Ноль. RAG не различает «гипотезу», «подтверждённый факт», «отвергнутую альтернативу», «deprecated-решение, перенесённое в архив». Для RAG всё это — эквивалентные точки в 384-мерном пространстве.
Представь, что ты пытаешься записать тридцать лет жизни в одну плоскую таблицу entries(text, vector) и потом искать по косинусу. Удивительно, что воспоминания сливаются? Это не твоя память подвела. Это структура, в которую ты её затолкал, — структура, которая не позволяет различить «я об этом думал» и «я это сделал», «я попробовал и сработало» и «я попробовал, и больно».
В RAG нет полей под эти различия. Не потому что разработчики не подумали. Потому что сама парадигма «вектор плюс расстояние» не вмещает причинность и время. Это математическое ограничение. Продуктовыми фичами его не лечат.
Дыра №3: у времени нет статуса first-class сущности.
Три недели назад я записал в память агента: «мы используем Postgres». Сегодня записал: «мигрировали на ClickHouse для аналитики, Postgres теперь только OLTP». В RAG оба факта лежат там. У обоих высокий косинус к запросу про БД. Top-k возвращает оба. Модель выбирает тот, что «звучит» лучше в её претрейне — обычно Postgres, потому что он встречается чаще в обучающих данных.
Это не память. Это рулетка, прикинувшаяся уверенностью.
Когда ты в последний раз видел поля valid_from, valid_until, deprecated_by, replaced_by, superseded_by в продакшен-RAG-системе? Я — никогда. Потому что в стандартном RAG их нет в схеме. И опять же — не потому что разрабы ленивые. Потому что у схемы «текст плюс эмбеддинг» нет места под жизненный цикл знания. Нет понятия «это правда сейчас» против «это было правдой тогда». Всё схлопывается в один временной срез — настоящее, которое каким-то образом содержит вчера, прошлый год и deprecated-три-квартала-назад одновременно.
Ctrl+F с эмбеддингами не помнит. Он находит. Разные глаголы.
«Но memory-фреймворки же это решают, да?»
Окей, говорит верующий. Есть mem0, Letta, Zep, Cognee, MemGPT — целый зоопарк long-term memory. Они добавили слой смысла поверх RAG. Они memory-aware.
Давай честно. Я их использовал. Один за другим. Долго. Смотрел под капот, не на лендинги.
Каждый из них берёт один кусок настоящей памяти — у кого-то это LLM-extraction перед записью, у кого-то buffer-иерархия как в OS, у кого-то post-hoc графовая экстракция из диалогов, у кого-то per-fact temporal validity — и реализует этот один кусок, не вплетая его в остальное.
Это теплее, чем ванильный Qdrant. Это не решение.
Потому что настоящая память требует семи свойств, работающих вместе. Каждое из них по отдельности уже существует в литературе или в опен-сорсе. Насколько я могу судить, никто не собрал все семь в одну систему. Какие именно семь — это часть 2 этой серии. Здесь — только ограничение, которое объединяет все flat-fact-решения, как бы они себя ни оборачивали:
Ни у одного из них нет права сказать «не знаю».
Покажи мне любую из этих систем с формальным abstain-механизмом: воротами, через которые факт не пройдёт в контекст промпта, если у него нет источника, нет confidence, нет temporal validity или есть нерешённое противоречие. Я подожду.
В стандартном flow всех этих фреймворков ответ системы на «в памяти противоречие или недостаточно данных» — «ну, модель разберётся». Что в переводе с маркетингового на инженерный означает «модель будет галлюцинировать, и это станет твоей проблемой в проде».
Хорошая память — это не «помнить много». Это знать границу того, чего ты не помнишь. Часть 2 этой серии построена вокруг этого тезиса.
«А почему просто не накачать контекст до 1М токенов?»
Это вторая мода последних двух лет, и она заслуживает отдельного разбора, потому что ведёт индустрию в тот же тупик под другим флагом. «Зачем нам память, если у Gemini 2М контекст, у Claude — 1М?»
Четыре проблемы, без преамбулы.
Один — экономика. Один проектный диалог на 800К токенов с выключенным prompt caching стоит десятки долларов за запрос. Без агрессивного кэширования ты разоришься за неделю. С агрессивным кэшированием ты строишь ровно ту же иерархию, что и Letta, — только дороже и привязанной к одному вендору.
Два — recall. Каждый long-context-бенчмарк (NIH, Ruler, LongMemEval) показывает одно и то же: модели тонут в собственном контексте после 200–300К токенов. Внимание распределено неравномерно. Это lost-in-the-middle, и оно не лечится размером окна — частично митигируется архитектурными трюками внутри модели, но не уходит. Чем больше ты впихиваешь, тем меньше реально учитывается.
Три — persistence. Контекст не сохраняется. Закрыл сессию — нет. Завтра тот же агент приходит с чистым контекстом. То есть тебе снова нужно скармливать ему 800К токенов «истории». Проблема не решена — она спрятана в твоём кошельке и в твоей latency.
Четыре — обучение. Если агент вчера ошибся, и ты его поправил, этот опыт не структурирован под будущее. Завтра он повторит ошибку. Контекст — это RAM, не диск. И когда кто-то говорит «просто увеличь контекст вместо того, чтобы строить память» — это всё равно что сказать «зачем мне база, у меня терабайт RAM». Технически слова рифмуются. По сути — несравнимые понятия.
Большой контекст не заменяет память. Он позволяет впихнуть в одну сессию больше — и всё.
Что делать с этим завтра утром
Если ты дочитал и думаешь «окей, согласен, RAG — это поиск, не память. Что теперь?» — у меня две новости.
Плохая: системно правильное решение требует переписать memory-слой от схемы до lifecycle, и это месяцы работы. Не выходные.
Хорошая: есть несколько вещей, которые ты можешь сделать завтра утром и уже снять половину боли. Не магия — просто инженерная гигиена.
- Убери слово «память» из стека, если у тебя RAG. Назови это retrieval или search — мгновенно честнее. Только это снимет 80% завышенных ожиданий пользователей и команды.
- Введи
valid_fromиvalid_untilдля каждого факта. Любой факт без temporal validity — это гипотеза, а не факт. Старые факты должны автоматически выпадать из retrieval, а не конкурировать с новыми по косинусу. - Различай
staging,working,consolidated,archived. Не сваливай всё в одну коллекцию. Только что прилетевший факт и знание, подтверждённое тестами, — это разные сущности с разным весом в retrieval. - Сделай abstain first-class исходом. Если ни один факт не прошёл confidence-порог при retrieve, система обязана иметь право сказать «не знаю, нужны данные». И это «не знаю» должно стать задачей в backlog’е, а не тупиком для пользователя.
Это не полный список — это минимум, чтобы стартовать переход от «у меня RAG, я называю это памятью» к «у меня память, и она знает свои границы». Полный список из семи принципов — в части 2.
Откуда это всё
Я сижу глубоко в этой кухне — Claude Code, Cursor, Codex, Windsurf, MCP-серверы, mem0, Zep, локальные RAG-стеки на Postgres + pgvector, Qdrant, Chroma. За последние несколько месяцев я перепробовал, пожалуй, всё, что есть на рынке. У меня свой MCP memory-сервер примерно на полторы тысячи записей, который я переписывал с нуля три раза, потому что каждый раз упирался в одну из трёх дыр выше.
В какой-то момент я устал. Не от AI — от того, что мы называем памятью у AI. Сел и начал писать свой cognitive runtime, который не претендует знать, который знает, чего не знает, и который сам ставит себе задачи на закрытие пробелов. Назвал braincore. Один Go-бинарь, локальный, MCP-stdio, Apache-2.0. Это не пича — он open-source — просто пример того, что я говорю «это можно сделать» не теоретически.
Семь архитектурных принципов, на которых он построен, — это часть 2 этой серии. Через неделю. Расскажу про atomic knowledge units, lifecycle, strict mode, causal decision chains, AST-идентичность кода, internal git как memory versioning, memory scoring и negative memory.
И почему всё это вместе даёт качественно другой результат, чем любой из этих кусков по отдельности.
Часть 3 — философская — про право AI-агента молчать и про то, почему правильная метрика для продакшен-AI — это не accuracy, а zero confidently-wrong actions at an acceptable abstain rate. Про self-tasking. Про то, почему cognitive runtime важнее размера модели.
Если ты дочитал и узнал себя в начальном абзаце — мы в одной лодке. Если у тебя RAG, который ты называешь памятью, и он работает — расскажи как, серьёзно, я хочу знать, может, я неправ.
Чего ты точно не можешь — это молчать.
Часть 1 из 3. Дальше — «Семь принципов настоящей памяти для AI-агентов» — выходит на следующей неделе.