Инженерные документы
Компакция контекста: карточка состояния для длинных ранов
Статус: действующий механизм (v1).
#1. Проблема
История messages строго append-only (prompt cache на префиксе), поэтому
контекст рана растёт на каждом ходе. Измерения (2026-11, 1067 ранов):
p50 ≈ 56k токенов, p90 ≈ 151k, максимум 730k (~1.8–2.0 МБ пейлоада при
2.4–3.0 виртуальных байта на токен). Суммарно раны израсходовали ~1.76 млрд
input-токенов. Два следствия:
- Пайлоад:
call_modelедет одним событием в Temporal, гдеlimit.blobSize.error= 2 МиБ. Перевал — это TMPRL1103 и молчаливая петля ретраев (инцидент 2026-11-08). Картинки и крупные тул-аутпуты ускоряют рост (см. гард и элисию в kernel/agent/workflow.go). - Деньги: каждый ход повторно шлёт всю историю — сумма input-токенов квадратична по длине рана.
#2. Принцип
Когда запрос перестаёт влезать в бюджет — не гони его. Сожми середину истории в карту состояния, оставь хвост дословно, работай дальше.
Компакция — оптимизация, а не гейт: её сбой никогда не останавливает
ран (событие context.compaction_failed, а пейлоад-гард остаётся честным
рубежом).
#3. Механика
- Триггер — на границе хода, перед планированием
call_model: сериализованный запрос >compactionTriggerBytes(1.2 МБ ≈ 400–500k токенов: выше нормального рана (p90 ≈ 150k), ниже потолка 2 МиБ). - Активити
kernel.compact_context— один вызов дешёвой дефолтной модели (та же роль, что у генерации заголовков рана) над дайджестом истории (сообщения с head+tail капом, скриншоты — числом, без байт). На выход — строго структурированная карточка состояния (≤ 8 КБ): `## Task / ## Decisions / ## Files and artifacts / ## Verified /#Open questions / ## Next step`. Промпт требует идентификаторы
(пути, id, хэши, числа) передавать дословно. - Пересборка
messages: system-промпт и исходный запрос юзера — всегда дословно (контракт с пользователем), карточка — одним system сообщением с рамкой («это запись вытесненной работы»), хвост — последние 2–8 целых групп хода (assistant + его tool-ответы; нарушать tool-contiguous контракт API нельзя), ≤ 256 КБ. - Событие
context.compacted(turn, bytes_before/after, card, kept/dropped) — карточка видна в просмотре рана: «что помнит агент».
#4. Согласованность
- Пересборка — чистая функция от истории и карточки; карточка приходит результатом активити и лежит в истории Temporal — replay детерминирован.
- Всё под гейтом
workflow.GetVersion("context-compaction"): раны, начатые старым бинарём, переигрываются без компакции и без пересборки. - Компакция — единственное намеренное нарушение append-only инварианта; цена — одна перезапись prompt cache при сдвиге границы.
#5. Ограничения
- Компакция сжимает будущие запросы, но не уменьшает уже накопленную
историю workflow: каждый call_model input навсегда остаётся в ней, и
длинный ран упирается в
limit.historySize.error(Temporal терминирует workflow — инцидент 2026-11-09). Решено в два слоя: ContinueAsNew — на чистой границе хода, когда накопленные байты запросов превышают бюджет (TEMPORALITY_CONTINUE_AS_NEW_BYTES, 128 МиБ), ран продолжается в СВЕЖЕМ исполнении с тем же run id: диалог, счётчики и цепочка событий журнала переносятся (RunInput.Resume), история обнуляется; и поднятый потолокlimit.historySize.errorдо 500 МиБ (docker-compose) как запас. Граница берётся только без ожидающих background-операций (их futures не переносятся). - Сжатие — потеряноe: карточка не гарантирует сохранность каждого факта транскрипта. Правило удержания: идентификаторы и верификации — дословно, рассуждения — резюме.
- Порог и размеры — глобальные константы (kernel/agent/compaction.go); per-agent настройка появится, если практика покажет необходимость.
- Модель сжатия — роль
compactionиз настроек харнеса (harness.compaction.modelв панели супервайзера; фолбэк — judge-модель, затем env-модель). Смена модели — правка настройки, не кода (docs/harness-settings.md).