compact-guard — не терять состояние при сжатии контекста
Сжатие контекста (/compact вручную или автоматическое при переполнении окна) выбрасывает
большую часть диалога. Автоматическое приходит без предупреждения: только что шла работа —
и вот агент уже не помнит, что правил, о чём договорились и зачем запускал фоновую задачу.
Дальше он достраивает пробел по догадке и уверенно врёт оператору.
Скилл закрывает это двумя механическими хуками (они работают сами, без участия модели) плюс дисциплиной, которую соблюдает агент.
Как устроен механизм (уже установлен и проверен 2026-08-15)
| Слой | Что | Когда срабатывает |
|---|---|---|
| PreCompact-хук | scripts/precompact_snapshot.py |
ПЕРЕД сжатием, и авто, и ручным |
SessionStart-хук (matcher compact) |
scripts/postcompact_restore.py |
СРАЗУ ПОСЛЕ сжатия |
| UserPromptSubmit-хук (уже был) | ~/.claude/hooks/context_guard.py |
на 80% и 90% заполнения, когда оператор пишет промпт |
Оба новых хука зарегистрированы в ~/.claude/settings.json (бэкап перед правкой:
settings.json.bak-precompact-20260815).
Что пишет PreCompact-хук
Снапшот в C:\Users\<you>\.claude\compact-snapshots\snap_<дата>-<время>_<сессия>.md плюс указатель
latest.json. Внутри — только механически проверяемое, без интерпретации:
- последние указания оператора дословно (основной источник — user-записи со СТРОКОВЫМ
content, они пишутся сразу при отправке;
last-prompt— запасной, он пишется с лагом в один ход — см. P-003); - файлы, которые правились (
Write/Edit/NotebookEdit); - последние выполненные команды;
- состояние git (ветка, HEAD, незакоммиченное) — если папка вообще git-репозиторий;
- заполнение контекста на момент сжатия, триггер (
auto/manual).
Хранится 40 последних снапшотов, старые удаляются сами.
Хук НИКОГДА не блокирует сжатие. Автокомпакт срабатывает, когда окно уже переполнено; заблокировать его — заклинить сессию. Любая внутренняя ошибка хука тоже проглатывается: сорванный снапшот не должен ронять работу.
Что делает SessionStart-хук после сжатия
Инъецирует в свежий контекст короткий указатель (не весь снапшот — иначе тут же забьём только что освобождённое окно): путь к снапшоту, три последних указания оператора, файлы в работе и требование сверить SESSION-STATE.md до продолжения.
Есть защита от протухания: снапшот старше 30 минут игнорируется молча — иначе после перезапуска агент получил бы чужое старое состояние и принял за своё.
Дисциплина агента — что делать ТЕБЕ
1. При предупреждении context-guard (80%) — писать хендофф, не откладывать
Хук context_guard.py на 80% просит обновить SESSION-STATE.md. Это не формальность: после
80% до автокомпакта может остаться один большой Read. Обновляй сразу, в том же ответе.
Что писать — по скиллу session-handoff (структура, обязательные поля, владелец, правило
устаревания). Этот скилл структуру не дублирует.
2. Дежурная гигиена, чтобы автокомпакт не был катастрофой
- Не держать состояние только в голове. Договорённость с оператором, принятое решение,
найденный факт → сразу в SESSION-STATE.md или в задачу (
TaskCreate/TaskUpdate). Задачи переживают сжатие, рассуждения — нет. - Фоновые задачи — с описанием.
run_in_background+ внятныйdescription: после сжатия единственный след задачи — её описание. - Крупные чтения — ближе к началу, а не на 85% заполнения.
3. Сразу после сжатия — порядок восстановления
Хук уже подсунул указатель. Дальше:
- Прочитать SESSION-STATE.md проекта — там смысл: решения, план, что нельзя трогать.
- Прочитать снапшот из
compact-snapshots\— там механика: что реально правилось и запускалось. - Сверить с диском фактом, а не памятью. После сжатия память ненадёжна: файл, который «точно был записан», мог не записаться. Проверять командой/чтением (#SA-1).
- Не начинать новых крупных задач, пока пункты 1-3 не закрыты.
4. Чего НЕ делать после сжатия (anti-patterns)
- ❌ Достраивать пробел по догадке и докладывать оператору как факт. Не помнишь — прочитай снапшот или скажи «не помню, проверяю».
- ❌ Считать, что раз в снапшоте файла нет, работа не делалась. Снапшот берёт хвост транскрипта (2 МБ) и последние 25 файлов — на очень длинной сессии ранние правки в него не попадут.
- ❌ Заново запускать долгую задачу «на всякий случай», не проверив её результат на диске.
- ❌ Просить оператора пересказать, что было. Для этого и существует снапшот.
Границы с соседними скиллами
session-handoff— СТРУКТУРА хендофф-документа (обязательные поля, владелец, устаревание). Используются вместе: там — что писать, здесь — когда и как пережить сжатие.workflow— бутстрап ролей/AGENTS.md для нового проекта. Не про сжатие.- §6/§30 глобального
CLAUDE.md— пороги гигиены контекста (80/90%) и правила SESSION-STATE.md. Этот скилл — исполнительный механизм под них.
Проверка работоспособности
# снапшот вручную (подставить свой transcript_path)
echo '{"session_id":"probe","transcript_path":"<путь к .jsonl>","cwd":"<проект>","trigger":"manual"}' \
| python C:/Users/<you>/.claude/skills/compact-guard/scripts/precompact_snapshot.py
# что записалось
ls C:\Users\<you>\.claude\compact-snapshots\
# что увидит агент после сжатия
echo '{"source":"compact"}' | python C:/Users/<you>/.claude/skills/compact-guard/scripts/postcompact_restore.py
Хуки fail-safe: при внутренней ошибке они молча возвращают exit 0. Поэтому «команда не
упала» ≠ «снапшот записан» — проверять наличие файла на диске (#SA-1; на этом уже споткнулись при
отладке 15.08: хук отвечал успехом, а папки не создавал).
История
Создан 2026-08-15 по запросу оператора: «важно чтобы агент перед компактом фиксировал состояние
и писал хендофф». Код хуков сгенерирован через IRON-MODE (workflow/scripts/gen_code.py,
qwen3-coder) по спекам scripts/_spec_*.md; при вычитке найдено и исправлено 5 дефектов
генерации — в том числе json.dump без файлового дескриптора, необъявленные переменные в
latest.json и сбор указаний оператора. ⚠ Прежний вывод «в type=="user" только результаты
инструментов, промпты — в last-prompt» оказался ОШИБКОЙ анализа (P-003, 2026-08-15): tool_result
лежат в user-записях со СПИСКОВЫМ content, а реальные промпты — в user-записях со СТРОКОВЫМ
content, и пишутся сразу; last-prompt пишется лениво на следующем ходе и терял самую свежую
фразу оператора. Теперь основной источник — user-строки, last-prompt запасной. Правки вносить в
спеки, не в сгенерированный .py.
Дефект №6 — найден на живом автокомпакте 2026-08-15, а не при вычитке. Оператор попросил
«давай проверим на тебе» — первый реальный автокомпакт после установки хука дал exit=0 и
{"suppressOutput":true} (видимость успеха), но файл снапшота на диске оказался 0 байт, а
latest.json не обновился. Причина: скрипт форсировал UTF-8 только на sys.stdout, не на
sys.stdin. На cp1251-консоли оператора (CLAUDE.md §2) sys.stdin.read() декодировал JSON-payload
от Claude Code не тем кодеком — кириллица в cwd (в частности слово «Цензор») превращалась в
суррогатные code points; f.write() в файл со строгим encoding="utf-8" падал
UnicodeEncodeError: surrogates not allowed, исключение ковталось широким except, оставляя
файл открытым-и-пустым (обнулён open(...,"w") до записи содержимого). Поймано ТОЛЬКО потому что
проверил файл на диске после «успешного» прогона (§SA-1), не поверил коду возврата. Диагностика —
патч debug-копии скрипта (traceback в файл вместо ковтания), не десяток догадок. Фикс — добавлен
sys.stdin.reconfigure(encoding="utf-8", errors="replace") в обоих хуках сразу после
stdout.reconfigure. Перепроверено полным циклом: precompact с реальным payload → снапшот 5379
байт с корректной кириллицей и настоящими last-prompt → postcompact вернул корректный
additionalContext с правильным путём. Урок: stdin и stdout форсировать UTF-8 СИММЕТРИЧНО,
не только вывод — любой хук/скрипт, читающий payload через stdin на этой машине, подвержен той же
дыре, если её не закрыть явно.