
Большинство пилотов с ИИ-агентами не доходят до продакшена по одной причине — у агента нет памяти. Каждая новая сессия начинается с нуля — пользователь заново объясняет контекст, задачи и историю. На демо это терпимо, но в реальной работе быстро становится ясно: без персистентной памяти это не ассистент, а дорогой калькулятор.
В открытых источниках тема памяти раскрыта поверхностно: либо «добавьте векторную базу», либо набор модных фреймворков без объяснения, зачем они нужны. Ниже — практический разбор: уровни памяти, рабочие архитектуры, типовые ошибки и причины, почему в большинстве кейсов достаточно pgvector. Материал будет полезен ML-инженерам и backend-разработчикам, которые сейчас затаскивают ИИ-агентов в продакшен, а также техлидам и архитекторам, которые проектируют такие системы.
Привет, Хабр! Меня зовут Владислав Янковский, я технический лидер продукта Evolution AI Agents в Cloud.ru. Занимаюсь тем, что кажется одной из самых недооцененных инженерных задач в теме агентов, — построением персистентной памяти. Дальше расскажу, как мы к этой задаче подходим у себя.
Первое, что важно проговорить, — память не является самоцелью: это следствие функциональных требований продукта.
Когда мы у себя решаем, нужна ли конкретному агенту память, то задаем один вопрос: что именно он должен помнить между сессиями и зачем? Если прогнать сценарий использования и оказывается, что без памяти он не ломается, то память не нужна.
Пара примеров, чтобы было наглядно.
Есть агент, он работает с Grafana через MCP. Пользователь пишет: «Покажи мне дашборд по такому-то сервису». Агент идет в Grafana, выгружает данные, отдает ответ. Между сессиями ему помнить нечего: каждый запрос самодостаточный. Персистентная память здесь избыточна и только усложнит архитектуру.
Другой сценарий — агент поддержки уровня L2. Пользователь заводит тикет, они разбираются по нему в течение нескольких сессий. Здесь память нужна как минимум внутри одного тикета: анализ, промежуточные результаты, уточняющие вопросы. А если добавить факты о самом пользователе: его окружение, предыдущие проблемы, предпочтения по коммуникации, — память становится критичной.
Отсюда правило, которое мы держим в голове: память — это результат ответа на вопрос «Что должен делать агент?», а не самостоятельный технический элемент, который мы приделываем, чтобы было.
И это самая частая ошибка команд, которые начинают агентские проекты. Они подключают память там, где не надо, и при этом не подключают там, где надо. Это касается любой разработки: если бизнес-требование не выходит из функциональности, то невозможно объяснить причину, зачем это делать.
Важное разграничение, которое стоит зафиксировать вначале.
Контекст — это то, что помещается в окно модели и участвует в каждом запросе.
Память — это то, что живет вне модели. Она может подгружаться выборочно — не вся сразу, а по релевантности к текущему запросу.
Здесь нужно разграничить два уровня решений. Первый — что достать из памяти в конкретный момент. Это делает не сама LLM — снаружи нее работает retrieve-механика, которая по текущему запросу подбирает релевантные факты и подмешивает их в промпт. Ниже я разбираю, как этот хук устроен архитектурно. Второй — что вообще запоминать после сессии. Это тоже отдельная механика, а не «модель сама разобралась». О ней тоже будет отдельный раздел.
В большинстве продуктовых сценариев LLM работает с уже подготовленным материалом, а не решает самостоятельно, что достать из хранилища или что туда положить. Это принципиально: чем больше решений о retrieval и записи вы отдаете модели, тем больше пространства для ошибок, а ошибки памяти в проде обходятся дорого.
Есть и обратный подход — agentic memory management, когда модель сама управляет своей памятью, примерно как ОС управляет страницами. Это работающий, но продвинутый паттерн. Он оправдан для долгоживущих автономных агентов, которые часами или днями работают над одной задачей. Про инструменты вроде Letta, реализующие эту логику, я расскажу ниже. Для большинства же обычных продуктовых кейсов начинать с этого не стоит.
В источниках память классифицируют по двум разным измерениям, и их важно не путать. По сроку жизни — short-term, working, long-term. По типу содержимого — semantic, episodic, procedural. Это два разреза: любой фрагмент памяти можно описать координатой в обоих измерениях. Пройдусь коротко по всем типам, с примерами из практики.
По сроку жизни
Short-term и working — это то, что «в голове» у агента прямо сейчас: текущий диалог, промежуточные шаги во время выполнения задачи. Для агента это контекстное окно плюс небольшой буфер сессии. Живет мало — от нескольких минут до нескольких часов. Часть этой памяти дальше суммаризируется и уходит в долгосрочную, часть выбрасывается как неактуальная.
Long-term — то, что переживает сессии и живет долго. Именно сюда идут стабильные факты о пользователе, накопленный опыт, выученные навыки. Обновляется по-разному: что-то — редко и точечно, что-то — постоянно.
По типу содержимого
Semantic — семантическая память. Факты о мире и пользователе: имя, часовой пояс, роль в компании, предпочтения. Сюда же — предпочтения по формату вывода. Например, «Пользователь хочет получать выгрузку из Jira в Markdown, потому что дальше обрабатывает ее другим агентом». Это семантический факт, а не навык. Он живет долго, то есть по сроку жизни это long-term, но по типу — семантика.
Episodic — эпизодическая память. События по временным меткам: «Вчера пользователь пожаловался на такое-то поведение, мы его починили тем-то». Это уже не «факт вообще», а «факт с привязкой ко времени». На практике я с ней в чистом виде не работал, но она частично проявляется в других механизмах (о них расскажу дальше), в частности — в разрешении конфликтов.
Procedural — процедурная память, то есть навыки «как что-то делать». Хороший пример: у агента есть скил с какой-то переменной в определенном формате. Агент начинает его использовать, натыкается на ошибку, пробует обходные пути. В итоге находит рабочий вариант и запоминает саму последовательность действий, которая привела к успеху. В следующий раз идет сразу по этому пути. Обратите внимание: запоминается именно способ действия, а не факт о пользователе.
По моей практике, в 80% продуктовых задач нужна связка short-term + long-term semantic. Short-term есть всегда как управление контекстом текущей сессии. Long-term semantic — это факты о пользователе и мире, которые дают персонализацию. Episodic и procedural — это уже более продвинутые истории, к которым имеет смысл идти, когда основа работает.
А теперь — к работе с памятью. Для удобства понимания разделил ее на уровни.
Самое простое — сохранять историю сообщений между сессиями и при новой сессии подгружать ее в контекст. Это работает для узкого класса задач, где важна сплошная непрерывность и объем диалогов небольшой.
Но подавляющее большинство кейсов тут ломается по одной простой причине.
Представьте, что вы полгода переписываетесь с человеком в Telegram. Ваша память о нем состоит не из полной истории чата. Вы не помните каждое сообщение дословно. Вы помните ключевые факты: где он работает, как зовут его собаку, когда он планирует переезд.
Если он рассказал вам одну и ту же историю три раза, у вас в голове не три копии, а одна консолидированная запись. Если он сменил номер телефона, старый вы инвалидировали, а не хранили как альтернативу.
С агентом ровно так же. Просто хранить сырые сессии — это не память. Настоящая память подразумевает извлечение фактов, дедупликацию, разрешение конфликтов, инвалидацию устаревшего.
Но здесь есть важный нюанс: извлечь факты не значит выбросить оригинал. LLM может неправильно понять реплику, интерпретировать иронию как утверждение, потерять контекст. Если оригинал удален, то вернуться и исправить память уже невозможно, ошибка становится «истиной по умолчанию». Правильная модель — двухслойная: исходное событие (или ссылка на него) хранится отдельно, извлеченный факт — отдельно, между ними есть прослеживаемая связь. Тогда на любом этапе можно вернуться к первоисточнику и переизвлечь факты заново или исправить ошибку.
Следующий шаг — семантический поиск по накопленной истории через эмбеддинги. Это нужно для нечетких, ассоциативных запросов:
Похожие ситуации, с которыми пользователь уже сталкивался.
Что-то на эту тему было раньше.
В каком контексте пользователь упоминал такую-то концепцию.
Расскажу коротко про основные варианты, с которыми имеет смысл разбираться.
pgvector — расширение Postgres для векторного поиска, наше дефолтное на большинстве проектов. Покрывает подавляющее большинство кейсов. Если у вас в стеке уже есть Postgres, подключение pgvector — это буквально пара кликов на нашей платформе через Managed Postgres. Дальше работаете с ним как с обычным Postgres.
Плюс в том, что можно делать гибридные запросы: векторный поиск + фильтры по метаданным в одном запросе. «Найди похожие по смыслу записи только для этого пользователя и только свежие» — это одна SQL-конструкция, без склеек между двумя разными системами.
Qdrant — это уже чистый векторный поиск, написан на Rust, self-hosted. Имеет смысл, когда векторный поиск — основная нагрузка, нужна высокая пропускная способность, сложные фильтры по метаданным, десятки или сотни миллионов векторов.
Что касается pgvector, то производительность на таких объемах — это вопрос конфигурации. Если у вас HNSW-индекс, RAM под него достаточно, а запросы не слишком селективные по метаданным, то pgvector нормально живет и на приличных объемах. Основная реальная боль — recall при селективных фильтрах: post-filtering режет качество выдачи, потому что векторный поиск сначала возвращает ближайших соседей, а потом уже отсекает по условиям. Часть проблемы решена в pgvector 0.8.0. Но именно эта история (а не «медленный поиск») обычно и становится причиной, по которой команды переходят на Qdrant или что-то еще.
Milvus — уже для миллиардов векторов. Честно, у меня не было проектов такого масштаба, поэтому в бою не пробовал. Но если у вас реально миллиарды векторов, смотрите в его сторону.
Pinecone — serverless-история, платная, не self-hosted, данные хранятся за пределами РФ. Для российских проектов это делает ее неприменимой в большинстве сценариев с персональными данными или коммерческой тайной. В остальных случаях Pinecone имеет смысл, только если вам критично не заниматься инфраструктурой вообще.
Мой прагматичный совет — начинайте с pgvector. Если через полгода упретесь в производительность на объемах, тогда переезжайте. Но большинство проектов до этого не доходят.
Основная ошибка на этом уровне — сохранять в векторное хранилище сырые реплики диалогов. Это тот же уровень 0, только с семантическим поиском поверх. Работает плохо: реплики без контекста несут мало смысла, консолидации нет, дубликаты копятся.
Правильный подход — сохранять извлеченные факты, а не реплики. О том, как это делать, расскажу в следующем разделе.
Векторная память отлично работает на нечетких вопросах. Но есть класс задач, где нужны точные ответы:
Какая роль у этого пользователя?
Что мы делали последние 20 минут?
Что запрашивал пользователь такого-то числа?
Ассоциативный поиск не подходит — нужна детерминированная выборка.
Здесь на сцену выходит структурированная память. Два основных варианта — SQL и графы.
SQL-подобная структура — key-value-хранилище с атомарными фактами. Одна запись — одна актуальная версия. Профиль пользователя, его роль, ключевые предпочтения, текущий статус задачи. Дешево запрашивается и детерминированно возвращает результат.
Графы — история поинтереснее. Узлы — сущности, ребра — факты и отношения. У ребер могут быть окна валидности («Этот факт был верен с такого-то по такое-то время»). Графы отлично работают там, где нужно понимать эволюцию фактов и отношения между сущностями.
На этой логике построен Graphiti — опенсорс-движок для темпоральных графов знаний под Apache 2.0. Работает поверх Neo4j или FalkorDB. Поверх Graphiti собран коммерческий сервис Zep. Сама Community Edition Zep была задеприкейчена в апреле 2025-го, а сейчас доступен только облачный вариант. Мы сами с графами пока не работали в продовой нагрузке, но опенсорс-проекты в этой области подтверждают закономерность: превращение репозиториев кода в графовую систему заметно ускоряет и удешевляет работу агента-помощника и уменьшает количество его ошибок. Из того, на что имеет смысл посмотреть, — GitNexus и RepoGraph. Оба строят граф над кодом репозитория и используют его как контекст для LLM-агентов.
На практике самое рабочее — комбинировать оба слоя.
Структурированный слой хранит атомарные факты: профиль пользователя, ключевые предпочтения, актуальные статусы. Один факт — одна актуальная запись.
Векторный слой хранит эпизоды и опыт, то есть то, что нужно для ассоциативного поиска.
При запросе делаем так: сначала точно достаем структурированные факты (это дешево), потом добавляем семантически похожие эпизоды из векторки — и все это отдаем в контекст.
Здесь у pgvector есть отдельное преимущество — и структуры, и векторы живут в одной ACID-системе, все можно джойнить в одном запросе. Не нужно синхронизировать две разные базы, не нужно ловить рассинхронизацию.
Теперь разберем, что и как хранить, как обновлять и как удалять.
Первое правило — запоминать факты, а не реплики. Через LLM-проход экстрактор идет по диалогу и выделяет атомарные утверждения:
Пользователь сказал: «Думаю, наверное, переехать в Питер».
Кандидат в память: «Рассматривает переезд в СПб (не уверен)».
Это принципиально важно. Дословный текст — это не то, что нужно вашей будущей коммуникации: вам нужна суть.
Но извлечение факта не отменяет хранения оригинала. И факт, и исходная реплика хранятся вместе, между ними — ссылка. Если через месяц окажется, что LLM ошиблась в интерпретации, например приняла шутку за серьезное утверждение, вы всегда сможете вернуться к оригиналу и переизвлечь заново. Если оригинал удален, ошибка становится частью истории, проверить ее нельзя.
Здесь снова помогает аналогия с человеком. Когда вам что-то рассказывают, вы не запоминаете фразу слово в слово, а вычленяете суть: «Иван думает про переезд, но не решил». Дословный текст — это не то, что нужно вашей будущей коммуникации с ним.
Правила устойчивости. Запоминать имеет смысл то, что с высокой вероятностью пригодится в будущих сессиях. Это стабильные факты, предпочтения, повторяющиеся паттерны. «Сменил номер телефона, теперь такой-то» — запоминаем. «Каждый день просит выгрузку в Markdown» — запоминаем после нескольких повторений.
А вот «Сегодня рассказал, как прошел день» — скорее всего, не стоит. Это не влияет на работу агента и просто будет забивать контекст.
Уверенность в факте. Разные факты имеют разные веса. «Думаю переехать в Питер» — низкий вес, не запоминаем сразу. «Переехал в Питер, снимаю квартиру» — высокий вес, можно записать. Дальше этот факт может использоваться агентом в других сценариях, например при поиске транспорта или доставки, чтобы предлагать питерские варианты, а не московские.
Часто хочется, чтобы агент подключался к разным системам: банку, почте, мессенджерам, календарю. Соблазн — пусть агент себе все это подгрузит и запомнит.
Не надо так: это антипаттерн.
Правильно: агент должен помнить, где он может посмотреть данные, а не хранить их у себя. Пошел в API, посмотрел, использовал, забыл. Хранить копию — это и утечки, и рассинхронизация, и раздувание памяти. То, что уже есть в системе-источнике, должно оставаться в источнике.
Пользователь вчера сказал одно, а сегодня — другое. Что делать? Простой ответ «Перезаписать сразу» — плохой. Нужно сравнить факты и понять, что происходит:
Дубликат. Игнорируем.
Уточнение. Мержим факты, получаем более полную запись.
Противоречие. Инвалидируем старый факт (не удаляем!), записываем новый.
Именно на этом шаге появляется episodic-логика, даже если вы ее явно не заводили. У факта появляется окно валидности: «Этот факт был верен с такого-то по такое-то время». Мы храним историю событий по каждому факту, и это дает возможность отвечать на вопросы: «Когда это изменилось?» и «Что было раньше?»
Помимо этого, учитываем два дополнительных сигнала.
Источник факта. В мультиюзер-сценариях важно понимать, кто именно сообщил новую информацию: веса разные.
Свежесть предыдущего факта. Если факту день, а пользователь его меняет, это подозрительно, стоит уточнить. Если факту год, это нормальное обновление.
При явных противоречиях агент должен уточнять у пользователя, а не гадать самостоятельно. Это довольно очевидное правило, но в реализациях о нем часто забывают.
Рост памяти — нормальное явление. Количество фактов растет, количество контекста растет. Задача не в том, чтобы этого избежать, а в том, чтобы держать под контролем. И здесь работают четыре механики, применяемые в комбинации:
Экстракция вместо накопления. Хранить структурированные факты, а не сырые диалоги — это единственный способ получить сублинейный рост памяти вместо линейного. Но важно не путать: «в основной оборот идут факты» не означает, что «оригиналы выбрасываются». Оригиналы диалогов остаются в архиве со ссылками из фактов. При необходимости к ним можно вернуться. Просто в горячей выборке для контекста работают уже извлеченные факты, а не сырой текст.
Дедупликация и консолидация. Пример: пользователь пять раз в разных сессиях упоминал, что каждое утро пьет зеленый чай и заказывает его через нас. Это пять разных фактов, только если хранить дословно. Консолидируем — получаем один факт: «Предпочитает зеленый чай, заказывает по утрам через нас».
Иерархическая суммаризация. Диалоги недельной давности превращаются в недельную сводку, а месячной давности — в месячную сводку. Детали теряем — суть сохраняем.
TTL по давности обращения. Оцениваем факты по времени последнего использования — работает как кеш. Если к факту не обращались очень долго, то, скорее всего, он уже неактуален и его можно увести в архив/cold-tie. Тут важна аккуратность — политика забывания настраивается индивидуально под класс проекта.
Приватность — отдельная большая тема, особенно чувствительная в России из-за 152-ФЗ. Здесь я расскажу, как мы ее решаем у себя, без претензии на исчерпывающий гид.
Начинается все с изоляции. У нас это реализовано через user_id и tenant_id прямо на уровне Postgres — фильтрация происходит в самом запросе, а не постфактум. Разница принципиальная: если вы фильтруете постфактум, значит, вы уже подняли данные из соседнего тенанта в память приложения. Это уже утечка, даже если по итогу вы их не отдали пользователю. Правильно, чтобы данные соседнего тенанта вообще никогда не покидали базу.
Дальше — право на удаление. Один факт в архитектуре, которую мы обсуждали выше, живет в нескольких местах: в исходном эпизоде, извлеченном факте, эмбеддинге, производных фактах, саммари. Именно поэтому удаление — сложная операция: нужно проходить по всей цепочке производности, иначе вы удалите одну копию, а остальные останутся. Здесь как раз проявляется практическая ценность графового представления фактов. Оно наглядно показывает, что от чего производно, и удаление превращается из угадывания в обход графа.
Отдельное правило, которое я бы называл главным, — минимизация на входе. Чем меньше вы сохраняете изначально, тем меньше шансов, что что-то утечет. Простая мысль, но ее систематически нарушают — сохраняют на всякий случай все, до чего дотянулась LLM-экстракция. Правильный подход обратный — экстрактор должен работать с высоким порогом устойчивости, и все, что не проходит порог, просто не попадает в память.
Еще одна тонкая история — работа с секретами и чувствительными данными. Тут важно то, что модель вообще не должна видеть секреты. Токены, пароли, ключи API — все это никогда не должно попадать ни в промпт, ни в контекст, ни в память. Если агент прочитал секрет, он потенциально может его вывести пользователю, передать другому инструменту или засветить в логах.
Правильная схема — отдельный сервис-посредник, который сам держит секреты и сам подставляет их в запросы к внешним системам. Агент вызывает этот сервис через безопасный интерфейс: «Сходи в такой-то API от моего имени и верни результат». Сервис делает вызов с нужным секретом, возвращает агенту уже очищенный от чувствительной информации результат. Модель работает только с результатом, и самих секретов она не видит никогда.
Для остальных категорий данных: обычных предпочтений пользователя, истории задач, статусов — разделение хранилищ с гранулярными правами доступа работает как обычная мера безопасности. Но именно секреты — отдельная история, где принцип «Модель не видит» строже, чем «Модель видит, но не выносит».
По хостингу для российских проектов выбор небольшой: либо self-host, либо провайдер вроде Cloud.ru. Иностранные managed-сервисы напрямую не подходят: данные не должны покидать РФ. С self-host в 2026 году всё сложнее: обслуживание серверов, команда, компетенции. Мало кто может это себе позволить, и для большинства проектов это уже плохо оправданно экономически.
И еще два инфраструктурных момента.
Первый — аудит доступа. Логируйте, кто и когда получал доступ и к каким данным. Без этого при инциденте вы просто не сможете разобраться, что произошло, и восстановление превратится в гадание.
Второй — версии зависимостей. В Postgres иногда находят уязвимости, которые можно эксплуатировать. Устаревшие пакеты — типичная точка входа, но команды часто про это забывают, потому что «база работает и работает». Работает, пока не перестанет работать по чужой воле.
На рынке уже есть достаточно опенсорс-решений для памяти, и меня часто спрашивают, что из этого брать. Разберу основные.
mem0 — гибридная система памяти. Под капотом — векторное хранилище, графовая память и LLM-пайплайн для извлечения и обновления фактов. Подключается через SDK или API, кросс-сессионная персонализация работает практически из коробки. Опенсорс-ядро, есть облачная версия. Подходит командам, которым нужно быстро сделать агента с памятью пользователя, не переписывая под это архитектуру.
Отдельно стоит сказать: тот цикл «извлечение факта → сравнение с существующими → дубль/уточнение/противоречие», который я подробно разбирал в разделе про уровень 3, mem0 из коробки уже реализует. Если вам нужен именно такой контур и не хочется собирать его самостоятельно, это готовое решение. Если же вы хотите большего контроля над логикой извлечения и обновления фактов, стройте свой слой поверх pgvector, как мы сделали у себя.
Graphiti + Zep — темпоральный граф знаний. Graphiti (Apache 2.0) — self-host-движок, работает поверх Neo4j или FalkorDB. Zep — облачный managed-сервис поверх Graphiti; собственная Community Edition Zep больше не развивается. Актуально для проектов с эволюцией данных и требованиями к аудиту: compliance, финансы, что-то подобное.
Letta — полноценный агент-рантайм, где модель сама управляет своей памятью так же, как страницами в операционной системе. Опенсорс. Нужен для долгоживущих автономных агентов, которые сами редактируют свой контекст и работают над многодневными задачами.
Отдельно упомяну managed-сервисы. Они имеют смысл, когда надо сделать быстро и инфраструктурной команды нет, или когда данных еще немного (стартап, MVP), или когда важна локализация под 152-ФЗ. Первый и второй пункты объясняют себя сами. Третий особенно актуален у нас в стране: даже если у вас есть команда и данные, соответствие требованиям российского контура — авторизация, логирование, размещение в РФ — проще получить у российского провайдера, чем строить самостоятельно.
У нас в Cloud.ru есть отдельная штука, которая помогает начать быстро: через UI можно собрать связку MCP → агент → оркестратор → система агентов, не разбираясь в инфраструктуре под капотом. Это удобно, когда команда готова разобраться в конфигурации, но не готова разворачивать всё с нуля.
Главный принцип в том, что память не должна размываться по логике агента. Она должна быть отдельным слоем на границах кода, за интерфейсом, который позволяет менять реализацию без переписывания всего остального.
Универсальный паттерн интеграции на входе шага простой: хук retrieve достает из памяти релевантные факты и подмешивает в промпт.
А вот с записью все сложнее, и здесь важно проговорить одну вещь отдельно: автоматически записывать в память все, что производит LLM после каждого шага, нельзя. Это одна из главных ошибок в текущих реализациях. Если делать так, в память попадает все подряд: ложные факты, которые LLM неправильно извлекла из диалога, ошибочные результаты инструментов, вредоносные инструкции с сайта, из письма или API-ответа, которые агент прочитал в процессе работы, данные другого пользователя, если изоляция неидеальна.
Правильная модель — двухшаговая. LLM только предлагает кандидатов в память: «Вот факт, который я извлекла, вот его источник». Дальше отдельный процесс проверяет каждого кандидата по нескольким осям:
Источник. Пришло из диалога с пользователем, из ответа доверенного инструмента или из внешнего API? Внешние источники — гораздо ниже приоритет, так как промпт-инъекции обычно приходят именно оттуда.
Идентичность пользователя. Точно ли этот факт относится к тому пользователю, о котором мы думаем? Не подменил ли контекст соседний тенант?
Достоверность. Насколько LLM уверена в извлечении? Не является ли это интерпретацией шумной фразы?
Чувствительность. Не относится ли факт к категории, которая по политике вообще не должна попадать в персистентную память?
Только после прохождения проверок кандидат превращается в факт и записывается в хранилище.
По конкретным фреймворкам — коротко. У LangChain раньше была встроенная абстракция Memory с классами вроде ConversationBufferMemory и родственниками, но все они задеприкейчены еще в версии v0.3 и удалены в 1.0. Официальная рекомендация сейчас — использовать LangGraph persistence: checkpointer для короткоживущей памяти внутри одного треда и Store для кросс-тредовой долгосрочной памяти. То есть даже сам LangChain в этом смысле отсылает пользователя к тому же паттерну, о котором я говорю, — к внешнему слою памяти за интерфейсом, а не встроенному буферу сообщений. Дальше внутри этого Store уже удобно поднимать mem0 через официальную интеграцию. Тогда вы получаете не просто хранилище, а полноценный контур управления фактами.
LlamaIndex удобно использовать как retrieval-движок над памятью, но слой экстракции и обновления фактов лучше держать отдельно от него: так проще эволюционировать логику работы с памятью независимо от того, как устроен поиск.
И одно важное правило поверх всех фреймворков: не привязывайте жестко память к конкретному фреймворку. Область меняется очень быстро, обратную совместимость соблюдают не все, и то, что сегодня выглядит удобным API, через год может потребовать миграции. Один слой абстракции между вашим кодом и фреймворком стоит недорого, а сохраняет вам возможность поменять инструмент без переписывания половины системы.
Если после всего этого хочется сесть и начать делать, то рекомендую пройти такой путь:
Задайте себе вопрос: нужна ли этому агенту память вообще? Прогоните сценарии использования без памяти. Если они не ломаются — останавливаемся.
Определите, что именно агент должен помнить. Предпочтения пользователя, факты о задаче, историю решений, промежуточные результаты. Из этого списка вырастает архитектура.
Начните с pgvector. Если он у вас в стеке уже есть — просто добавьте расширение. Если нет — берите Managed Postgres: разворачивается за минуты.
Извлекайте факты, но храните и оригиналы. Настройте LLM-проход после сессии, который извлекает атомарные утверждения из диалога. Оригинальные реплики оставляйте в архиве со ссылками из фактов на случай, если извлечение окажется ошибочным и надо будет вернуться.
Реализуйте разрешение конфликтов. Сравнение → определение типа (дубль/уточнение/противоречие) → инвалидация вместо удаления.
Заложите изоляцию с самого начала. user_id и tenant_id в фильтрации запросов, а не постфактум. Переделывать это потом дорого.
Держите память за абстракцией. Не привязывайте ее к конкретному фреймворку.
Настройте политику забывания. TTL, иерархическая суммаризация, дедупликация — комбинация этих механик держит рост под контролем.
Все остальное — Zep, Letta, графовые представления, LLM-as-a-judge для сложных сценариев обновления — приходит по мере того, как продукт растет и появляются реальные требования. Начинать с этого нет смысла, а вот пропустить базовые шаги в погоне за модной архитектурой — верный способ упереться в тупик через полгода.
Тема памяти агентов сейчас находится в интересной точке: инструменты появляются быстрее, чем формируются устоявшиеся паттерны. Многое из того, что я описал, у нас работает, но какие-то решения через год мы наверняка пересоберем, потому что появятся новые фреймворки, изменятся требования, всплывут проблемы, которых мы сейчас не видим. Поэтому мне интересно, как эту задачу решаете вы. На чем построена память в ваших агентах? С какими «граблями» вы уже столкнулись?
Было бы интересно услышать живые истории. И если вы решите, что памяти вашему агенту все же не нужно, тоже расскажите почему. Кейсы «Оказалось, что калькулятор — это то, что надо» — тоже ценные.