🥗 Диета контекста агента: свои же скиллы съедают окно
Класс задач: агент (Hermes, OpenClaw, любой CLI-агент) стал дорогим и «упирается в окно», а причина — не задачи, а собственные скиллы/заметки/инструкции. Этот скилл даёт замер, разрез и проверку. Три шага, все измеримые.
Когда применять
- контекст часто упирается в предел и срабатывает компакция/сжатие истории;
- счёт за токены вырос без роста числа задач;
- у агента десятки скиллов, и часть из них — «простыни» на 50–150 КБ;
- надо передать правило другим (людям, второму агенту) — тогда отдавайте скилл папкой целиком.
Что здесь важно понять сразу
Дамп скилла приходит в контекст целиком и не сжимается: компрессор запросов трогает
прошлые сообщения (обычно только роль tool), а скилл попадает в запрос последним —
по устройству он вне сжатия. Дедупа тоже нет: дважды загруженный скилл — два полных дампа.
Единственный работающий рычаг — не пускать монолит в контекст: SKILL.md короткий,
тяжёлые разделы отдельными файлами и по требованию.
Замер (шаг 1): не верить, а посчитать
Три числа, которые всё решают:
- Размер одного дампа — сколько символов/токенов отдаёт ваш инструмент чтения скилла
(примерно 1 токен на 3,5–4 символа для русского текста). Прямая проверка:
skill_view(Hermes) илиwc -cпо SKILL.md + его references. - Доля повторов за сессию — сколько раз за сессию грузится один и тот же скилл. Считается по журналу вызовов инструментов (у Hermes — сессии/логи, у OpenClaw — свои логи).
- Общий объём запроса и его состав по ролям — если под рукой прокси или логи API,
добавьте туда счётчик «символов по ролям + дубли внутри запроса». Готовый рецепт
(только измерение, без изменения запроса) — см.
references/measurement-recipe.md.
Наш случай (24 сентября 2026, по 109 запросам живого трафика): выводы инструментов — 88,2 % объёма запроса; медианный запрос 568 188 символов (среднее 510 260; часто цитируемые 465 743 — это ОДИН замер, а не медиана); тяжелейшие блоки — дампы скиллов 90–98 тыс. символов (~23–24 тыс. токенов); один и тот же скилл грузился до 10 раз за сессию; топ-14 сохранённых оригиналов в базе прокси = 1,42 млн символов. Точных повторов внутри одного запроса — 0 символов из 1,86 млн: дедуп внутри запроса на таком трафике ничего не даёт (обобщать на любой трафик нельзя — проверяйте своим замером). Токены по всему документу считаются одним коэффициентом — символы ÷ 4 (для русского текста реальный делитель меньше, то есть это нижняя оценка). Компакция сессии — наблюдение, а не измерение.
Разрез (шаг 2): скилл-монолит → SKILL.md + references
# каталог скиллов вашего агента (подставьте свой)
SKILLS_DIR="${SKILLS_DIR:-$HOME/.hermes/skills}"
# сухой прогон: покажет, что и куда уедет, ничего не меняя
python3 scripts/split_skills.py --root "$SKILLS_DIR"
# один скилл
python3 scripts/split_skills.py --root "$SKILLS_DIR" <категория>/<скилл> --apply
# все, что больше бюджета (по умолчанию 20 000 символов на файл инструкции)
python3 scripts/split_skills.py --root "$SKILLS_DIR" --apply
Как работает: frontmatter, вступление и ведущие разделы остаются в SKILL.md, пока суммарно
не упрутся в --budget (по умолчанию 20 000 символов); остальные разделы переносятся
дословно, пачками по --chunk (38 000 символов), в references/, а в SKILL.md
добавляется указатель. Ничего не переписывается и не выбрасывается — только переезжает.
Бэкапы исходников: каталог из --backup-dir (по умолчанию $HOME/.skill-split-backups).
Проверка (шаг 3): без неё не считается сделанным
python3 scripts/split_skills.py --root "$SKILLS_DIR" --verify-only
# → «mega-skill: OK: заголовков потеряно 0» по каждому скиллу
Важно: --verify-only читает бэкапы из каталога --backup-dir — указывайте его явно,
иначе проверять будет нечего (скрипт теперь сообщает об этом и возвращает код 2, а не молчит).
Скрипт сверяет все заголовки H2 исходника с суммой SKILL.md ∪ references и возвращает
ненулевой код, если хоть один заголовок потерян, — годится как гейт в CI. Дополнительно есть независимая сверка по строкам — scripts/check_split.py <бэкап> <каталог скилла>:
все непустые строки исходника должны найтись в сумме частей. Плюс регрессионные тесты самого
разрезателя: python3 scripts/test_split_skills.py (проверяют в том числе границу бюджета,
одинаковые заголовки и работу --verify-only как гейта). И обязательно живая загрузка: skill_view
(Hermes) или команда показа скилла (OpenClaw) должна отработать и показать новые файлы
в списке ссылок.
Результат на нашем парке (эталон для сравнения)
Единица — символы (len), не байты: в кириллице байт почти вдвое больше, и путаница
единиц уже один раз дала ложный вывод. В скобках — байты для сверки с wc -c.
| Скилл | Было, симв. (б) | Стало, симв. (б) | Эффект |
|---|---|---|---|
| tinvest-trading-bots | 99 982 (151 736) | 19 532 (29 582) | −80 % |
| vibo-runtime-pitfalls | 93 568 (137 698) | 20 381 (29 382) | −78 % |
| vibo-skillqa | 75 130 (109 451) | 19 317 (27 716) | −74 % |
| marketplace-publishing | 94 457 (109 314) | 20 432 (22 258) | −78 % |
| vibo-ops | 99 970 (104 825) | 20 173 (21 530) | −80 % |
| эти пять | 463 107 | 99 835 | −78 % |
| весь парк, 20 скиллов | 973,659 | 379,091 | −61 % |
Один дамп тяжёлого скилла: ~23–24 тыс. токенов → ~5 тыс. Типичная задача (2–3 скилла): 70–100 тыс. токенов → 12–15 тыс. Проверка: 0 потерянных заголовков на 20 скиллах.
Правила, чтобы не выросло заново
- SKILL.md ≤ 20 000 символов. Всё длиннее — в
references/,templates/,scripts/. - Не перечитывать скилл, который уже в контексте. Проверяйте по журналу, а не по памяти.
- Один скилл — один класс задач. Скилл с 30 разделами почти всегда два-три скилла.
- Расти в глубину, а не в ширину: новый материал дописывать в
references/, а в SKILL.md — одну строку указателя. - После каждого разреза — проверка (шаг 3) и живая загрузка.
Ловушки (проверено на себе)
- Бэкапы не кладите внутрь каталога скиллов: файл с именем инструкции, оказавшийся внутри
корня скиллов, будет загружен как отдельный скилл. Поэтому бэкап уходит ВНЕ корня
(каталог
--backup-dir) и отличается по имени от самой инструкции. - Не сжимайте сами разделы. Разрез — это переезд дословно. Любая «умная» переписка в момент разреза = потеря проверенного содержимого.
- Заголовки внутри код-фенсов (
```) — не заголовки. Скрипт это учитывает; ручной разрез поgrep '^## '— ломает (проверяйте себя независимой сверкой строк). - Кириллица в именах файлов. Скрипт транслитерирует заголовки в ASCII — иначе часть инструментов и репозиториев с кириллическими путями работает непредсказуемо.
- Порядок разделов сохраняется. Если для скилла важен порядок чтения — проверьте нумерацию в указателе после разреза.
- Указатель тоже занимает место. Если не учитывать его при планировании, итоговый файл вылезает за бюджет (дефект, найденный независимым аудитом 24.09.2026; в текущей версии бюджет подбирается по итоговому файлу, а если не вышло — печатается предупреждение).
- Одинаковые заголовки. Два раздела с тем же H2 раньше могли схлопнуться в один файл с одной строкой указателя; теперь имя всегда уникально в пределах запуска.
- Скиллы ≤ бюджета не трогаем. Скрипт сам их пропускает («в бюджете»).
- Смысл проверяйте на живой задаче, а не только по размеру: загрузите один скилл и прогоните типовую задачу из него.
Откат
BACKUP_DIR="${BACKUP_DIR:-$HOME/.skill-split-backups}"
cp "$BACKUP_DIR/<имя>.SKILL.md.bak-<дата>" <путь>/<имя>/SKILL.md # вернуть как было
rm -rf <путь>/<имя>/references/<новые-файлы> # убрать переехавшее
Передача другим
Отдавайте папку скилла целиком (инструкция + scripts/split_skills.py) — зависимостей нет,
нужен только Python 3.9+. Второму агенту: скопировать папку в его каталог скиллов, затем
прогнать --root <его каталог> сначала в сухом режиме и показать ему --verify-only после
записи. Людям: тот же артефакт + README.md с постановкой задачи и числами.
