🐾 claw-stack
· Orange & Qiushi Wu memory architecture context-window AI agents

Система памяти v2: Решение проблемы переполнения контекста

Две проблемы осложняли работу нашего AI-агента: контекстные окна взрывались от выходных данных инструментов (82,5% контекста составляли результаты инструментов), и /new стирала все состояние работы без передачи краткосрочной памяти. Мы реализовали агрессивное сокращение контекста, сжали MEMORY.md на 97%, и написали хук передачи сессии, который автоматически обновляет файлы памяти перед каждым сбросом сессии.

В нашей последней статье о создании постоянной системы памяти мы описали проблему переполнения MEMORY.md: через шесть недель файл вырос более чем на 700 строк, и мы решили проблему, переходя от встроенного содержимого к записям на основе указателей. Исправление сработало. MEMORY.md стал компактным, запуск сессии улучшился, всё было хорошо.

Затем он снова переполнился.

Четыре недели спустя MEMORY.md снова достиг 92 000 символов и 790 строк. Конвейер организатора продолжал писать новые факты встроенным способом, а не ссылаясь на файлы по темам. Наш лимит размера в байтах не соблюдался последовательно. Исходное исправление устранило симптом, а не причину.

Более тревожно было то, что мы заметили, что сессии достигали лимитов контекста в середине задачи, даже когда MEMORY.md был под контролем. Агент прочитал бы несколько файлов, выполнил поиск, а затем зависал — не потому что у него кончилась память, а потому что его контекстное окно было заполнено выходными данными инструментов с более ранних этапов той же сессии.

И была третья проблема, которую мы терпели: каждый раз, когда мы запускали /new для начала новой сессии, агент терял понимание того, что он только что делал. Наша система долгосрочной памяти (v1) хорошо справлялась с фактами, предпочтениями и знанием проекта. Но краткосрочное рабочее состояние — какая задача была в процессе, какие решения были только что приняты, каков был следующий шаг — полностью исчезало. Пользователю приходилось вручную напоминать агенту обновить свои файлы памяти перед сбросом, или смириться с потерей контекста.

Три проблемы, одна тема: отсутствие систематического жизненного цикла контекста на любой шкале времени.

Измерение перед исправлением

Прежде чем что-либо менять, мы написали session-stats.py для анализа последних 15 сессий и понимания того, куда действительно уходил контекст. Результат был прояснительным.

Session context breakdown (15 sessions, chars):
┌──────────────────┬───────────┬───────────┬────────────┐
│ Category         │ Total     │ % of ctx  │ Avg/session│
├──────────────────┼───────────┼───────────┼────────────┤
│ Tool results     │ 1,842,300 │   82.5%   │   122,820  │
│ System prompt    │   268,100 │   12.0%   │    17,873  │
│ Assistant text   │    64,700 │    2.9%   │     4,313  │
│ User input       │    55,900 │    2.5%   │     3,727  │
└──────────────────┴───────────┴───────────┴────────────┘

Самая экстремальная сессия: 159 000 символов результатов инструментов, 1 500 символов входных данных пользователя и текста ассистента вместе взятых. Фактический разговор был почти невидим в собственном контекстном окне.

Системный запрос составлял в среднем 17K символов за сессию. Мы знали, что MEMORY.md загружается при запуске, но видеть, что он составляет 12% всего контекста во всех сессиях, включая сессии, где ничего не связанного с памятью не произошло, сделало число конкретным. Агент платил налог контекста в 17K символов на каждую сессию, независимо от того, что он делал.

Две проблемы теперь были измеримы: переполнение результатов инструментов в рамках сессии и переполнение MEMORY.md между сессиями. Обе были решаемы, и у нас были цифры для оценки решений.

Решение 1: Сокращение контекста

Проблема в рамках сессии заключается в том, что выходные данные инструментов накапливаются. Агент читает файл — это 8K символов контекста. Запускает поиск — ещё 4K. Редактирует файл, видит diff — 2K. Читает выходные данные теста — 6K. После умеренно сложной задачи контекст в основном состоит из выходных данных инструментов с более ранних шагов, на которые агент больше не нуждается в ссылке.

Функция contextPruning OpenClaw обрабатывает это с использованием подхода на основе TTL: после настраиваемого временного окна выходные данные инструментов старше последнего хода заменяются на заполнитель. Содержимое исчезает из активного контекста, но агент может видеть, что что-то произошло.

Наша конфигурация:

contextPruning:
  mode: cache-ttl
  ttl: 30
  minPrunableToolChars: 100
  hardClearRatio: 0

С ttl: 30 любой результат инструмента старше 30 секунд может быть удален при следующем ходе. minPrunableToolChars: 100 предотвращает замену крошечных выходных данных инструментов, которые стоят почти ничего. hardClearRatio: 0 означает, что мы никогда не делаем полную очистку — мы сохраняем последний ход в целости.

Эффект заключается в том, что агент работает с движущимся окном свежих выходных данных инструментов вместо полной накопленной истории. Для задач, включающих повторное чтение файлов или поиск-итеративные циклы, это разница между достижением лимитов контекста на шаге 8 и завершением задачи.

У нас была одна забота: сломает ли сокращение способность агента ссылаться на более раннюю работу? На практике нет. Для большинства задач агенту нужен либо выход последнего вызова инструмента, либо общий факт, который должен быть в памяти, а не в эфемерном результате инструмента. Если агенту нужно повторно прочитать файл, который он уже обработал, это обычно признак того, что факт должен был быть записан в память, а не кэширован в контексте.

Решение 2: Структурное сжатие MEMORY.md

Миграция 92K → компактная версия потребовала решения вопроса дизайна, который мы избегали в первый раз: что именно должно содержать MEMORY.md?

Наш ответ v1 был “недавняя активность, активные проекты, ключевые контакты и примечания инфраструктуры” с ограничением по размеру в байтах, чтобы поддерживать управляемость. Это было неправильно. Ограничение по размеру в байтах — это стимул к сжатию содержимого, но оно не предотвращает накопление — оно просто делает каждую запись короче, прежде чем вы закончите место и начнете нарушать правила.

Правильный ответ заключается в том, что MEMORY.md должен содержать указатели, а не содержимое. Если вы можете ответить на вопрос “для чего этот файл?” ответом “он содержит X”, то MEMORY.md не должен содержать X — он должен содержать “см. memory/X.md для X”. MEMORY.md — это индекс, который говорит агенту, куда смотреть, а не документ, содержащий то, что знает агент.

С этим определением целевая структура стала очевидной:

## Users
| handle | role | notes |
| --- | --- | --- |
| @orange | owner | ... |

## Projects
| name | status | detail file |
| --- | --- | --- |
| claw-stack | active | memory/entities/project-claw-stack.md |
| info-pipeline | active | memory/entities/project-info-pipeline.md |

## Infrastructure
| service | notes | detail file |
| --- | --- | --- |
| CF Workers | edge compute | memory/infra/cloudflare.md |

## Behavior rules
See AGENTS.md for current rules.

## Recent (last 5)
- 2026-03-09: ...

Таблицы для структурированных фактов (пользователи, проекты, инфраструктура). Указатели для всего остального. Недавняя активность ограничена пятью записями, циклично. Общая цель: менее 5 000 символов.

После миграции MEMORY.md сократился со 92 000 символов до 2 900 символов — сокращение на 97%. Запуск сессии улучшился с ~23K токенов контекста MEMORY.md до ~700 токенов. Всё, что было в MEMORY.md раньше, всё ещё доступно для поиска через поиск векторов QMD; это просто в файлах по темам теперь, а не встроенным образом.

Сам скрипт миграции был около 150 строк Python: прочитать текущий MEMORY.md, извлечь факты по категориям используя Claude Haiku, написать факты в соответствующие файлы по темам, создать новый MEMORY.md на основе указателей. Запуск занял 20 секунд.

Решение 3: Хуки передачи сессии

Сокращение контекста и сжатие MEMORY.md решили проблемы технического переполнения. Была третья проблема, которую мы терпели: когда вы запускаете /new для начала новой сессии, вы теряете весь рабочий контекст из текущей сессии. Какой файл вы редактировали? Каков был следующий шаг? Что вы только что поняли об ошибке, которую вы отлаживали?

Обычный ответ — “пишите лучшие заметки”. Мы хотели это автоматизировать.

OpenClaw поддерживает хуки, которые срабатывают на специфические команды. Мы написали хук command:new, который запускает конвейер суммирования сессии перед началом новой сессии:

# Triggered on /new
def session_handoff(transcript):
    summary = claude_haiku(
        system=open("MANIFEST.md").read(),  # file map for the memory system
        prompt=f"Summarize this session. Extract: current work state, "
               f"decisions made, lessons learned, entities updated. "
               f"Format as structured updates for memory files.\n\n{transcript}"
    )
    apply_memory_updates(summary)  # updates MEMORY.md, TODO.md, entities, etc.

Хук запускается синхронно с таймаутом в 20 секунд, затем откатывается на асинхронность, если стенограмма слишком длинная для быстрой обработки. На практике большинство сессий обрабатываются за 8–12 секунд.

Ключевым элементом является MANIFEST.md, файл, который описывает структуру системы памяти: какие файлы существуют, что содержит каждый из них, и какие виды обновлений куда идут. Без него Haiku не знает, что обновление проекта должно идти в memory/entities/project-X.md вместо прямого добавления в MEMORY.md. MANIFEST — это документация схемы для агента, который поддерживает память.

После хука передачи сессии /new по-прежнему начинает свежий контекст, но MEMORY.md теперь отражает результаты текущей сессии. Следующая сессия начинается с понимаем того, где вы остановились.

Правила предотвращения деградации

После перестройки системы дважды мы написали явные правила в AGENTS.md для предотвращения повторения одних и тех же проблем:

Жёсткие ограничения:

  • MEMORY.md должен оставаться под 5 000 символов. Если обновление превысит этот лимит, пишите в файл по теме и добавляйте указатель вместо этого.
  • Никогда не пишите хэши коммитов, фрагменты кода или необработанные сообщения об ошибках в MEMORY.md. Они либо эфемерны (хэши коммитов, ошибки), либо принадлежат файлам по темам (код).

Запрещённое содержимое:

  • Списки более чем из 5 элементов (используйте файл по теме)
  • Факты, уже присутствующие в другом файле памяти (без дублирования)
  • “Временные” заметки (пишите в файл TODO, а не в MEMORY.md)

Регулярное обслуживание:

  • После любой сессии, которая затронула более 3 файлов, проверьте, нуждаются ли файлы по темам в обновлении
  • Когда статус проекта меняется, обновляйте файл сущности, а не таблицу в MEMORY.md

Правила, написанные в AGENTS.md, становятся частью системного запроса, что означает, что конвейер организатора и хук передачи сессии оба их видят. Они не обеспечиваются кодом, но явные правила в контексте значительно лучше, чем неформальные соглашения.

Измеренные результаты

Непосредственные результаты после развёртывания изменений v2:

МетрикаДоПосле
Размер MEMORY.md~92K символов (~23K токенов)~2,9K символов (~700 токенов)
Налог контекста запуска сессии~23K токенов~700 токенов
Доля результатов инструментов в контексте82,5%Удалено после 30s
Сохранение рабочего состояния между /newНетДа (автоматизировано)

Сокращение MEMORY.md составляет 97% сокращение. Каждая новая сессия теперь начинается на 22K меньше токенов служебных данных, что означает больше места для фактической задачи. Конфигурация сокращения контекста означает, что результаты инструментов старше 30 секунд заменяются заполнителями, предотвращая накопление в рамках сессии, которое вызывало зависания на многошаговых задачах.

Будет ли хук передачи сессии постоянно генерировать правильные обновления памяти — мы узнаем через несколько недель использования. Архитектура правильная — вопрос в том, будет ли суждение Haiku о том, что обновлять, выдерживать масштабирование. Мы вернёмся с отчётом.

Что мы узнали о памяти

Пост блога v1 представлял проблему переполнения как техническую проблему с техническим решением: обеспечить ограничение по размеру в байтах, использовать указатели вместо встроенного содержимого. Эта формулировка была правильной, но неполной.

Реальная проблема в том, что управление памятью — это проблема информационной архитектуры, а не проблема хранения. Каждый раз, когда мы говорили “этот факт может быть релевантным позже, поэтому поместите его в MEMORY.md”, мы принимали плохое решение индексирования. MEMORY.md использовался как универсальная свалка вместо специфического слоя в архитектуре.

Система v2 работает не потому, что у нас есть лучшие механизмы обеспечения (хотя сокращение TTL и ограничения по размеру помогают), а потому, что мы яснее понимаем, для чего предназначен каждый слой:

  • Активный контекст: рабочее состояние текущей сессии. Эфемерно. Удаляется агрессивно.
  • MEMORY.md: ориентация сессии. Минимальный контекст, необходимый для начала сессии. Только указатели.
  • Файлы по темам: углубленное знание по конкретным темам. Загружаются по запросу. Где живёт содержимое.
  • Поиск векторов: резервный поиск по всей памяти. Для запросов, которые не знают, где смотреть.

Когда приходит новый факт, вопрос не “должен ли я это помнить?” Это “какой слой это принадлежит?” Большинство фактов не принадлежат MEMORY.md. Правильная архитектура — это то, что предотвращает переполнение.

Практические выводы для разработчиков агентов

Если вы создаёте что-то подобное, ошибки, которые мы сделали дважды, стоит знать:

Обеспечьте разделение индекса/содержимого при записи, а не ретроактивно. Ограничение по размеру в байтах на MEMORY.md не предотвращает переполнение — оно просто делает переполнение меньше, прежде чем вы его превысите. Реальное ограничение: никакого содержимого в индексе, только указатели. Проверяйте это при каждой записи.

Измеряйте распределение контекста перед оптимизацией. Мы предположили, что MEMORY.md — основная проблема. Это была проблема. Результаты инструментов были более крупной проблемой. Написание session-stats заняло день и сразу выявило более крупную проблему. Сначала измеряйте.

Сокращение контекста на основе TTL имеет низкий риск и высокую награду. Мы беспокоились, что это сломает поведение агента. Оно не сломало. Для большинства задач старые результаты инструментов — это шум, а не сигнал. Удаляйте их.

Хук передачи стоит больше, чем идеальное ведение заметок. Просить людей (или агентов) надёжно писать заметки в конце сессии — это проигрышная стратегия. Автоматизируйте это. Даже грубое извлечение, которое занимает 10 секунд, лучше, чем ручные заметки, которые не пишутся.

Документируйте схему системы памяти для агентов, которые её используют. Паттерн MANIFEST.md — файл, который объясняет, куда идут вещи — это то, что делает автоматизированные обновления памяти фактически помещающими вещи в правильное место. Без него каждое обновление становится ad-hoc решением о размещении файла.

Системы памяти для AI-агентов ещё молоды, чтобы была установленная практика. Это паттерны, которые сработали для нас в нашем масштабе. Ваш масштаб, ваши паттерны доступа и распределение задач вашего агента создадут другие ограничения. Но основной принцип остаётся: память агента — это информационная архитектура. Сначала получите архитектуру правильной, прежде чем строить инфраструктуру.