AI-агенты - это цикл обмена сообщениями между пользователем и языковой моделью (LLM), где для ответа пользователю модель может обратиться к доступным ей напрямую инструментам (tools) или через настроенные для неё MCP. Каждое такое взаимодействие дописывает историю диалога. Для простоты часто считают, что вся эта история и уходит в любой следующий вызов модели - иначе она «не вспомнит», о чём шла речь, и криво соберёт ответ.
История «не резиновая» - у современных моделей контекстное окно может быть огромным, но умение работать с длинным логом сильно зависит от того, насколько он структурирован. Плюс накопленная история разговора и реальный контекст, который модель видит на очередном ходе, не всегда одно и то же. В последнее время это один из фокусов развития агентских систем в рамках context engineering: что сжать, что оставить снаружи, что подтянуть инструментами только когда нужно.
В этой статье хочу рассказать про эволюцию подхода работы с длинной историей и где мы находимся сейчас. Примеры будут на LangChain - на открытом стеке легко посмотреть реализацию, которая в готовых продуктах часто спрятана. При этом LangChain достаточно популярный, развивающийся фреймворк - остальные либо делают похожие вещи, либо сами опираются на него как на базу.
Почему история агента вообще становится проблемой
Мозг агента - большая языковая модель (LLM). Текст режется на токены, модель предсказывает следующий токен, из цепочки токенов снова собирают текст. У модели есть контекстное окно: сколько информации она может учесть, когда делает очередное предсказание.
Часто на учебных слайдах рисуют «весь вход → модель → весь ответ» и контекстным окном называют «весь вход», но генерация считает окно иначе: каждый новый токен предсказывается уже с учётом предыдущих. Для последнего токена в полном ответе контекстом будет весь вход + опционально большой блок thinking/reasoning (в UI его может и не быть) + все уже сгенерированные токены ответа, кроме самого последнего, который как раз сейчас предсказывают. Т.е. ответ сам увеличивает требования к размеру контекстного окна.
Размеры контекстных окон за последние годы сильно выросли. Когда-то нормой были тысячи токенов, потом десятки тысяч, сейчас они могут считаться в миллионах. Гонка лимитов не отменила вторую проблему: большой контекст не значит, что конкретный факт в нем так же легко найти, как в маленьком. Три чётких факта в коротком окне модель вытащит увереннее, чем их же среди тысячи похожих записей в длинном контексте. Более того, качество зависит от места данных внутри контекста. Классическая работа Lost in the Middle показывает U-образную кривую: начало и конец длинного входа используются лучше, середина - хуже.
Отсюда две причины работать над сокращением истории агента - вернее, того, что попадает в контекст. Либо окно физически кончается, и его надо как-то сжать. Либо, даже при огромном теоретическом размере контекста, много разнородной информации в контексте часто ухудшает работу с конкретными фактами из истории.
Практический вывод, который современные harness’ы уже усвоили: один бесконечный неструктурированный диалог «обо всём» - плохой режим. Удобнее держать контекст вокруг текущей задачи, а не копить годы переписки в одном потоке сообщений (messages) - даже если для пользователя снаружи это всё ещё один чат.
Пробежимся по истории эволюции подходов.
Обрезка хвоста: ещё не суммаризация, но близка по идее
Самый простой способ уменьшить контекст - «помнить только недавнее» т.е. оставить последние N сообщений, а остальное выбросить. Это как «память золотой рыбки», которая просто забывает всё, что было за пределами последних 3 секунд (чтобы не оскорбить золотых рыбок, биологи давно доказали, что память у них на самом деле гораздо лучше).

В экосистеме LangChain чистый rolling window для всей истории агента - обрезка списка сообщений trim_messages:
from langchain_core.messages.utils import trim_messages trimmed = trim_messages( messages, # исходный список сообщений истории max_tokens=4000, # бюджет: сколько токенов максимум оставить strategy="last", # брать с конца (свежий хвост), старое отбросить token_counter="approximate", # как считать токены; есть несколько подходов с разным балансом скорости и точности )
Когда такая обрезка до сих пор уместна? Возьмём простого агента, который назначает встречи в календаре. Источник правды у него - текущее состояние календаря, а не переписка годичной давности. Длинная история почти не нужна: обычно хватает недавнего куска про ход обсуждения, обсуждения вариантов и уточнение вопросов. В идеале каждый новый запрос можно было бы начинать с чистой истории, но если это сложно организовать, то небольшой хвост прошлых реплик как раз и является простой реализацией + он даёт связанность разговора.
Вместо хвоста - блок сводки (summary)
Следующий ход очевидный - старое не просто удалить, а сжать отдельным запросом к модели и положить в историю короткий блок сводки (summary). Диалог продолжается «примерно помня», что было. Типичная форма после срабатывания:
[сводка старого] + [свежий хвост сообщений]

Оригинальная старая история теперь уже не исчезает молча - оно превращается в пересказ. Место в истории высвобождается, и смысл должен быть похож на смысл оригинальной полной истории.
В LangChain этот паттерн ближе всего к SummarizationMiddleware у create_agent: при пороге (trigger) по числу сообщений, токенов или доле окна старая часть уходит в вызов суммаризатора, хвост задаётся политикой keep.
from langchain.agents import create_agent from langchain.agents.middleware import SummarizationMiddleware agent = create_agent( model=model, # основная модель агента tools=tools, # инструменты агента middleware=[ SummarizationMiddleware( # middleware сжатия истории через сводку model=model, # может быть отдельная (часто более дешёвая) модель для сводки # summary_prompt=..., # свой промпт суммаризатора; если не указать - дефолт из middleware trigger=("tokens", 4000), # когда сжимать: порог по токенам / messages / fraction keep=("messages", 20), # сколько свежего хвоста оставить после сводки ), ], )
В специализированных агентах часто задают свой промпт суммаризатора: что именно оставлять, а что можно свернуть. При этом мы платим отдельным вызовом модели за то, чтобы сократить занятый контекст, и впервые принимаем непростое решение: текст стал короче ценой возможной потери детали.
Жать середину, беречь начало и хвост
Когда разобрались, что модели хуже достают факты из середины длинного входа (Lost in the Middle), следующим логичным шагом для агентов стало оставить завязку диалога и свежий хвост, а серединку сжать. Для агентских сценариев это часто даже важнее, чем в общих LLM-кейсах из той работы: в начале обычно лежит постановка задачи и ключевые ограничения, а середина - длинное обсуждение деталей, которое как раз удобнее свернуть.
Форма:
[начало] + [сводка середины] + [свежий хвост]

По сути это тот же приём «сжать старое в summary», только в «старое» не пускают первые N сообщений: их явно защищают. Тогда режется именно середина между защищённым началом и хвостом.
Штатный SummarizationMiddleware в LangChain так не умеет: у него есть keep (свежий хвост), но нет параметра «оставь ещё и первые N». Если такая защита нужна, её несложно дописать.
Сам подход живой, например в Hermes Agent как раз protect_first_n + защита хвоста и LLM-сводка куска между ними - сборка head / summary / tail. У OpenRouter близкий по мотиву middle-out / context compression: края берегут, середину ужимают, чтобы влезть в окно; там чаще truncate/сжатие середины, а не обязательно отдельный вызов суммаризатора - но форма «режем середину, края важнее» та же.
Новый подход: жать контекст, но не уничтожать историю
У всех вариантов со сводкой (summary) вместо оригинальной истории одна общая проблема - любое сжатие через LLM это сжатие с потерями (lossy). Модель-суммаризатор решает, что будет важно для продолжения разговора. Она может угадать, и всё будет хорошо, может не угадать и выкинуть нужное, а может перефразировать историю так, что для будущего хода смысл просто поменяется. Ведь сводка - это просто применение определённого промпта к истории. Часть проблем снимается, если агент специализированный под конкретные задачи и вы сами в промпте суммаризатора прописали, что важно оставлять, а что нет, но даже это не всегда спасает.
Без сводки длинный агентский разговор может упереться в окно. Со сводкой он продолжает работать, но уже по «пересказу исходного диалога». Если ваш диалог - каталог артикулов, «сжать без потери смысла» почти невозможно: смысл и есть перечень.
Отсюда развилка. Можно считать, что история сообщений и есть единственная память, и продолжать её переписывать всё более умными сводками. Можно развести два понятия: накопленная информация и рабочий контекст следующего вызова модели (некий view на накопленное).
Инженерия контекста как раз про второй путь: не обязательно скармливать модели всё, что у вас есть. Можно оставить данные снаружи и дать инструменты, чтобы подтянуть нужное по запросу.
На суммаризации истории это выглядит так.
История сообщений копится как раньше - во внутреннем состоянии агента. Пока она небольшая, в контекст вызова модели уходит всё из этого состояния. Как только срабатывает порог «слишком много для окна», harness разделяет накопленное и то, что увидит модель:
Выбирает кусок, который пора сжать в сводку - например всё старше последних N сообщений.
Строит сводку по этому куску и кладёт её в состояние агента (это ещё не контекст вызова, а заготовка для view).
Сам выбранный кусок (или полный лог) сохраняет во внешнее хранилище: файл на backend / виртуальной FS агента - чтобы сырьё не исчезло.
В следующий вызов модели собирает view: свежий хвост (и иногда начало) + сводка вместо вытесненного куска. Полная история формально никуда не делась; в окно идёт собранный срез.
Агенту оставляют инструменты вроде
read_file/grep, чтобы при необходимости открыть файл полной истории и достать деталь, которой нет в текущем контексте.

В прошлом году LangChain выпустил дополнительную библиотеку Deep Agents (расширение поверх core функционала). Именно такой подход реализован в классе SummarizationMiddleware - вытесненное дописывается, в /conversation_history/{thread_id}.md, а канонический state["messages"] остается как был - меняется только то, что уходит в model call, что абсолютно не соответствует поведению оригинального LangChain.
Забавный факт: в базовом LangChain и в Deep Agents слой называется одинаково - SummarizationMiddleware, но то что они делают внутри, это вообще абсолютно разные подходы, импорт из другой библиотеки и ваш код работает уже иначе, что не очень хорошо. Знакомые убили несколько часов на дебаг этой проблемы.

Именно сюда эволюция суммаризации свернула после активного развития идеи context engeneering - сводка (summary) перестаёт быть единственной правдой о прошлом. Она становится рабочим сжатием для окна, а сырая история может оставаться отдельным артефактом, куда агент может заглянуть с помощью доступных ему инструментов.
В завершении
Жать активный контекст - уже обязательная часть современных агентов: без неё сложно держать длинные диалоги (хотя авторы обычно учат пользователей не решать все вопросы в одном чате, чтобы прибегать к ней меньше). Суммаризация - это часть более широкого тренда по управлению контекстом: разделять что агент делает и знает (полная история, большие результаты tools, skills, субагенты со своим контекстом), и что реально кладёт в контекст следующего вызова. Для пользователя это часто неочевидно - в UI видна вся переписка, но в таком виде она попадает в model call только в начале разговора, а дальше контекст и видимая пользователю история чата на самом деле уже расходятся.
Комментарии (7)

Andrey_Solomatin
12.08.2026 15:32Есть ещё заход с другой стороны, чистить сами сообщения, выкидывая из них токены в которые не несут смысла. https://github.com/juliusbrussee/caveman https://www.rtk-ai.app/

Innesiya
12.08.2026 15:32Спасибо за наглядную линейку от «обрезки хвоста» до выгрузки истории на диск. Смущает только, что защита начала статична: в длинной агентской сессии постановка задачи нередко переопределяется по ходу, и «священные» первые N сообщений устаревают. Не встречали ли подходов, где голова контекста тоже пересобирается, а не просто замораживается?

StasCh Автор
12.08.2026 15:32Собственно из-за этого суммаризация и делается отдельным вызовом LLM (этой же или попроще). Если это общий разговор, то он может пойти как угодно и у LLM есть шанс понять основной смысл и попробовать решить что тут важно, а что было обсуждением, которое зашло в тупик и его можно выкинуть, если модель правильно поняла суть, то сжатие будет неплохим, если перепутала, то будет хуже. При этом после нескольких суммаризаций шансов, что все детали будут учтены становится все меньше. Защита первой части помогает прежде всего в специализированных агентах, где процесс завязан на первоначальной постановке и это явное решение автора агента, который знает его специфику. В целом переполнение контекстного окна почти всегда зло, суммаризация лишь попытка продлить пользователю возможность продолжить диалог попробовав сделать это не сильно больно.
maxnoosphere
крутая тема, сам мучаюсь с окном контекста в клоде когда проект раздувается. а как решаешь проблему с тем что агент накопил состояние а потом окно схлопывается и инфа теряется? просто сбрасываешь сессию или как-то хитрее?
Andrey_Solomatin
Я пока сам разбиваю на подзадачи и делаю каждую в отдельном окне. Я часто выкидываю первую версию и разбиваю на более мелкие подзадачи. Код потом смотреть людям и нужно чтобы комитеты были осмысленными и небольшими.
Очень хорошо видны косяки архитектуры: когда простое изменение затрагивает десяток файлов то что-то пошло не так.
StasCh Автор
Сильно зависит от проекта, если это разработка, то у меня как правило любой новые в жизни проекта - отдельный чат для формирования идеи и ее "раскрытия" или обсуждения - где результат пишется в файл, потом отдельным чатом идея превращается в детальную спецификацию для реализации (часто с несколькими отдельными работами), потом отдельным чатом прошу взять спецификацию и начать реализацию каждого пункта из спецификации в отдельном субагенте (у каждого будет свой короткий контекст и в родительский чат они выкинут только результат), а потом проверить общий результат.
Чем меньше и понятнее задача для чата, тем лучше он ее сделает. сейчас Grok только что выпустил модель с приличным контекстным окном и неплохой ценой, только вся фишка, что после 200K токенов цена удваивается, фактически они перешли к экономическим стимулам для пользователей не спрашивать все в одном чате