Двухуровневая память для долгоживущих агентов: рабочая память vs постоянные факты

← All posts

Контекстное окно — это ловушка. Не потому что его не существует, а потому что «200K токенов» создаёт иллюзию, что управлением памятью займётся кто-то другой. Не займётся. Долгая агентская сессия накапливает выводы инструментов, промежуточные результаты, тупиковые исследования и повторяющиеся статусные проверки. К 30-му ходу контекст на 80% состоит из шума.

Мы построили двухуровневую систему памяти для агентов KB Labs, которая разделяет то, что важно долгосрочно, от того, что важно прямо сейчас.

Уровень 1: Рабочая память (sliding window)

Рабочая память — это последние N раундов разговора: непосредственный контекст, который нужен агенту для продолжения текущей задачи. Реализована как ContextFilterMiddleware, выполняющийся перед каждым LLM-вызовом.

// ContextFilterMiddleware (order: 15, fail-open)
const patch = {
  messages: [
    ...systemMessages,                            // всегда сохраняются
    ...lastNRounds(messages, windowSize),          // скользящее окно
    ...truncateToolOutputs(messages, maxOutputLen) // обрезка больших выводов
  ]
};

Скользящее окно хранит недавние ходы; всё старше — отбрасывается. Выводы инструментов свыше порога усекаются — листинг файлов на 50KB не нужно хранить в контексте после того, как агент уже его обработал.

Уровень 2: Постоянная память (факты, переживающие сессии)

Постоянная память — это структурированные данные, которые переживают разговор. Хранится по каждой сессии в .kb/memory/<sessionId>/ и записывается через специальные инструменты:

Есть ещё общий слой в .kb/memory/shared/memory.json — предпочтения и ограничения для всех сессий воркспейса («всегда pnpm, не npm», «TypeScript strict mode обязателен»).

Сессионная непрерывность

Когда агент возобновляет сессию (--session-id=X), он загружает последние 16 ходов из turns.json и инжектирует их перед текущей задачей. Это даёт агенту разговорный контекст — что было сделано раньше, что сказал пользователь, какие решения были приняты — без повторного воспроизведения всей истории.

// runner.ts — сессионная непрерывность
const continuityEnabled = !!this.config.sessionId;
if (continuityEnabled) {
  const history = await loadConversationMessages(sessionPath);
  messages = [...history.slice(-16), ...currentMessages];
}

Пайплайн middleware

Управление памятью — часть пятиступенчатого middleware-пайплайна, обрабатывающего каждый ход агента:

  1. Observability (order: 5) — трассировка спанов, подсчёт токенов
  2. Budget (order: 10) — контроль бюджета токенов, nudge-сообщения при приближении к лимиту
  3. ContextFilter (order: 15) — скользящее окно + усечение
  4. FactSheet (order: 20) — инжекция постоянной памяти
  5. Progress (order: 50) — обнаружение застреваний, интервенция

Почему не просто использовать длинный контекст?

Три причины:

  1. Стоимость. Контекст на 200K токенов при $15/M output-токенов дорог, когда большая его часть — устаревшие выводы инструментов.
  2. Латентность. Больше входных токенов — дольше до первого токена вывода.
  3. Точность. LLM работают хуже с нерелевантным контекстом. Проблема «иголки в стоге сена» реальна — похоронить критический факт в 100K шума снижает вероятность, что модель им воспользуется.

Двухуровневая память не борется с контекстным окном — она делает его стоящим своего размера, заполняя сигналом вместо шума.