160M токенов за один PR — и где они осели
Одна OMP-сессия проекта TasK / codexcli, модель glm-5.2. Период активности: 2026-08-12 19:58 → 2026-08-14 10:19 (Asia/Novosibirsk). Работа над EPIC chat-quality-reliability и merge-PR #2829. Отчёт построен по одному зафиксированному JSONL-снимку; сырые промпты, исходный код, приватные пути и результаты production-запросов намеренно не встраиваются.
Главный вывод: 98.2% raw-токенов — это пере-чтение накопленного контекста (cacheRead). Свежий ввод и ответ модели вместе — 1.8%.
Состав расхода
Полезной «новой» работы (fresh input + output) — 2.94M токенов. Остальные 157.2M — повторное учёние уже накопленной истории на каждом из 723 вызовов.
Длина контекста
Средний raw на ответ — 221.4K. Контекст дошёл до 438.5K, после чего сработали две авто-компактизации (на 438K и 323K), и он снова отрастал.
Медиана ответа: 219.6K; p90: 390.7K; максимум: 438.5K.
Время и паузы
Календарная длительность — 38.3 ч; оценка активной работы агента (без межсессионных пауз) — 4.96 ч.
Сессия возобновлялась 42 раза; основная масса raw пришлась на возобновления при уже большом контексте.
Наблюдение: что именно раздувало расход
- На один ответ в среднем пришлось 221.4K raw, из них ~217.5K — кэшированный контекст.
- Первые 25 ответов стоили в среднем 57.9K; окно 201–300 (до первой компактизации) — 297.7K. Затем две компактизации опустили среднее до ~209–251K, но оно снова ползло вверх.
- Reasoning (thinking) дал ~1.38M символов, видимый output — ~0.21M. Увеличение «длины ответа» не объясняет объём — расход делает вход, а не выход.
Рост/сброс расхода по окнам
Среднее на ответ росло до ~298K, падало на компактизациях и снова отрастало. Это не монотонный рост, а «накопил → сбросил → накопил».
| Окно | Ответов | Raw | Среднее | Cache | Fresh + out |
|---|---|---|---|---|---|
| Первые 25 | 25 | 1.45M | 57.9K | 1.35M | 94.5K |
| 26–100 | 75 | 9.91M | 132.1K | 9.77M | 135.2K |
| 101–200 | 100 | 22.23M | 222.3K | 22.09M | 143.3K |
| 201–300 | 100 | 29.77M | 297.7K | 29.25M | 519.9K |
| 301–500 (после compact.) | 200 | 50.29M | 251.5K | 48.84M | 1.45M |
| Последние 223 | 223 | 46.53M | 208.7K | 45.94M | 598.8K |
Макро-фазы работы
Границы — по пользовательским продолжениям и таймлайну PR; названия — аналитическая классификация. Тексты запросов не публикуются.
PR смержен 08-14 09:53 (Nsk). M4 включает вторую компактизацию (09:56, на 323K).
Токены vs строки кода: что реально принес PR #2829
Состав 602 добавленных строк (по net-диффу merge):
| Категория | Куда | ≈ строк net |
|---|---|---|
| Продуктовый код + шаблон | SendCommandHandler, ChatMessageModel, ChatProcessingErrorCodeResolver, 5 контроллеров, _message_user.twig | ~135 |
| Тесты | unit + integration (handler, resolver, mapper, entity) | ~196 |
| Миграция БД | Version20260812130648 (lastProcessingErrorCode) | 26 |
| Документы / отчёты / todo | eval-report, EPIC-план, backlog, закрытые/отменённые задачи | ~220 |
| Конфиг | .env — candidate CHAT_SYSTEM_PROMPT | ~4 |
.env и eval-report правились по 10–15 раз каждый (коммиты «refactor(chat): правило N»). То есть реальные правки промпта итерировались в git десятки раз, а в net-дифф почти не видны.Инструменты и объём возвращаемых данных
Зафиксировано 752 tool call. Размер tool result — в символах, не токенах; это независимый индикатор того, что наполняло контекст.
| Инструмент | Вызовов | Текст результата | Макс. один | Ошибок (isError) |
|---|---|---|---|---|
read | 174 | 601 683 | 40 064 | 1 |
grep | 35 | 225 629 | 23 416 | 1 |
bash | 324 | 201 931 | 6 969 | 2 |
edit | 121 | 111 974 | 6 779 | 5 |
glob | 23 | 30 434 | 9 779 | 0 |
todo | 21 | 25 773 | 2 251 | 1 |
eval | 24 | 3 587 | 718 | 0 |
ask | 9 | 3 332 | 2 287 | 2 |
write | 21 | 3 015 | 243 | 1 |
Инвентарь OMP tools
Фактические tool calls ассистента. Всего: 752.
| Tool | Вызовов |
|---|---|
bash | 324 |
read | 174 |
edit | 121 |
grep | 35 |
eval | 24 |
glob | 23 |
todo | 21 |
write | 21 |
ask | 9 |
CLI-утилиты
Команды верхнего уровня внутри bash (консервативный счётчик, после дедупа по whitelist утилит; cd/echo как обвязка не показаны).
| Утилита | Запусков |
|---|---|
tail | 265 |
git | 210 |
head | 197 |
grep | 157 |
timeout | 134 |
php | 50 |
sed | 31 |
docker | 29 |
gh | 26 |
find | 20 |
psql | 17 |
ssh / scp / sleep | 5 / 5 / 5 |
tail+head+grep = 619 запусков: чаще всего пересрезка одних и тех же логов/выводов, что дополнительно наполняло контекст.
Файлы и фрагменты, попавшие в контекст
read — ≈100 целей из ~90 путей. Пути приведены относительно корня проекта; абсолютный cwd намеренно убран.| Файл (относит.) | Наблюдений | Символов |
|---|---|---|
src/Module/Chat/Application/UseCase/Command/ChatMessage/Send/SendCommandHandler.php | 15 | 52 475 |
reports/chat-quality-evidence/eval-2026-08-12-prompt-baseline-vs-candidate.md | ~18 | 91 319 |
todo/EPIC-chat-quality-reliability.todo.md | 6 | 38 870 |
tests/Unit/Module/Chat/Application/UseCase/Command/ChatMessage/Send/SendCommandHandlerTest.php | 6 | 26 587 |
reports/chat-quality-evidence/{README,U1-C1,U2-C1,U3-C2,U4-C1}.md (бандл) | 1 (×5) | 40 064 |
todo/TASK-chat-response-prompt-contract.todo.md | 4 | 24 912 |
docs/architecture/infrastructure-containers.md | 1 | 16 784 |
todo/AGENTS.md | 1 | 15 027 |
apps/web/assets/controllers/chat/_messageSender.js | 3 | 12 209 |
todo/TASK-chat-message-processing-error-state.todo.md | 1 | 12 052 |
src/Module/Chat/Integration/Service/LlmManager/LlmManagerService.php | 6 | 11 871 |
apps/web/.../templates/chat-message/_message_assistant.html.twig | 1 | 9 560 |
SendCommandHandler.php читали 15 раз, eval-report — ~18 раз, EPIC-todo — 6 раз. Каждый рерид не просто добавлял символы — он оплачивался полным контекстом того вызова, то есть стоил тем дороже, чем дальше по сессии.Ошибки, тупики и лишний контекст
isError=true (edit×5, ask×2, bash×2, по 1 у read/grep/todo/write), а также 3 ответа со stopReason=error и 4 aborted. Кроме явных ошибок часть сбоев команд маскировалась shell-пайплайнами.| Эпизод | Наблюдение | Влияние на расход |
|---|---|---|
| «Буря» шлифовки промпта (M3) | 175 ответов, 38 edit, 23.1M raw — на правку ~6 правил системного промпта. В net-дифф это несколько строк в .env и eval-report. | ~608K токенов на один edit в среднем; задача про «одно слово в правиле» решалась в возобновляемом контексте, а не пакетным eval-циклом. |
| Churn в ветке | .env и eval-report правились по 10–15 раз (коммиты «refactor(chat): правило N»); net-вклад минимален. | Каждая итерация = новое возобновление при большом контексте + git/read/edit на подтверждение. |
| Рериды одних файлов | SendCommandHandler×15, eval-report×~18, EPIC-todo×6, LlmManagerService×6. | Полные рериды на поздних вызовах оплачивались контекстом ~200–400K. |
| Infra-разведка в главной нити | Чтение infrastructure-containers.md (17K), deploy-production.md (10K); ~56 infra-вызовов (ssh/docker/psql/scp). | Эти данные таскались через весь EPIC и раздували каждый последующий ответ. |
| Поздние компактизации | Только 2 авто-компактизации — на 438K и 323K, уже на излёте фаз. | До сброса p90 успел дойти до ~391K; явный рестарт сессии помог бы раньше. |
Как сделать ту же работу дешевле — с оценками
Базовый принцип: рычаг — контекст, а не объём ответа. Fresh input + output вместе — 1.8% расхода. Значит «писать меньше» почти ничего не даёт; экономит контроль за тем, что модель каждый раз пере-читает.
1. Разнести 4 фазы по 4 сессиям ≈ 3–5×
M1–M4 — логически независимы (реализация error-state, prompt-контракт, шлифовка промпта, финальный merge). Треугольная модель накопления кэша даёт: при старте каждой фазы с пустым контекстом raw-расход оценивается в ~40–53M вместо 160M. Перед стартом — короткий handoff (изменённые файлы + команды проверки).
2. Рестарт при p90 > 250K
Здесь p90 = 391K, и компактизации сработали поздно. Триггер «обычный вызов стабильно > 200–250K raw» → закрывать сессию и открывать новую с резюме. Это режет «хвост» из M3/M4, где ответ стоил ~200–300K ради правки пары строк.
3. Промпт-работу — в оффлайн-eval, не в чат
M3 потратил 23.1M на правку правил. Eval-report уже был; нужно было готовить candidate офлайн, прогонять eval, применять одну правку. Это убирает 19 возобновлений и ~десяток «refactor(chat)» коммитов.
4. Перестать перечитывать файлы целиком
Для read — узкий путь и диапазон строк один раз; для больших файлов держать компактное саммари, а не тянуть их повторно. SendCommandHandler×15 и eval-report×~18 — первые кандидаты.
5. Infra-разведку — в отдельной одноразовой сессии
Сходить за production-фактами (ssh/docker/psql), вернуть компактную таблицу с периодом и запросом, продолжить в свежем контексте. Не таскать 17–40K-бандлы containers/deploy-документов через весь EPIC.
6. Меньше пересрезки вывода
tail+head+grep = 619 запусков, часто по одним и тем же логам. Агрегировать на стороне shell/SQLite и возвращать короткий результат.
Сабагенты и skills
В tool payload не найдено вызовов
subagent, spawn или delegate. Количество: 0.Загрузок
SKILL.md или tool calls навыков не найдено. Упоминания слова «skill» внутри прочитанных файлов не считаются вызовом.Технические сигналы
- Модель/provider: glm-5.2 / zai во всех 723 ответах.
- Распределение остановок: toolUse 679stop 37aborted 4error 3.
- 42 пользовательских продолжения — в среднем 17.2 ответа модели на одно.
- 2 авто-компактизации (на 438.5K и 323.0K tokensBefore).
- Тело tool results: 1.21M символов; read+grep+bash — ~85%.
Что не следует заключать
- 98.2% cacheRead ≠ 98.2% бесполезного расхода: кэш дешевле свежего input, это механизм переиспользования.
- Raw total ≠ сумма по подписке; $46.34 — значение
usage.cost.totalиз журнала OMP. - «266K токенов на строку» — не клеймёт результат: PR включал миграцию, тесты, eval-доказательство и отменённую задачу. Это индикатор цены контекста, а не ценности кода.
- Число tool calls само по себе не измеряет результативность; для эффективности нужен принятый результат при меньшем raw-расходе.
- Причину конкретного возобновления нельзя доказать агрегированными счётчиками — выводы выше это наблюдения и операционные гипотезы.
Методика и воспроизводимость
- Источник: один OMP JSONL session snapshot, сессия
019ff60d…; абсолютный путь источника намеренно не указан. - Для каждого assistant-сообщения суммированы
usage.input,usage.cacheRead,usage.output,usage.totalTokens,usage.cost.total. В этой схемеcacheWrite = 0, поэтомуtotalTokens = input + cacheRead + output. - Состав raw: cacheRead 157.23M · fresh input 2.50M · output 0.44M = 160.18M.
- Макро-фазы (M1–M4) размечены по границам пользовательских сообщений и таймлайну PR (#2829: создан 2026-08-13 09:36 UTC, смержен 2026-08-14 02:53 UTC), с привязкой к двум компактизациям.
- Строки кода: net-дифф merge PR #2829 (
+602/−43, 25 файлов) — изgh/git; кумулятивный churn по коммитам ветки (~878 ins / 319 del) — изgit logпо всем non-merge-коммитам ветки. - CLI-счётчики консервативны: учитываются утилиты верхнего уровня по whitelist после дедупа обвязки; вложенные/удалённые команды и текст grep не считаются.
- Явные ошибки — по
toolResult.isErrorиstopReason. Сбои, видимые только в stdout/stderr, приведены консервативно: shell-пайплайны могли скрывать exit code. - В отчёт не копируются промпты, исходный код, значения
.env, production-данные и абсолютные приватные пути; пути приведены относительно корня проекта. - Числа относятся к моменту окончания снимка: 2026-08-14 10:19 (Asia/Novosibirsk). Если сессия продолжалась позже, отчёт не обновляется автоматически.