Час ожидания в Codex стоит до 188 миллионов токенов

Команда Relux в разборе от 11 сентября объяснила, почему недельный лимит подписки Pro за 200 долларов в Codex заканчивается за сутки. Goal-режим превращает ожидание дочерних агентов в непрерывный опрос, и каждая проверка заново перечитывает весь контекст. Авторы прошли по 3808 сессиям с двух машин за июль, август и сентябрь 2026 года, на версиях CLI от 0.144 до 0.154. Каждое утверждение о механике у них подкреплено ссылкой на исходники Codex на GitHub, сами логи сессий не опубликованы.
Ключевая сцена случилась в 23:50: автор спросил модель, зачем она так часто проверяет логи. Модель ответила, что «контракт цели требует, чтобы каждый ход либо продвигал работу, либо доказывал проверенное ожидание живым опросом запусков», и пообещала опрашивать реже. Через 0,3 секунды после этого ответа goal-режим запустил следующий ход. Модель не решает, когда её вызовут снова, поэтому сдержать обещание она не могла.
Сколько стоит час ожидания
Модель не помнит разговор: на каждом шаге ей заново отправляют всю переписку, и она отвечает одним действием. Если переписка занимает 240 тысяч токенов, шаг стоит 240 тысяч входных токенов, даже когда весь шаг сводится к tail -n 18. Весь разбор строится на одной формуле: цена ожидания равна числу проверок, умноженному на размер контекста. Если агент может спать между проверками и не тратить на это шаг, опрос обходится не дороже уведомления. Беда начинается, когда спать ему нечем.
В исходной сессии оркестратор на muse-spark 1.3 через провайдера opencode ждал своих дочерних агентов. За 48 минут goal запустил 173 продолжения и сделал 466 вызовов модели, каждый из которых перечитывал от 120 до 470 тысяч токенов. Всего ушло 149 миллионов входных токенов, или 188 миллионов в час. Остановил это только ответ провайдера с кодом 429.
На официальной gpt-5.6-sol тот же цикл обходился дешевле, но порядок цифр не менялся. В сессии A модель ждала по 30 секунд на вызов, это потолок для запущенной команды. За 7,8 часа ожидания она делала 271 шаг в час и потратила 650 миллионов токенов, 83 миллиона в час. В сессии B модель дочитывала вывод пустым write_stdin и получала до пяти минут паузы на вызов. Там 92 часа ожидания, 43 шага в час и 1,85 миллиарда токенов, около 20 миллионов в час.
Дешевле всех вышла gpt-6-astra, единственная модель, которой выдан инструмент паузы clock.sleep. В двух goal-сессиях она вызвала его 60 и 129 раз и спала по 45 секунд. Час ожидания стоил ей 10-15 миллионов токенов при 55-100 вызовах. По подсчёту авторов, это в 6-18 раз дешевле спина (так в разборе называют опрос без пауз) и всё равно в 20-30 раз дороже уведомления, потому что goal перезапускал модель 600 и 443 раза за сессию.
Иногда модель заканчивает ход, ничего не сделав, например пишет «жду», и goal тут же запускает её снова. В логах есть цепочки из четырёх таких ходов за 20 секунд. В одной сессии 14 пустых ходов стоили 77 миллионов входных токенов.
Все цифры в разборе приведены в полных входных токенах, как их считает клиент. В архиве 97,8% входа пришло из KV-кэша: в нём хранятся уже посчитанные значения для начала промпта, и повторяющийся префикс не пересчитывается. В API OpenAI кэшированный вход стоит 10% обычной цены. По какой формуле токены превращаются в проценты лимита подписки, не опубликовано: клиент получает от сервера только процент использования и время сброса. Авторы допускают, что скидка за кэш там действует, иначе тяжёлая сессия выбирала бы окно за минуты, но сотни миллионов лишних токенов съедают его и со скидкой. Довод о том, что в rate limits API кэшированные токены считаются как обычные, они после обсуждения на Reddit вычеркнули: это правило касается поминутных лимитов запросов, а не тарифа.
Что показал архив
Goal был включён в 52 сессиях из 3808, в полутора процентах. На них пришлось 29,2 из 59,2 миллиарда входных токенов. Все семь самых дорогих сессий архива, от 1,8 до 5,7 миллиарда токенов каждая, шли с goal, а самая тяжёлая сессия без него весит 1,2 миллиарда. Без принудительного продолжения модель заканчивала ход и ждала человека, и это ожидание ничего не стоило.
В каждом событии token_count в логах лежит снимок лимитов аккаунта. За десять недель с июля по сентябрь недельное окно тринадцать раз доходило до 99% и одиннадцать раз упиралось в 100%. Ошибка «You've hit your usage limit» встречается в 28 календарных днях. От начала окна до 99% в медиане проходило 25 часов, в самом быстром случае 7, в самом медленном 93. Пятичасовое окно за тот же период кончалось всего дважды. При 83-188 миллионах токенов за час ожидания в сутки набегает два-четыре миллиарда, отсюда и «за день» в заголовке разбора.
Почему модель не может просто подождать
Как только поток освобождается, обработчик on_thread_idle в расширении goal вызывает continue_if_idle и сразу начинает новый ход. Паузы перед перезапуском нет, счётчика пустых продолжений тоже, а бюджет токенов на цель по умолчанию не задан. В исходной сессии между концом одного хода и началом следующего проходило 0,02-0,05 секунды, в медиане 0,03. Авторы называют любой goal-режим встроенным в продукт циклом while true; do codex "continue"; done.
Капкан достраивает текст контракта в continuation.md. Ожидание засчитывается, только если модель в этом ходу опросила живой процесс, а «жду» без опроса считается отсутствием прогресса. Сама модель может перевести цель только в complete или blocked. Blocked разрешён после трёх одинаковых блокеров подряд, и ожидание дочерних процессов блокером не считается. Поставить цель на паузу может только человек.
Нужный инструмент в Codex при этом есть. clock.sleep появился в версии 0.141.0 18 июня, своё имя получил в 0.143.0 8 июля, умеет спать до 12 часов и просыпается от нового ввода. Регистрируется он, только если запись модели в каталоге объявляет поддержку clock или инструмент принудительно включён в конфиге. Во встроенном каталоге clock есть только у gpt-6-astra, у gpt-5.6-sol, gpt-5.5, gpt-5.4 и у всех моделей на кастомных провайдерах список пуст. codex features list показывает sleep_tool stable true, но это флаг функции, и наличия инструмента в запросе он не доказывает. Из 420 вызовов sleep в архиве 418 сделала gpt-6-astra, на все остальные модели пришлось всего два. Авторы видят в этом поэтапную раскатку: модель, которую не учили выбирать длительность сна, может заснуть вместо работы или крутить sleep 1 в цикле. Goal при этом включён для всех моделей.
Без sleep остаётся shell. По умолчанию Codex возвращает управление модели через 10 секунд, по аргументу yield_time_ms можно попросить до 30, а пустой write_stdin ждёт до пяти минут. У muse-spark закрылся и этот путь: провайдер передавал числа как 60000.0 вместо 60000, и парсер Codex отклонял вызов с ошибкой вида invalid type: floating point 60000.0, expected u64. На этой модели упали 10 из 10 вызовов write_stdin и 23 из 25 вызовов с yield_time_ms. Принудительно включённый sleep её бы тоже не спас, потому что его duration_ms тоже целое число. Медианный ход длился 10 секунд, средний 17: модель читала контекст, делала один опрос и писала «изменений нет». Из 181 хода сессии 135 состояли ровно из одной такой команды.
Сильнее всего это бьёт по оркестраторам. С одним дочерним процессом можно заблокироваться до его завершения, и трёхчасовой процесс обойдётся примерно в 36 вызовов, по 12 шагов в час. Когда процессов несколько, оркестратору нужно реагировать по ходу: принять результат, перезапустить упавший. Долго блокироваться он не рискует и переходит на короткий цикл опроса. В Codex есть барьер wait_agent, который ждёт первого завершившегося из нескольких агентов, но он видит только агентов, запущенных через spawn_agent. Qwen 3.6 через локальный llama.cpp вела себя так же, 21 из 22 ходов её goal-сессии были чистым опросом: доступ к инструменту паузы решает каталог моделей, провайдер тут ни при чём.
В исходной сессии получился и естественный эксперимент. В 00:03 провайдер ответил 429, и цикл замер на час. В 01:34 автор возобновил цель, и опрос пошёл снова, раз в 12 секунд. В 01:35 автор нажал Ctrl-C, а через восемь минут попросил продолжить без goal. Хватило пяти вызовов, работа была закончена. Контекст, модель и задача остались те же, поменялся только диспетчер.
Как ждёт Claude Code
В Claude Code есть такой же режим /goal, но диспетчер у него другой. По документации, после каждого хода условие проверяет отдельная небольшая модель. Если ещё работает фоновая команда или субагент, проверка откладывается, а их результат приходит новым ходом. Когда фоновая работа затягивается, первая проверка случается через 30 минут, дальше интервал удваивается, и без человека таких проверок не больше трёх. Несколько ходов подряд без вызова инструментов останавливают цикл с предупреждением. Механику уведомлений авторы смотрели по ClawCodex, стороннему порту на Python: background.py ждёт процесс и кладёт в очередь конверт <task-notification>, и пока процесс работает, модель не вызывают. По словам авторов, обучение модели здесь роли не играет. В одном продукте ждёт обёртка и будит модель, в другом модель вынуждена спрашивать сама.
Что с этим делать
Самый простой вариант, по мнению авторов, - брать для goal-задач gpt-6-astra на минимальном effort, только у неё sleep есть из коробки. Для остальных моделей sleep включается в ~/.codex/config.toml:
[features]<br>
sleep_tool = { mode = "always_on" }
После перезапуска Codex clock.sleep появляется в наборе у любой модели. Проверить просто: попросить модель поспать пять секунд через clock.sleep и посмотреть, появился ли вызов. Оговорок две. Модель не учили пользоваться инструментом, и она может продолжить опрашивать shell. А у моделей на кастомных провайдерах, которые пишут 60000.0, инструмент не заработает, пока не исправят сериализацию чисел. Сами авторы включили sleep через конфиг, учат оркестратор ждать все дочерние процессы одним вызовом и ставят бюджет на каждую цель, но признают, что в масштабе этот путь ещё не проверен.
От Codex они просят четыре небольших изменения: регистрировать clock.sleep при любой активной цели независимо от каталога, делать паузу между продолжениями после пустого или ждущего хода, принимать 60000.0 там, где ожидается 60000, и прописать в continuation.md sleep и длинный write_stdin как допустимые формы проверенного ожидания. Проверить свои сессии можно скриптом codex-rollout-audit. Это один файл на Python без зависимостей, он читает ~/.codex/sessions, никуда ничего не отправляет и выводит сводку по сессиям, эпизоды исчерпания лимита и число отклонённых дробных аргументов.
Свой разбор авторы закончили той же формулой: число проверок, умноженное на размер контекста. «Пока модели не дадут способ ждать, не тратя шаг, платит тот, кто ждёт».
Дальше
Claude Code проверяет, не зря ли вы написали плагин
Плагин может ничего не менять: команда claude plugin eval в Claude Code прогоняет каждый тест ещё и без плагина, а разница Δ показывает вклад. Одинаковый балл в обоих прогонах значит, что Claude справлялся и сам.
Читайте также
- Codex научился тратить сброс лимита по просьбе в чате
- OpenAI ускорила Codex до 8 раз, но только на Pro 500
- OpenAI restores 5-hour Codex and Work limits for ChatGPT Plus users
- Записи в кэш съели 85% счёта Codex на Bedrock
- ChatGPT и Codex с декабря тратят один общий лимит
- OpenAI удвоила лимиты Pro и отменила урезание плана за $200
Комментарии
Пока никто не написал. Будьте первым.
Присоединяйтесь к разговору
Войдите, чтобы оставить комментарий. Он появится после модерации.

