Привет, Хабр. Меня зовут Сергей Врулин, я 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 (содержимое)

Профильная

USER.md

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/

Профиль пользователя (USER.md)

ProjectID, UserID

agent

projects/{pid}/agents/{aid}/workspace/

Системные файлы агента (SOUL/TOOLS/AGENTS)

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

Комментарии (1)


  1. newt76
    29.08.2026 07:03

    Векторное хранилище — чёрный ящик. Сотрудник не может посмотреть, что именно агент «запомнил»: эмбеддинги нечитаемы, а восстановить исходный текст по вектору нельзя. Для корпоративного сегмента это блокер: compliance требует, чтобы человек мог в любой момент увидеть, какие данные о нём и о проекте хранятся.

    Мне кажется, аргумент против векторных БД из-за прозрачности и compliance здесь немного искусственный. Да, сам embedding человек прочитать не может и восстановить из него исходный текст тоже нельзя. Но ведь никто не обязывает хранить в векторной БД только embedding. Обычно рядом с вектором хранится исходный текст чанка, metadata, scope, source и т. д. Тогда embedding фактически является просто индексом для поиска по этому тексту.

    Поверх этого вполне можно сделать обычный UI, где пользователь видит, что именно находится в памяти, может отредактировать или удалить конкретную запись. На изменение текста вешается переиндексация — старый embedding удаляется/обновляется, новый строится из актуального текста. Туда же достаточно естественно добавляются RBAC, audit log, versioning и остальные требования корпоративного контура.

    То есть проблемы «пользователь не может посмотреть и исправить то, что агент запомнил» — это скорее свойство конкретной реализации memory layer, а не принципиальное ограничение векторных БД.

    При этом сам выбор Markdown/S3 для вашего сценария мне понятен: если память небольшая и вы всё равно целиком инжектите её в контекст, векторный поиск действительно может быть просто лишней сложностью. Но я бы тогда сформулировал аргумент именно так: «для нашего объёма и access pattern vector DB не даёт достаточной пользы, чтобы оправдать дополнительную инфраструктуру», а не как ограничение по прозрачности/compliance.