Опубликовано
State вместо истории: как малая модель с окном 32k тянет многоходовый чат
Автор: Гусев Николай [портфолио]
Темы: agent-memory, ai-agents, benchmarks, local-models
Серия про длинные сессии подходит к практике. В прошлой части я разобрал SKILL.state от Google и Purdue - схему, где вместо накопления истории каждый ход включает только спецификацию, стейт и последнее наблюдение. Авторы замерили экономию в 16-50 раз на Gemini-3-Flash. Здесь я проверил эти три механики на своей карте: какой промпт уходит на 25-ходовую сессию у каждой и где именно это ломается, если модель маленькая. Финальный вопрос - практический: как получить способность к многоходовым чатам от модели, у которой окна всего 32k, и при этом не переплачивать токенами.
Три механики на одном бенчмарке
Сценарий один для всех: журнал заявок, 25 ходов, на каждом ходе модель получает фрагмент лога, где появляется контрольный код. На 25-м ходе я спрашиваю код первого хода. Три конфигурации:
Первая - ReAct, классика агентов: в запрос на каждом ходу уходит весь накопленный транскрипт. Ход n стоит сумму всех предыдущих.
Вторая - SKILL.state: агент на каждом ходу выдает патч к JSON-состоянию, рантайм применяет его детерминированно. В запрос входит только спецификация задачи, текущий стейт и новое наблюдение.
Третья - механика Hermes Agent: короткая память охраняет последние ходы, старая история сжимается в summary, а исходные тексты остаются в архиве, откуда точный FTS-поиск достает нужный факт дословно. Это гибрид: state для работы, архив для истории.
Модель бенча - LFM2.5-8B-A1B Q4_K_M на RTX 4060 8 ГБ, 128k контекста. Сиды 301, 302, 303, температура 0, одинаковый бюджет вывода. Все три конфигурации получают одни и те же данные.
| Конфигурация | Промпт, старт -> конец | Средняя задержка | Recall моделью | Дословный recall |
|---|---:|---:|---:|---:|
| A. ReAct, весь транскрипт | 194 -> 3712 токенов, x19 | 2,8 с | 1/3 | нет архива |
| B. SKILL.state, стейт и патчи | 330 -> 371 токенов, x1,1 | 6,3 с | 0/3 | 4-6 кодов из 25 в стейте |
| C. compress + session_search | 102 -> 354 токенов, x3,5 | 4,5 с | 0/3 | поиск 3/3 |
Что скажет таблица про токены
Главная цифра таблицы - не в правой колонке, а в центре. ReAct за 25 ходов вырос в 19 раз: history-append это линейный рост на каждом ходу и квадратичная (O(T^2)) оплата за сессию. Обе стейт-механики держат промпт почти постоянным: у чистого state - плоский 371 токен, у compress - 354. Это и есть обещание SKILL.state, подтвержденное на карте за 30 тысяч рублей.
Задержка тоже считается в пользу state-схем: средний ход ReAct дороже, чем его же ход у compress-варианта, потому что каждый раз машина заново читает всю историю.
Почему оба state-режима промахнулись, и это ответ
Экономию токенов показали обе стейт-схемы. Но recall финального кода моделью промахнулся у всех: в ReAct факт есть физически, но к 25-му ходу засыпан историей (lost-in-the-middle, U-образная кривая внимания, что я отдельно подтвердил needle-тестом - 0 попаданий из 7 на середине 30k). В чистом state малая модель не удержала extraction: из 25 кодов в стейт попали 4-6, проверочный ход вернул нули.
А вот session_search в гибридной конфигурации достал правильный код из архива во всех трёх сидах. Это ключ ко всей задаче: на малой модели держат не "память модели", и не state как единственное хранилище, а состояние + внешний дешёвый поиск. Именно так устроен Hermes Agent: session_search по SQLite FTS5 над архивом сессии.
Сравнение на второй и третьей моделях подтверждает, что дело не в LFM2.5: агентные обвязки (big-pickle с 200k и muse-spark-1.3 с 1M контекста, обе с tool-use) решают тот же needle-тест четыре из четырёх. Промах середины - свойство связки малый stateless-декодер без инструментов. Даете модели инструмент поиска - и тот же тест решается тривиально.
Ответ на вопрос, как выжать многоходовость из 32k
Сложение этих результатов даёт рабочий рецепт для модели с окном 32k - на нашей карте LFM2.5 с этим окном жила бы вообще без ограничений.
Практический смысл окна в многоходовой сессии - не хранить историю, а держать по каждой из трёх компонент: спецификацию, стейт и пришедший ход. 32k для этого достаточно: спецификация - сотни токенов, стейт - сотни, наблюдение - сотни. Экономия против ReAct - квадратичная: чем дольше сессия, тем заметнее.
Рецепт из четырёх пунктов.
-
Держать в контексте только спецификацию, стейт и последний ход. Историю в архив на диск - дословно, а не в пересказ.
-
Стейт собирает модель патчами, но рантайм применяет их детерминированно и валидирует. Малая модель промахивается мимо полей (у нас 4-6 записей из 25); без валидации вы получите не экономию, а молчаливую потерю. Помочь модели можно строгой схемой ответа и подсказками в системном промпте.
-
Старый факт не "вспоминается" - ищется. substring-поиск или FTS5 по архиву дешевле, стабильнее и не зависит от настроения модели. В моем замере это единственный способ recall, давший 3/3.
-
Summary нужен только как навигатор по архиву ("в какой части лога искать"), а не как носитель фактов. Суммаризация теряет связи - это описано на Хабре в моей статье про деградацию на длинных сессиях.
Замки и честные оговорки
Один GPU, одна модель, три сида, синтетический журнал, reasoning-режим на малом бюджете (часть финальных проб LFM2.5 уперлась в лимит вывода и не дошла до ответа - это зафиксировано, отдельно от результата поиска). Число 0,94 из статьи авторов SKILL.state не переносится на эту карту: другой класс модели и structured output. Ссылки: arxiv 2608.26263, Хабр про кривую внимания.
🛠 Материалы
Код харнесов, тесты и сырые результаты присутствуют в папке 2026-09-12-skill-state-bench. Код Hermes Agent и механизм session_search - в репозитории проекта. Следующей частью я хочу разобрать, как отделить надежный поиск от работы модели и как измерять это в живой агентной сессии.