
Привет, Хабр. Меня зовут Сергей Врулин, я Team Lead в команде агентов VK AI Space — корпоративной платформы для создания и запуска AI-агентов.
В июле 2026 года VK AI Space представила многоуровневую память для корпоративных AI‑агентов. Она позволяет агентам сохранять и переиспользовать информацию на всех этапах работы — в текущем диалоге и в рамках долгих проектов. Потенциально это позволит ускорить подготовку ответов и уменьшить число повторных задач.
В этой статье я во всех деталях расскажу, как именно мы проектировали память для агентов: от теории к реализации, с разбором ключевых решений и компромиссов.
Начнем с азов: зачем агенту память
Типичный LLM-агент — это функция без состояния. Контекстное окно модели ограничено, и как только диалог заканчивается, всё исчезает. То есть каждый следующий разговор начинается с чистого листа: пользователь заново объясняет контекст, агент переоткрывает файлы, повторяет поиск, снова угадывает предпочтения.
В потребительских сценариях это терпимо — чат-боту не обязательно помнить, о чем его спрашивали месяц назад. Но в корпоративном сегменте отсутствие памяти убивает смысл «цифрового сотрудника». Чтобы понять, насколько это критично, разберем несколько ситуаций.
Разбор инцидента. Агент помог разобрать сбой в продакшене. Через неделю — похожий инцидент. Без памяти агент начнёт с нуля: заново будет искать логи, выяснять, какие сервисы связаны, предлагать гипотезы. С памятью — «вспомнит» прошлый разбор и сразу предложит проверить ту же цепочку.
Подготовка тендера. Сотрудник с агентом собирает ответы на требования, а через месяц — новый тендер от того же заказчика. Без памяти агенту придется делать всё заново. С памятью — агент достанет готовые формулировки и адаптирует их.
Проектный отчёт. Агенту можно поручить сбор ежедневного дайли-репорта. Без памяти ему каждый день придется объяснять формат, источники, получателей. С памятью — агент уже знает, как вчера выглядел отчёт, и повторяет формат.
Поэтому мы захотели, чтобы агент помнил профиль сотрудника, накопленные решения по проекту и собственный опыт — и переиспользовал это между сессиями.
От задачи к исследованию: какие виды памяти бывают у агентов
Для начала рассмотрим, какие виды памяти бывают в агентах. Выделим пять основных:
Тип памяти |
Что хранит |
Пример |
Горизонт |
Кратковременная (working) |
Текущий диалог |
История сообщений сессии |
Одна сессия |
Эпизодическая |
Конкретные события |
«Пользователь просил …, я сделал …» |
Дни/недели |
Семантическая |
Факты о мире/домене |
Правила проекта, спецификации |
Долго |
Процедурная |
Информацию о том, как делать задачи |
Навыки, инструменты, подходы |
Долго |
Профильная |
Данные о пользователе |
Предпочтения, роль, контекст |
Долго |
И интересно понять, как эти виды реализованы в других проектах — например, в MemGPT, LangGraph, CrewAI, Cline, Claude Code. Рассмотрим подходы в каждом из них.
MemGPT использует paging-модель, где контекстное окно выступает «основной памятью», а долгая живет во внешних хранилищах и подгружается по требованию через специальные функции (core_memory_append, recall_memory_search). Но модель сама решает, что загружать, из-за чего тратит итерации LLM без гарантии своевременного извлечения данных.
LangGraph опирается на checkpointing состояния графа для кратковременной памяти и низкоуровневое key-value хранилище для долгосрочной. Но семантику сборки контекста разработчик вынужден строить самостоятельно.
CrewAI явно разделяет память на краткосрочную (диалог), долгосрочную (векторные эпизоды) и профильную, но сталкивается с проблемой прозрачности: векторные эмбеддинги нечитаемы для человека.
Cline («Memory Bank») и Claude Code (CLAUDE.md) используют наборы Markdown-файлов как читаемый источник контекста проекта вместо скрытых баз данных.
Анализируя подходы этих инструментов, можно столкнуться с ограничениями в корпоративном контуре. Выделю основные:
Прозрачность. Векторное хранилище — чёрный ящик. Сотрудник не может посмотреть, что именно агент «запомнил»: эмбеддинги нечитаемы, а восстановить исходный текст по вектору нельзя. Для корпоративного сегмента это блокер: compliance требует, чтобы человек мог в любой момент увидеть, какие данные о нём и о проекте хранятся.
Изоляция. Большинство фреймворков не имеют встроенной модели изоляции по проектам/пользователям/агентам. Её нужно строить поверх — и легко ошибиться так, что память одного проекта «протечёт» в другой.
Управляемость. Исправить неверный факт в векторной БД — это не «открыть и поправить строчку». Нужно найти нужный чанк, удалить, переэмбеддить, перезаписать. Человек не может просто взять и отредактировать то, что помнит агент.
Исходя из этого, мы сделали выбор в пользу модели «память как обычные текстовые файлы» (plain text / Markdown), а не эмбеддингов в векторной БД. У такого подхода несколько весомых преимуществ.
Читаемость по умолчанию. Файл USER.md или SOUL.md можно открыть и прочитать — без инструментов, без декодирования. Что написано, то агент и помнит. Никакого зазора между «что хранится» и «что видит человек».
Редактирование в обычном редакторе. Память правится так же, как любой документ: открыл в редакторе, исправил формулировку, удалил лишний абзац, сохранил. Не нужно ни API векторной БД, ни специальных инструментов агента — сотрудник работает с памятью напрямую, как с текстом.
Diff и версионирование. Текст естественно ложится на привычные инструменты: видно, что и когда изменилось, можно откатить. Для эмбеддингов такого нет.
Предсказуемость для LLM. Модель получает контекст ровно в том виде, в каком он записан в файле — без «примерного» семантического поиска, который может вернуть не тот чанк.
При этом мы сознательно отказались от семантического поиска «по смыслу» на больших объёмах: plain text хорош, пока память соразмерна контекстному окну. Для нашего сценария (профиль пользователя, правила проекта, эпизоды команды) этого достаточно, а прозрачность и управляемость важнее.
Требования к памяти и реализованная архитектура
К самой памяти мы сформулировали несколько требований:
Персистентность между сессиями. Важно, чтобы агент помнил контекст после перезапуска. Профиль сотрудника, накопленные решения по проекту, собственный опыт — всё должно переживать завершение диалога.
Нулевые итерации на загрузку. Контекст должен быть доступен с первого сообщения, чтобы агент не тратил вызовы LLM на чтение файлов памяти. Это и про скорость, и про предсказуемость: если загрузка памяти — это вызов инструмента, модель может его «забыть» сделать.
Единый интерфейс записи. Хотели, чтобы агент писал память как обычные файлы, без специализированных инструментов. Меньше инструментов — меньше когнитивная нагрузка на LLM, меньше кода — меньше точек отказа.
Изоляция и безопасность. Было важно, чтобы агент видел только те данные, которые доступны сотруднику по роли. Проектные файлы — только участникам проекта. Персональные — только владельцу.
Управляемость и прозрачность. Требовалось, чтобы человек мог посмотреть, что помнит агент, отредактировать или удалить. Память не должна быть чёрным ящиком.
Последние два пункта — это требования compliance для корпоративного сегмента. Без их выполнения продукт не выйдет на рынок.
Исходя из этого, в своей реализации мы решили скомбинировать разные типы памяти. Сделали это следующим образом.
Тип памяти |
Реализация у нас |
Где хранится |
Как попадает в контекст |
Кратковременная (working) |
История сообщений текущей сессии |
В памяти раннера (session scope) |
Инъектится при сборке контекта в основном рантайме агента |
Эпизодическая |
Файлы событий в проектной области, дописываемые post-hook'ами |
S3, scope=project |
Инъекция через pre-hook и чтение через sandbox |
Семантическая |
SOUL.md — идентичность и характер агента, знания о том, «кто он есть |
S3, scope=agent |
Инъекция через pre-hook (содержимое) |
Процедурная |
AGENTS.md — правила и инструкции «как делать»; TOOLS.md — как работать с инструментами |
S3, scope=agent |
Инъекция через pre-hook (содержимое) |
Профильная |
S3, scope=user |
Инъекция через pre-hook (содержимое) |
При этом мы используем четыре, управляемых агентом, системных файлы, которые несут семантическую (SOUL.md), процедурную (AGENTS.md, TOOLS.md) и профильную (USER.md) память. Плюс — произвольные файлы в workspace проекта (эпизодическая память: события, дописываемые post-hook'ами).
Здесь стоит отметить, что граница между типами условна — один файл может нести оттенки нескольких. Более того, у нас «под капотом» нет пяти разных подсистем памяти — всё это лишь разные конфигурации одного механизма, логика которого живет в pre/post-хуках (post-hook дописывает эпизоды, pre-hook инжектит всё остальное).
Изоляция памяти
Изоляцию памяти мы построили на scope-модели. Семь типов и для каждого свой префикс и свои правила доступа в S3-бакете:
Scope |
S3-префикс |
Что хранит |
Поля, определяющие scope |
session |
projects/{pid}/sessions/{sid}/workspace/ |
Файлы текущей сессии |
ProjectID, SessionID |
user |
projects/{pid}/users/{uid}/workspace/ |
Профиль пользователя ( |
ProjectID, UserID |
agent |
projects/{pid}/agents/{aid}/workspace/ |
Системные файлы агента ( |
ProjectID, AgentID |
agent_user |
projects/{pid}/agents/{aid}/users/{uid}/workspace/ |
Пересечение агент+пользователь |
ProjectID, AgentID, UserID |
project |
projects/{pid}/workspace/ |
Файлы проекта (эпизодическая память) |
ProjectID |
skills |
projects/{pid}/skills/ |
Скиллы проекта |
ProjectID |
hooks |
projects/{pid}/hooks |
Скрипты хуков проекта |
ProjectID |
Для памяти задействованы agent, user и project. Префикс строится функцией BuildS3Prefix — единая точка правды для формата ключей. Валидация строгая: если для scope не хватает обязательного поля (например, AgentID для agent scope) — возвращается доменная ошибка.
При этом:
семантическая память изолируется по AgentID — разные агенты не пересекаются;
процедурная — по AgentID (те же префиксы, что у SOUL.md);
профильная — по UserID (строгая изоляция между пользователями);
эпизодическая — по ProjectID (доступны всем участникам проекта);
кратковременная — по SessionID (только текущая сессия).


Lakehouse-платформа для аналитики и ML
Объединяйте данные из разных систем и снижайте расходы на хранение в 7–10 раз
Получить консультацию
Почему работаем с файлами?
Как я уже упомянул раньше, в качестве основного подхода мы определили, что вся память хранится как файлы. При этом на текущем этапе развития платформы специализированные memory-БД, векторное хранилище или набор инструментов просто лишние — агент будет работать с файлами через песочницу OpenSandbox. Аргументов в пользу такого решения сразу несколько.
Sandbox-подобный (openclaw-подобный) подход даёт гибкость. Песочница монтирует файлы из S3, агент работает с ними как с локальной файловой системой, а sandbox синхронизирует изменения обратно. Это та же модель, по которой работают современные coding-агенты (Claude Code, OpenCode и подобные): агент не знает про S3 и API — он просто пишет в файлы, а инфраструктура разбирается с персистентностью.
Меньше кода — меньше точек отказа. Не нужно реализовывать специализированные memory-инструменты (write_memory_file, list_memory_files), их factory-регистрацию, ToolTypeID, prompt hints. Sandbox уже есть, файловые операции уже есть — переиспользуем.
Файлы монтируются как volume (read-only или read-write). Изменения синхронизируются обратно в S3 при завершении работы. Подходит для файлов, которые агент должен обновлять в процессе — USER.md, файлы проекта.
Жизненный цикл записи выглядит следующим образом:
Шаг 1. Агент вызывает write_file для /workspace/user/USER.md.
Шаг 2. Sandbox FS watcher замечает изменение в volume-mount.
Шаг 3. Файл выгружается обратно в projects/{pid}/users/{uid}/workspace/.
Шаг 4. Сессия завершается, sandbox уничтожается.
Шаг 5. Следующий запуск: sandbox mount уже содержит обновлённый USER.md.

Подход к получению содержимого памяти в контекст
Одной из задач при построении многоуровневой памяти для AI-агентов было определение способа передачи содержимого памяти в контекст.
Упрощенно здесь возможно несколько вариантов.
Отдельные memory-инструменты
Способ подразумевает создание специализированных инструментов: write_memory_file, list_memory_files, read_memory_file. Чтение — on-demand через read_memory_file, запись — через write_memory_file, навигация — через list_memory_files.
У такого подхода есть два преимущества:
memory-специфичная валидация (лимиты, разрешённые имена) инкапсулирована в инструменте;
чёткая ответственность — каждый инструмент делает одно.
Вместе с тем, есть и ощутимые недостатки:
новые инструменты существенно повышают когнитивную нагрузку на LLM (модель должна выбрать между read_memory_file и load_storage_file_content);
агент тратит итерации на on-demand загрузку каждого файла;
есть риск, что агент забудет вызвать read_memory_file для USER.md и сгенерирует ответ без учета контекста пользователя;
write_memory_file дублирует файловые операции sandbox.
Гибрид
Вариант подразумевает чтение on-demand через существующий load_storage_file_content, а запись — через новый write_memory_file. При этом для навигации предполагается использовать prompt hint с фиксированными путями.
Главное преимущество способа — необходимость создания только одного инструмента и переиспользование существующего load_storage_file_content.
Но минусов больше:
те же итерации на загрузку;
write_memory_file всё ещё дублирует sandbox;
нет навигации по файлам проекта (только фиксированные пути).
Full Injection + Sandbox Write
В этом случае все системные memory-файлы инжектятся в system prompt через managed pre-agent hook, а агент получает контекст с первого сообщения, то есть не требуется дополнительных итераций на загрузку. Запись осуществляется через стандартные файловые операции sandbox.
Такой подход имеет некоторые недостатки. Например:
все файлы в system prompt — расход ~2–5K токенов;
USER.md до 2000 chars увеличивает prompt;
валидация размеров при sandbox→S3 sync, а не на уровне инструмента;
зависимость от sandbox для записи.
Но преимущества более весомые:
0 итераций LLM на загрузку памяти. Благодаря этому можно экономить около 1.5–6K токенов на запрос при 3 on-demand файлах, и время (каждая итерация — это ~1–3 секунды latency LLM). При стоимости итерации около 500–2000 токенов это существенная экономия.
Sandbox — уже существующий механизм. Можно не создавать параллельный путь поверх файловой системы. Вместо этого реализуется последовательность sandbox → файловая система → LLM, без надстройки.
Устранён риск, что агент не загрузит память. Hook гарантирует инъекцию, поэтому агент не может ничего «забыть» или «пропустить».
Меньше кода. Не нужно создавать factory-регистрацию, ToolTypeID, prompt hints для on-demand загрузки.
В результате для своей реализации мы выбрали именно этот подход.
Управление контекстом
Память — это не только хранение данных, но и логика их сборки в нужный момент. В нашем проекте за этот процесс отвечает механика хуков с четырьмя триггерами:
Триггер |
Когда срабатывает |
Что можно делать |
pre |
До запуска агентного цикла |
Подгрузить файлы, собрать контекст, установить переменные окружения, проверить права |
post |
После ответа агента |
Отправить уведомление, опубликовать результат, дополнить память |
tool |
При вызове инструмента |
Залогировать аргументы, валидировать, подменить результат |
error |
При ошибке агента |
Отправить алерт, записать трейс, запустить retry-логику |
Хук представляет собой произвольный скрипт (Python, bash). Конфигурация сводится к простому манифесту: ядро предоставляет среду выполнения (runtime), а пользователь пишет логику.
Для работы с памятью ключевую роль играют pre- и post-hook: первый собирает контекст перед запуском (context assembly), второй сохраняет итоги диалога. Однако они способны на большее: например, post-hook может обновлять процедурную память (дописывать правила в AGENTS.md), профильную (уточнять настройки в USER.md) или даже семантическую (корректировать тон в SOUL.md). Более того, по умолчанию система настроена на автозапись только эпизодических событий, так как это самый частый сценарий, но архитектурных ограничений нет. Это универсальная модель: файлы служат хранилищем, а хуки управляют всей логикой.
Для наглядности рассмотрим, как именно pre-agent hook собирает контекст на практике.
Так, типовой платформенный хук представляет собой Python-скрипт, который читает подмонтированные из S3 файлы (/workspace/agent/SOUL.md, /workspace/user/USER.md) и формирует единый текст для системного промпта.
Конфигурация описывается простым YAML-манифестом:
# hook.yaml name: context-assembly description: Сборка memory-файлов в SystemPromptInjection command: python3 index.py
Скрипт исполняется внутри песочницы. Его работа строится на нескольких принципах:
Семантические секции, а не дамп файлов. Модель видит размеченные блоки <agent_identity>…</agent_identity>, а не сырые заголовки SOUL.md. Она не знает, что данные пришли из файловой системы.
XML-теги как границы блоков. Для четкого разделения контекста используются теги <agent_identity>, <user_profile>, <tool_notes>, <instructions>.
Graceful degradation (устойчивость к сбоям). Отсутствующие или пустые файлы пропускаются. У только что созданного агента все секции будут пустыми — это штатная ситуация.
Мягкие лимиты с маркером усечения. При превышении размера содержимое файла обрезается. Для SOUL.md и AGENTS.md отсекается конец текста, для логов (append-only) — начало. В контекст добавляется метка [...truncated...].
Диагностика в stderr. Информация о том, какие файлы найдены, пропущены или усечены, выводится в stderr. Эти технические подробности не попадают в контекст LLM.
Кастомизация через env CONTEXT_FILES. Список читаемых файлов можно переопределить через переменную окружения, не переписывая сам Python-скрипт.
Теперь для наглядности рассмотрим на конкретном пользовательском кейсе, как связка pre+post hooks реализует эпизодическую память в рамках сценария подготовки дайли-репорта.
Так, всё сводится к следующему алгоритму:
Пользователь просит агента: «Сделай дайли-репорт по вчерашним инцидентам».
Агент собирает данные, формирует отчёт.
После ответа срабатывает post-agent hook, который извлекает из ответа ключевые факты (дата, тема, формат), дописывает в файл /workspace/project/incidents-log.md запись: «2026-07-25 — дайли-репорт по инцидентам, опционально — публикует сам отчёт через Bot API.
При следующем запросе «Сделай дайли-репорт» срабатывает pre-agent hook, который читает incidents-log.md и инжектит в контекст: агент уже знает, что в прошлый раз использовался определенный формат, и переиспользует его. Контекст не нужно собирать заново.

То есть post-hook пишет события в проектную область, pre-hook подхватывает их при следующем запуске. Никакой жёсткой логики в ядре — пользователь сам настраивает хуки под свой сценарий.
Почему так, а не через векторную БД? Причины две:
Эпизодические события — это структурированные записи (дата + что произошло + артефакт), их логичнее хранить как log-файл, а не как embedding.
Пользователь должен видеть этот лог и иметь возможность его почистить. Работа с файлами позволяет это делать, с векторной БД — нет.
Заключение
В процессе работы с памятью мы сформулировали несколько инсайтов.
Injection лучше on-demand. Агент не должен тратить итерации LLM на то, что система может собрать за него. Контекст с первого сообщения — это и про экономию токенов, и про предсказуемость (модель не может «забыть» загрузить память).
Sandbox лучше специализированных инструментов. Такой подход позволяет не плодить сущности поверх файловой системы — агент пишет память как обычные файлы, инфраструктура разбирается с персистентностью. Меньше кода, меньше точек отказа, единый интерфейс.
Хуки лучше жёсткой логики. Можно не строить отдельную подсистему для каждого типа памяти. Например, вместо этого мы реализовали универсальный механизм: pre-hook собирает контекст перед запуском агента, post-hook дописывает события после ответа. Разделение на семантическую (SOUL.md), процедурную (AGENTS.md) и профильную (USER.md) память — это лишь дефолтная конфигурация хуков, которую можно переписать под любой сценарий компании.
При этом нам удалось выстроить реализацию, которая отвечает на все классические запросы при работе с AI: пользователь видит и контролирует файлы памяти напрямую через админ-панель, агент работает с ними через привычный интерфейс файловых операций в песочнице, изоляция гарантируется на уровне S3-префиксов, а гибкость достигается настройкой pre/post-хуков. И эти возможности многоуровневой памяти уже можно протестировать в VK AI Space.
newt76
Мне кажется, аргумент против векторных БД из-за прозрачности и compliance здесь немного искусственный. Да, сам embedding человек прочитать не может и восстановить из него исходный текст тоже нельзя. Но ведь никто не обязывает хранить в векторной БД только embedding. Обычно рядом с вектором хранится исходный текст чанка, metadata, scope, source и т. д. Тогда embedding фактически является просто индексом для поиска по этому тексту.
Поверх этого вполне можно сделать обычный UI, где пользователь видит, что именно находится в памяти, может отредактировать или удалить конкретную запись. На изменение текста вешается переиндексация — старый embedding удаляется/обновляется, новый строится из актуального текста. Туда же достаточно естественно добавляются RBAC, audit log, versioning и остальные требования корпоративного контура.
То есть проблемы «пользователь не может посмотреть и исправить то, что агент запомнил» — это скорее свойство конкретной реализации memory layer, а не принципиальное ограничение векторных БД.
При этом сам выбор Markdown/S3 для вашего сценария мне понятен: если память небольшая и вы всё равно целиком инжектите её в контекст, векторный поиск действительно может быть просто лишней сложностью. Но я бы тогда сформулировал аргумент именно так: «для нашего объёма и access pattern vector DB не даёт достаточной пользы, чтобы оправдать дополнительную инфраструктуру», а не как ограничение по прозрачности/compliance.