1. Проблема: Agent Amnesia

В Части 1 я показал ReAct-агента. В Части 3 — Reflexion, где агент учится на ошибках через эпизодическую память. Четыре паттерна, четыре сессии — и ни один не решает фундаментальную проблему: контекстное окно — это не память.

Представьте: ваш дебаг-ассистент в пятницу нашёл, что баг в middleware — это race condition на shared cache. В понедельник вы спрашиваете того же агента про похожий баг — и он начинает с нуля. Не помнит ни вывод, ни контекст, ни даже то, что cache вообще существует. Каждый понедельник — Groundhog Day.

И это не экзотика. Это норма. LLM stateless по природе: обработала токены, вернула результат, забыла. Контекстное окно — 128K, 200K, даже 1M токенов — всё равно конечное. Сессия закончилась, окно очистилось, агент ничего не знает о вашем проекте.

Но ведь человеку тоже не нужно помнить всю жизнь — он хранит важное и забывает мусор. Агенту нужна не бесконечная память, а правильная. Что сохранять? Как сжимать? Когда поднимать в контекст? Именно эти вопросы я разберу.

2. Таксономия памяти агента

Системный обзор Du et al. (Memory for Autonomous LLM Agents, 2026) формализует память агента как write-manage-read loop:


graph LR
    W["✏️ WRITE
Что и когда сохранять"] --> M["🔧 MANAGE
Сжатие, дедупликация,
разрешение противоречий"] M --> R["📖 READ
Что и когда
поднять в контекст"] R -->|"новый опыт"| W

Три фазы, три принципиально разных инженерных решения:

  • Write: не всё стоит сохранять. «Пользователь спросил погоду» — мусор. «Пользователь предпочитает YAML вместо JSON» — факт. Нужна фильтрация.
  • Manage: факты устаревают, дублируются, противоречат друг другу. «Боб — лид ML-команды» и «Алиса руководит ML-командой» — нужен механизм разрешения.
  • Read: поднять нужное в нужный момент. Не весь архив, а релевантное. И уложить в токен-бюджет.

Du выделяет три измерения для классификации: temporal scope (краткосрочная / долгосрочная), representation (текст / векторы / графы) и control policy (фиксированные правила / обучаемые). Из комбинаций рождаются пять семейств механизмов — от context-resident compression до policy-learned management. Но на практике инженеры выбирают не из академических семейств, а из готовых решений. И тут вариантов неожиданно много.

3. Подход 1: File-First Brain (OpenClaw)

OpenClaw — проект от команды mem0 (58K ⭐ на GitHub, YC S24), популярного управляемого слоя памяти для AI-агентов. Если mem0 выносит память в сервис, то OpenClaw уходит в противоположную крайность — радикальная идея: файловая система = мозг агента.

Агент хранит всё в markdown-файлах:

ФайлНазначениеРазмер
SOUL.mdИдентичность: кто я, мои ценности~2K токенов
AGENTS.mdПроцедуры: как я работаю~3K токенов
MEMORY.mdКурируемая долгосрочная память~5K токенов
memory/YYYY-MM-DD.mdСырые дневные логиБез лимита

Каждая сессия начинается с boot sequence — агент читает SOUL.md, AGENTS.md, MEMORY.md и последние дневные логи. Это его «утренний кофе»: 4K–10K токенов только на старт, прежде чем он скажет первое слово.


graph TB
    START["🚀 Новая сессия"] --> SOUL["📖 SOUL.md
идентичность"] SOUL --> AGENTS["📖 AGENTS.md
процедуры"] AGENTS --> MEMORY["📖 MEMORY.md
курированные факты"] MEMORY --> LOGS["📖 дневные логи
последние записи"] LOGS --> READY["✅ Агент готов"] style START fill:#1565c0,color:#fff style READY fill:#2e7d32,color:#fff

Зачем файлы, а не векторная база? Философский аргумент OpenClaw: RAG — для поиска информации, а агенту нужен мозг. Векторная база фрагментирована — семантический поиск возвращает куски без контекста. Файлы — цельные, читаемые агентом нативно, редактируемые человеком.

Но есть цена. 10K токенов на boot — это 10K токенов, которые не используются для задачи. При 5M токенов/день (типичный расход OpenClaw) это серьёзный бюджет. И чем больше MEMORY.md, тем дороже каждая сессия.

4. Подход 2: Structured Compression (MemPalace)

MemPalace решает главную проблему OpenClaw — token crushing. Идея: не грузить всё сразу, а поднимать по требованию.

Архитектура — иерархия Memory Palace: Wing (проект/человек) → Room (подтема) → Hall (тип памяти: факты, события, открытия, предпочтения, советы). На каждом уровне — сжатое описание, которое LLM читает нативно. Сжатие — не магия AAAK, а банальная truncation: snippets обрезаются до 200–300 символов и группируются по room.

Четыре слоя памяти (layers.py):

СлойЧто хранитКак работаетТокены
L0 — IdentityКто я, мои принципы, ключевые людиЧитает ~/.mempalace/identity.txt (пишет пользователь)~100
L1 — Essential StoryТоп-15 важных моментов из всего palaceСканирует до 2000 drawers, ранжирует по importance/emotional_weight/weight, группирует по room, обрезает до 3200 символов~500–800
L2 — On-DemandФильтрованная выборка по wing/roomMetadata-фильтр в ChromaDB (не семантика!), до N drawers~200–500 за запрос
L3 — Deep SearchПолный семантический поискcol.query(query_texts=...) по всему palace с similarity-ранжированиемБез лимита

Старт — только L0 + L1, ~600–900 токенов. L2 и L3 поднимаются через MCP-инструменты, когда агент сталкивается с задачей, требующей контекста.


graph TB
    BOOT["🚀 wake-up
~600-900 tokens"] --> L0["L0: Identity
~100 токенов
файл identity.txt"] BOOT --> L1["L1: Essential Story
~500-800 токенов
топ-15 drawers по весу"] L0 --> L2["L2: On-Demand
~200-500 токенов
фильтр по wing/room"] L1 --> L2 L2 --> L3["L3: Deep Search
без лимита
семантический поиск"] L3 --> WING["🏰 Wing
проект / человек"] WING --> ROOM["🏠 Room
подтема"] ROOM --> HALL["🚪 Hall
тип памяти"] style BOOT fill:#2e7d32,color:#fff style L2 fill:#e65100,color:#fff style L3 fill:#c62828,color:#fff

Плюс: Knowledge Graph с temporal validity — факты имеют срок годности. «Боб — лид ML-команды» действительно до определённой даты. Когда Алиса стала лидом — факт автоматически устаревает. Это решает проблему противоречий из write-manage-read loop.

Результат: 96.6% R@5 на LongMemEval (бенчмарк долгосрочной памяти) при ~600–900 токенах на wake-up (L0 + L1). Против 10K у OpenClaw. Разница в 10–15x — MemPalace вспоминает лучше, тратя на загрузку на порядок меньше контекста. И это без LLM на этапе поиска: чистый ChromaDB, чистый semantic search, ноль API-вызовов.

5. Подход 3: Managed Memory Layer

Третий подход — вынести память в отдельный сервис. Агент не хранит ничего, а запрашивает контекст у внешнего слоя.

mem0 (GitHub, 58K ⭐) — «universal memory layer». Управляемый сервис (YC S24), который автоматически извлекает факты из диалогов, дедуплицирует и возвращает релевантное по запросу. Новый алгоритм (апрель 2026): 94.8% на LongMemEval при 6.8K токенов. Плюсы: не нужно думать о write/manage — сервис делает всё сам. Минусы: вендор-лок, зависимость от внешнего API.

А вот Zep — совсем другой зверь. Это open-source memory server, и его стоит рассмотреть отдельно, потому что он решает проблему, которую mem0 не решает: структуру связей между фактами.

Представьте: агент знает, что «Боб работает в ML-команде» и «ML-команда использует PyTorch». Векторная база (как у mem0) найдёт каждый факт по отдельности — но не поймёт, что Боб, скорее всего, работает с PyTorch. Zep добавляет Knowledge Graph поверх векторов — через свой движок Graphiti. Сущности и связи извлекаются автоматически из диалогов, а namespace-based изоляция разделяет контекст разных пользователей.

Почему мы его рассматриваем? Потому что в реальных проектах факты не живут в вакууме — они связаны. «Клиент X перешёл на план Y» и «план Y не поддерживает фичу Z» — агент должен сделать вывод, а не просто вернуть оба факта. Граф даёт такую возможность.

Но есть цена. Zep требует инфраструктуру: векторная БД + граф + embedding service. Это не один сервис, а стек. Если у вас нет DevOps-ресурса — деплой будет болью.

LangMem (документация, от LangChain) — SDK с тремя типами памяти: semantic (факты), episodic (прошлый опыт), procedural (эволюция поведения). Процедурная память — уникальная фича: агент обновляет свой промпт на основе обратной связи, «учится» вести себя лучше. Плюсы: нативная интеграция с LangGraph, namespace-изоляция для privacy. Минусы: привязка к экосистеме LangChain.

Что общего? Все три — middleware: сидят между агентом и LLM, перехватывают контекст, обогащают релевантными фактами. Различия — в хранилище (векторы vs граф vs промпт), управлении (авто vs курируемое) и деплойменте (SaaS vs self-hosted vs embedded).

6. Как мы решаем

А мы с командой как раз решаем это — проектируем память для AI-агентов на нашей платформе. Архитектура, к которой мы пришли, подозрительно похожа на гибрид OpenClaw и MemPalace.

Четыре слоя памяти:


graph TB
    SESSION["🔄 Session Layer
текущий диалог,
автоочистка"] AGENT["🤖 Agent Layer
SOUL.md, ABOUT.md,
характер и профиль"] USER["👤 User Layer
предпочтения,
контекст пользователя"] PROJECT["📁 Project Layer
AGENTS.md, TOOLS.md,
workspace + Virtual FS"] SESSION --> AGENT --> USER --> PROJECT style SESSION fill:#1565c0,color:#fff style AGENT fill:#6a1b9a,color:#fff style USER fill:#e65100,color:#fff style PROJECT fill:#2e7d32,color:#fff

Runtime-сборка контекста: при каждом запросе система собирает контекст послойно — от session (самый дешёвый, всегда в окне) до project (самый дорогой, грузится по требованию). Это как MemPalace: старт дешёвый, детализация on-demand.

Файловая структура агента — прямое вдохновение от OpenClaw:

ФайлАналог OpenClawНазначение
SOUL.mdSOUL.mdХарактер агента
ABOUT.mdПрофиль/описание
AGENTS.mdAGENTS.mdИнструкции по созданию агента
TOOLS.mdАвто-сборка из скиллов + MCP

От MemPalace мы взяли идею layered loading: не грузим всё в контекст сразу, а поднимаем слои по мере необходимости. И концепцию temporal validity — у фактов есть срок годности, устаревшие не попадают в контекст. Но что это значит на практике?

Layered loading: как это работает у нас

В MemPalace четыре слоя (layers.py), и каждый решает свою задачу:

  • L0 — Identity (~100 токенов). Plain-text файл ~/.mempalace/identity.txt, который пишет пользователь. «Я — Atlas, личный AI-ассистент Алисы. Черты: тёплый, прямой. Люди: Алиса (создатель), Боб (партнёр Алисы). Проект: приложение для дневников.» Это константа — не меняется между сессиями, не вычисляется, просто читается с диска.

  • L1 — Essential Story (~500–800 токенов). Автоматически генерируется из palace: сканирует до 2000 drawers (чанков памяти), ранжирует по весу (importance, emotional_weight, weight), берёт топ-15, группирует по room для читаемости, обрезает до 3200 символов. Это «самое важное, что случилось» — не весь архив, а выжимка. Алгоритм не идеальный: например, drawer с высоким emotional_weight может вытеснить более полезный, но менее «эмоциональный» факт. Но для boot-задачи — дать агенту минимальный контекст — работает.

  • L2 — On-Demand (~200–500 токенов за запрос). Фильтрованная выборка: «дай мне всё из wing=driftwood, room=auth-migration». Это metadata-фильтр в ChromaDB, не семантический поиск — просто WHERE wing = ? AND room = ?. Поднимается, когда в разговоре всплывает конкретный топик.

  • L3 — Deep Search (без лимита). Полный семантический поиск: col.query(query_texts=["why did we switch to GraphQL"]) по всему palace. Возвращает результаты с similarity-ранжированием. Это уже тяжёлая артиллерия — когда L1 и L2 не дали нужного контекста.

Старт — wake_up() → L0 + L1, ~600–900 токенов. L2 и L3 — через MCP-инструменты, когда агент сталкивается с задачей, требующей контекста.

Мы адаптировали эту модель, но с ключевым отличием: MemPalace — это внешний сервис, а у нас слои собираются внутри runtime агента. Не нужно ходить по MCP за памятью — контекст уже собран к моменту, когда LLM начинает генерировать ответ.

Как выглядит сборка на каждом запросе:

СлойЧто грузитсяКогдаТокены
SessionТекущий диалогВсегда0 (уже в окне)
AgentSOUL.md + ABOUT.mdВсегда~1K
UserПредпочтения, контекстВсегда (для текущего юзера)~500
ProjectAGENTS.md + TOOLS.md + workspaceПо требованию (первый запрос к проекту)~2–5K

Итого на старте: ~1.5K токенов. Больше, чем MemPalace (~600–900), но на порядок меньше, чем OpenClaw (~10K). Компромисс.

Зачем грузить Project по требованию? Представьте: пользователь зашёл в чат, поздоровался. Агент не знает, в какой проект пойдёт работа. Зачем тратить 5K токенов на загрузку AGENTS.md и TOOLS.md проекта, который может вообще не понадобиться? Поэтому: ждём, пока задача не потребует контекста проекта — и только тогда поднимаем слой.

А как у нас работает аналог L1 — ранжирование «самого важного»? У нас его нет. SOUL.md и ABOUT.md — это и есть L0/L1, курируемые вручную. Нет автоматического ранжирования из 2000 drawers, зато нет и риска, что алгоритм с высоким emotional_weight вытолкнет полезный, но «скучный» факт. Для платформы с сотнями агентов — это осозненный выбор: предсказуемость важнее автоматизации.

Temporal validity: факты с сроком годности

Это вторая идея из MemPalace, которую мы перенесли. В MemPalace Knowledge Graph реализован на SQLite: каждый факт — это триплет (subject, predicate, object) с полями valid_from и valid_to (knowledge_graph.py). Когда факт устаревает, он не удаляется — вместо этого проставляется valid_to. Это позволяет отвечать на запросы вида «что было правдой на дату X?» фильтрацией: valid_from <= X AND (valid_to >= X OR valid_to IS NULL).

# MemPalace: adding a temporal fact
kg.add_triple("Kai", "works_on", "Orion", valid_from="2025-06-01")

# Kai leaves Orion
kg.invalidate("Kai", "works_on", "Orion", ended="2026-03-01")

# Query: what's true now?
kg.query_entity("Kai")
# → [Kai → works_on → Orion (ended), Kai → recommended → Clerk]

# Query: what was true on Jan 20, 2026?
kg.query_entity("Kai", as_of="2026-01-20")
# → [Kai → works_on → Orion (active)]

Зачем это нужно? Самая частая проблема с памятью агента — не отсутствие фактов, а устаревшие факты. Агент помнит, что «проект использует REST API», а команда уже три месяца как переехала на gRPC. И вместо правильного ответа агент тащит в контекст ложный факт. Temporal validity решает это: у каждого факта срок годности, истёкшие автоматически исключаются из контекста.

У нас реализация проще, чем в MemPalace: вместо отдельного SQLite-графа мы используем метаданные в Virtual FS. Каждый файл памяти агента (MEMORY.md, ABOUT.md) имеет last_modified — и если факт не обновлялся дольше TTL (настраивается), он помечается как stale и не попадает в контекст. Нет полноценного графового запроса с as_of, но для нашего кейса (платформа с сотнями агентов, не один coding-ассистент) этого достаточно. Графовые запросы — это территория Zep, и если нам понадобится логический вывод на графе фактов, мы знаем, куда смотреть.

Отличие от OpenClaw: мы не храним сырые дневные логи. Вместо этого — курируемая память с автоматическим сжатием. Отличие от mem0: мы не зависим от внешнего сервиса — всё работает внутри платформы через Virtual FS и S3-маунт.

Runtime-архитектура: от запроса до контекста

Выше я описал абстрактные слои памяти. А вот как это выглядит в рантайме — на уровне кода.

Главный оркестратор — Chat UseCase. Каждый запрос пользователя проходит через конвейер из 8 этапов:


graph TB
    REQ["📥 User Request"] --> PREP["1️⃣ prepareSession
Load Session + AgentVersion"] PREP --> REG["2️⃣ RegisterInAgent
Bind tools to agent"] REG --> PRE["3️⃣ ExecutePreAgent
RAG pre-search"] PRE --> ASM["4️⃣ Context Assembly
Session[] + SystemPrompt + Tool Injections"] ASM --> LLM["5️⃣ LLM react-loop
eino/adk Runner"] LLM -->|"needs context"| TOOLS["6️⃣ Tool Execution
Workspace files / RAG / Skills"] TOOLS -->|"loaded into context"| LLM LLM -->|"done"| POST["7️⃣ PostAgent + Hooks
cleanup + side effects"] POST --> SAVE["8️⃣ Update Session
Context[]"] style REQ fill:#1565c0,color:#fff style TOOLS fill:#e65100,color:#fff style SAVE fill:#2e7d32,color:#fff

Ключевой момент: контекст собирается до того, как LLM начинает генерировать. К моменту вызова runner.Run() всё уже собрано — сессия, системный промпт, инъекции от инструментов. LLM получает готовый контекст и работает с ним.

Но это не значит, что вся память грузится сразу. SOUL.md и ABOUT.md уже в системном промпте — это «горячая» память. А вот файлы из Workspace агент запрашивает сам во время react-loop (этап 5→6 на схеме), когда понимает, что ему не хватает контекста. Это и есть on-demand loading из MemPalace — только вместо MCP-инструментов у нас eino tools.

Пять scope’ов Workspace и их маппинг на S3:

ScopeVirtual PathS3 PrefixКто использует
session/storage/session/...projects/{pid}/sessions/{sid}/workspace/Артефакты текущего диалога
user/storage/user/...projects/{pid}/users/{uid}/workspace/Файлы и предпочтения пользователя
agent_user/storage/agent_user/...projects/{pid}/agents/{aid}/users/{uid}/workspace/Память пары «агент ↔ юзер»
skills/storage/skills/...projects/{pid}/skills/SKILL.md, проектные документы
hooks/storage/hooks/...projects/{pid}/hooks/Hook-скрипты (post-agent)

И в отличие от OpenClaw и MemPalace, нам не нужен внешний сервис памяти — всё работает внутри платформы через Workspace (Virtual FS → S3) и Session Context (PostgreSQL).

7. Итоги

Пять подходов к памяти агента — от файлового мозга до управляемого сервиса:

ПодходПроектBoot costХранилищеУправлениеBest for
File-FirstOpenClaw~10K токеновMarkdown-файлыКурируемое вручнуюCoding-агенты, полный контроль
Structured CompressionMemPalace~600–900 токеновИерархия + ChromaDBАвто (ранжирование + temporal)Много-проектные агенты, строгий бюджет
Managed Layermem0~6.8K токеновВекторная БДАвто (SaaS)Быстрый старт, не хочешь инфра
Vector + GraphZepЗависит от запросаВекторы + Knowledge GraphАвто (self-hosted)Структурированные связи, privacy
Embedded SDKLangMemЗависит от типаLangGraph storeАвто + процедурная памятьЭкосистема LangChain

Нет серебряной пули. OpenClaw даёт максимальный контроль, но сжигает токены. MemPalace элегантна, но требует дисциплины в структуре. mem0 прост в интеграции, но вендор-лок. Zep мощен для графовых связей, но тяжёл в деплое. LangMem идеален для LangChain, но бесполезен вне его.

Мы выбрали гибрид: слои памяти как у MemPalace, файловая структура как у OpenClaw, runtime-сборка вместо статического boot. Потому что наша задача — не coding-агент и не чат-бот, а платформа, где агенты разных типов живут и работают вместе. И память должна обслуживать это разнообразие.


Предыдущая статья серии: Часть 4: Multi-Agent Patterns