Агенты теряют память быстрее, чем вы успеваете сказать "context window exhausted"
Вы запускаете мультиагентную систему. Десять, сто, тысяча AI-агентов. Каждый решает свою подзадачу. Но как передать опыт одного агента другому? Традиционный подход — тащить весь history в следующее окно. Результат? Токены растут экспоненциально, latency падает, агенты "глупеют" на длинном контексте (об этом я писал в статье про coding agent и KV cache quantization). Знакомо?
Выход есть — Persistent Latent Memory. И если вы думаете, что это очередной RAG или векторная база, вы ошибаетесь. Речь о новом методе, представленном на ICML 2026 — Incremental Latent Compression Protocol (ILCP). Он берет β-VAE, скрещивает с gated MLP и создает сжатое скрытое состояние, которое живет вечно. Или пока вы его не перезагрузите.
Как работает ILCP? Аналогия с 6G handover
Представьте, что ваш агент — это мобильное устройство, которое перемещается между сотами (другими агентами). В 6G handover передается не весь дамп трафика, а только контекст сессии — закодированный в компактный сигнатурный вектор. ILCP делает то же самое: вместо пересылки гигабайтов текста агент передает латентный вектор фиксированной размерности, полученный через β-VAE.
Этот вектор — не просто хэш, а осмысленное скрытое представление всей предшествующей истории, регуляризованное так, чтобы быть информативным и несжимаемым до шума. Параметр β контролирует компромисс между качеством реконструкции и размерностью латентного кода. Маленькое β — много информации, но высокий риск переобучения. Большое β — сильное сжатие, но возможна потеря деталей.
Архитектура ILCP включает три компонента:
- Энкодер β-VAE — принимает историю взаимодействия агента (конкатенацию предыдущих запросов и ответов, закодированных через эмбеддинги LLM) и выдает параметры гауссова распределения в латентном пространстве.
- Декодер — восстанавливает приближенный контекст для проверки или повторной инициализации.
- Gated MLP Memory Manager — принимает текущее латентное состояние и новый контекст (через энкодер), вычисляет обновление через вентильный механизм (как GRU, но в латентном пространстве) и сохраняет новое состояние.
Если вас интересуют альтернативные подходы к памяти, взгляните на MemoryLLM — интерпретируемая память с внешними слоты, или LCME, которая работает быстрее моргания, но для одиночного агента.
Собираем ILCP своими руками (и не ломаем голову)
Теперь к делу. Предположим, вы используете любой LLM на базе трансформера (Qwen3, LLama 4, DeepSeek-V4). ILCP работает как прослойка между агентом и его контекстом. Вот минимальный код на PyTorch для ключевых компонентов.
import torch
import torch.nn as nn
import torch.nn.functional as F
class BetaVAE(nn.Module):
def __init__(self, input_dim, latent_dim, beta=4.0):
super().__init__()
self.beta = beta
self.encoder = nn.Sequential(
nn.Linear(input_dim, 512),
nn.ReLU(),
nn.Linear(512, latent_dim * 2) # mean and log_var
)
self.decoder = nn.Sequential(
nn.Linear(latent_dim, 512),
nn.ReLU(),
nn.Linear(512, input_dim)
)
def encode(self, x):
h = self.encoder(x)
mean, log_var = h.chunk(2, dim=-1)
return mean, log_var
def reparameterize(self, mean, log_var):
std = torch.exp(0.5 * log_var)
eps = torch.randn_like(std)
return mean + eps * std
def decode(self, z):
return self.decoder(z)
def loss(self, x, recon, mean, log_var):
recon_loss = F.mse_loss(recon, x, reduction='sum')
kl_loss = -0.5 * torch.sum(1 + log_var - mean.pow(2) - log_var.exp())
return recon_loss + self.beta * kl_loss
class GatedMemoryUpdater(nn.Module):
def __init__(self, latent_dim):
super().__init__()
self.update_gate = nn.Linear(latent_dim * 2, latent_dim)
self.reset_gate = nn.Linear(latent_dim * 2, latent_dim)
self.candidate = nn.Linear(latent_dim * 2, latent_dim)
def forward(self, z_prev, x_new_latent):
combined = torch.cat([z_prev, x_new_latent], dim=-1)
u = torch.sigmoid(self.update_gate(combined))
r = torch.sigmoid(self.reset_gate(combined))
c = torch.tanh(self.candidate(torch.cat([r * z_prev, x_new_latent], dim=-1)))
z_new = (1 - u) * z_prev + u * c
return z_newНе пытайтесь использовать ILCP без предварительного обучения β-VAE на репрезентативных данных. Вы получите шум. Я рекомендую сначала насобирать датасет диалогов агентов, обучить VAE, а потом уже встраивать Memory Manager.
Для обучения β-VAE понадобятся GPU. Рекомендую арендовать A100 на RunPod — у них хорошие цены и быстрый деплой контейнеров.
1 Выбор размерности латентного пространства
Эмпирическое правило: latent_dim = log2(context_length) * 64. Для контекста в 32K токенов — 64 * 15 = 960. Но на практике начинайте с 512 и смотрите на метрики реконструкции.
2 Предобучение β-VAE на логах агентов
Соберите последовательности вида [input_1, output_1, input_2, output_2, ...] длиной в среднем 10 шагов. Каждый шаг — эмбеддинг последнего токена LLM (возьмите hidden states). Обучите VAE с β в диапазоне [1, 10]. Выберите β, при котором качество реконструкции на валидации не падает ниже 95%.
3 Интеграция с LLM агентом
При старте агента инициализируйте z как нулевой вектор. На каждом шаге: получаете ответ от LLM, затем эмбеддинг конкатенации [запрос, ответ] прогоняете через энкодер VAE (получаете x_latent), и обновляете z через GatedMemoryUpdater. Сохраняете z в памяти агента.
4 Передача памяти между агентами
Когда один агент вызывает другого, он передает только свой текущий z (512 чисел float). Принимающий агент использует его как начальное состояние своего Memory Manager. Никакого копирования контекста.
| Метод | Скорость передачи | Интерпретируемость | Требования к памяти |
|---|---|---|---|
| RAG | Средняя (зависит от поиска) | Высокая | Векторное хранилище |
| MemoryLLM | Низкая (слоты внимания) | Средняя | Дополнительная память модели |
| ILCP (наш метод) | Высокая (один вектор) | Низкая (латентное пространство не читается) | Минимальная (512 floats) |
Если ваша система работает на vLLM, изучите 5 техник оптимизации vLLM — они помогут снизить задержки при большом числе агентов.
Типичные грабли и как на них не наступить
Первая и главная ошибка — игнорировать cold start. Если агент только что родился и получил чужой z, он не понимает, что это. Нужно либо передавать вместе с z небольшой текстовый дамп (первые 2-3 шага истории) для контекстной настройки, либо обучать VAE на смешанных данных от разных агентов.
Вторая грабля — затухание памяти. Со временем обновления через gated MLP могут привести к дрейфу z в сторону среднего. Решение — добавлять residual connection и периодически (каждые N шагов) форсировать реконструкцию через декодер для сверки с реальной историей.
Третья — выбор β. Слишком маленькое β (<1) — VAE вырождается в обычный автокодировщик, латентное пространство нерегуляризовано, передача между агентами ломается. Слишком большое β (>10) — агент теряет детали, например, забывает упомянутые пользователем факты. Оптимум обычно лежит в диапазоне 2-6.
Четвертая — отсутствие аугментации данных. Обучайте VAE на синтетических последовательностях с разными стилями диалогов. Иначе модель не обобщит на невиданные сценарии.
Когда агентов станет 1000, ILCP спасет от перегрузки контекста — подробнее в гайде по масштабированию LLM.
Готовы пожертвовать интерпретируемостью?
ILCP — это мощный инструмент, но за скорость и компактность вы платите прозрачностью. Вы не сможете заглянуть в латентный вектор и понять, что именно помнит агент. Для многих задач это приемлемо (программирование, планирование, 6G). Но если вам нужна объяснимая память — присмотритесь к MemoryLLM или LCME.
В любом случае, ILCP — один из самых горячих методов на ICML 2026. Внедряйте, тестируйте, но не забывайте про cold start и β. Удачи.