Когда у кодового агента заканчивается контекст, случается компакт: история сворачивается в саммари, сессия едет дальше. У всех, кто видел это посреди работы, рефлекс одинаковый: тянуть до последнего. Саммари теряет детали, а компакт выбрасывает кэш промпта, за который ты уже заплатил. Логично же подержаться.

Логично ровно наполовину. Вторая половина стоила мне нескольких недель с неправильно выставленным порогом.

Кэш не переживает границу

Кэшированный входной токен стоит десятую часть обычного, поэтому длинный стабильный префикс кажется дешёвым. Он и правда дёшев за токен. Просто платишь ты за него каждым ходом.

Я прошёл по двенадцати границам компакта в своих транскриптах и сравнил цифры кэша с обеих сторон. До границы из кэша читалось от 93k до 950k токенов за ход. После границы читается 15–31k. А следующие несколько ходов показывают крупную запись, от 34k до 403k: кэш строится заново, теперь уже из саммари.

Границу переживает только стабильный префикс. Всё, что сессия нажила, из кэша исчезло, потому что исчезло из контекста. Так и задумано, и отсюда простое следствие: «подожду с компактом, сберегу кэш» не размен, а самообман. Ожидание ничего не сберегает. Оно просто заставляет платить полную цену за большой контекст каждый ход, пока ты всё равно не скомпактишься.

Значит, компактить раньше

Claude Code даёт задать размер контекста, на котором он компактится сам: autoCompactWindow в ~/.claude/settings.json или команда /autocompact на одну сессию. У меня стояло 500k. Я поставил 250k.

Дальше я сравнил неделю до правки с двумя днями после. Сессии поделены по тому, стартовали они раньше переноса порога или позже; считались только основные сессии, все проекты, 43k ассистентских ходов с одной стороны, 6,6k с другой.

допосле
стоимость хода1.000.66
медиана контекста249k150k
p90 контекста535k244k
ходов выше 300k38%5%

Если сузить базу до утра того же дня, когда работа была буквально та же самая, картина не меняется: 1.00 против 0.73 за ход, медиана падает с 237k до 150k, p90 с 389k до 244k.

Но убедило меня не это. Я разложил недельный расход по размеру контекста, на котором он потрачен, и увидел верхнюю полосу: ходы выше 500k токенов забирали от 14% до 31% всех денег. Каждый день. В первый полный день после правки там стало ровно ноль. Полоса эта целиком накладная: та же работа, просто сделанная с огромной историей за спиной.

Чего это стоило

Компактов стало больше, тут никакого сюрприза. За первые тридцать два часа я получил 23 границы в основных сессиях, 20 из них автоматических, плюс ещё шесть внутри субагентов. Автоматические срабатывали не ровно на 250k, а между 217k и 250k: порог оставляет запас на следующий ход.

Вдвое больше, чем я прикинул заранее. Я прогонял траектории контекста за прошлую неделю против порога 250k, и модель обещала одну границу на 170 ходов. В жизни получилось около пятнадцати в день, и почти все из одного тяжёлого проекта.

Каждый компакт стоит одного вызова модели по всему контексту: примерно $0.5 и медианные 163 секунды. Двадцать девять штук обошлись в $15 и час ожидания. Деньги здесь не самое интересное: на окне 250k компакт отбивается за пять ходов уменьшившегося контекста, а сессии, где это вообще обсуждается, живут сотнями ходов; на 950k он отбивается меньше чем за два. Интереснее время, и его стоит знать до того, как копировать настройку. Сорок минут ожидания в день, а не пара минут, которую я себе рисовал.

Урок шире конкретного числа. Компакт стоит фиксированную сумму один раз. Большой контекст стоит переменную сумму каждым ходом. Как только ловишь себя на том, что бережёшь второе, лишь бы не платить первое, пересчитай арифметику.

Что ломается, когда компактишь чаще

Саммари держит решения и теряет причины.

«Выбрали A» переживает компакт. «Отвергли B, потому что померили и вышло хуже» не переживает: это читается как история, а историю саммари сжимает первой. И следующая сессия снова предлагает B, и ты платишь за тот же эксперимент второй раз. Компактить втрое чаще значит натыкаться на этот отказ втрое чаще.

Спасает хук PreCompact. Он выполняется до того, как саммари написано, и всё, что хук печатает в stdout, дописывается к инструкциям, по которым модель его пишет. Мой печатает вот это:

When writing the compaction summary, add a dedicated section titled
"Rejected alternatives and why" and fill it from the conversation being
compacted. For every approach, design, library, schema, tool or debugging
route that was considered and then dropped, record:

  - what the option was;
  - why it was rejected — keep the concrete reason: the number that was
    measured, the error that reproduced, the constraint that blocked it.
    Never flatten a reason to "did not work" or "was not suitable";
  - what was chosen instead;
  - under what condition the decision would be worth revisiting.

Две вещи про этот хук стоит знать заранее, и каждая стоила мне по компакту. Версия Claude Code 2.1.236; обвязка хуков от версии к версии меняется.

Печатай простой текст, не JSON. Обычно хук подкладывает контекст JSON-объектом с hookSpecificOutput.additionalContext. У PreCompact такого поля нет: схема заводит его для других событий, а JSON на компакте отлетает с ошибкой валидации. Весь интерфейс здесь сырой stdout.

Сохраняй инструкции пользователя. Вывод хука подставляется в newCustomInstructions, а это поле заменяет текст, который ты набрал в /compact <инструкции>, а не дополняет его. Печатаешь только своё, значит всё, что человек попросил руками, молча пропало. Читай custom_instructions из stdin хука, печатай сначала его, потом своё:

#!/usr/bin/env bash
set -euo pipefail
dir="$(dirname "$0")"
user_instr="$(jq -r '.custom_instructions // empty' 2>/dev/null || true)"
if [ -n "$user_instr" ]; then
  printf '%s\n\n' "$user_instr"
fi
cat "$dir/precompact-preserve.txt"

Зато поверх встроенного промпта саммари хук именно ложится, а не затирает его: в моих саммари остались все штатные разделы плюс добавленный. Забавно, что в API всё наоборот: у серверного компакта похоже названное поле instructions заменяет дефолтный промпт целиком. Два близких имени, два разных поведения.

Стоит удовольствие около тысячи токенов на границу. И раздел сцепляется: пункты, перенесённые в одно саммари, доезжают до следующего, пока остаются на входе.

Чего это не чинит

Дешёвая сессия не значит хорошая. Компакт пораньше означает, что модель раньше перестаёт видеть собственную историю, и есть работа, где это мешает. Скажем, длинное ревью, которому нужно держать десяток находок разом. Для таких я поднимаю окно обратно на одну сессию и плачу осознанно. В этом и разница между дорогой сессией и дорогой неделей.

И хук вытаскивает только те рассуждения, которые были записаны внутри сессии. Конец сессии он не переживает. Это уже другая задача, и под неё я написал floppy.