Вопрос был «Ответь одним словом: работает». Двадцать девять символов. Claude Code отправил на сервер запрос на 305 042 байта, сервер посчитал его как 113 460 токенов и положил в кэш. Ответ пришёл из четырёх токенов. По списочному прайсу API такой «привет» стоит как чашка кофе; на подписке этого не видно, но контекстное окно он съедает точно так же.
Документацию про системный промпт и инструменты я читал. Хотелось увидеть само тело запроса: поставить между агентом и сервером прокси, сохранить всё, что уходит, и разобрать по частям. Сделал это для Claude Code и для Codex CLI. Дальше цифры, скрипт на сорок строк, чтобы повторить у себя, и список того, что после этого стоит отключить.
Как ловил
Прокси — сорок строк на Python: слушает локальный порт, принимает POST, сохраняет тело и заголовки в файл, пересылает дальше по HTTPS как есть и отдаёт ответ обратно. Оба агента умеют ходить на свой адрес:
Claude Code: переменная окружения
ANTHROPIC_BASE_URL=http://127.0.0.1:8766.Codex CLI: в
config.tomlу провайдераbase_url = "http://127.0.0.1:8765/v1".
Схема http и локальный адрес ни одного из них не смутили. Токен авторизации прокси в файл не пишет, в сохранённых заголовках он заменён на <redacted>; всё остальное лежит как есть.
#!/usr/bin/env python3 import http.server, http.client, json, os, sys, time, ssl UPSTREAM = os.environ.get("UPSTREAM", "api.example.com") LOGDIR = os.environ.get("LOGDIR", "captures") os.makedirs(LOGDIR, exist_ok=True) seq = [0] class H(http.server.BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" def log_message(self, *a): pass def _fwd(self): seq[0] += 1 n = seq[0] body = self.rfile.read(int(self.headers.get("Content-Length") or 0)) base = f"{LOGDIR}/{time.strftime('%H%M%S')}-{n:03d}" open(base + ".req.json", "wb").write(body) json.dump({k: ("<redacted>" if k.lower() == "authorization" else v) for k, v in self.headers.items()}, open(base + ".req.headers.json", "w"), indent=1) hdrs = {k: v for k, v in self.headers.items() if k.lower() not in ("host", "content-length", "transfer-encoding", "accept-encoding")} hdrs["Host"] = UPSTREAM hdrs["Content-Length"] = str(len(body)) t0 = time.time() conn = http.client.HTTPSConnection(UPSTREAM, timeout=600, context=ssl.create_default_context()) conn.request(self.command, self.path, body=body, headers=hdrs) resp = conn.getresponse() data = resp.read() open(base + ".resp.txt", "wb").write(data) self.send_response(resp.status) for k, v in resp.getheaders(): if k.lower() not in ("content-length", "transfer-encoding", "content-encoding", "connection"): self.send_header(k, v) self.send_header("Content-Length", str(len(data))) self.end_headers() self.wfile.write(data) print(f"[{n}] {self.command} {self.path} -> {resp.status} {len(body)}B req, {time.time()-t0:.1f}s", flush=True) do_POST = _fwd do_GET = _fwd http.server.ThreadingHTTPServer(("127.0.0.1", int(sys.argv[1])), H).serve_forever()
Запуск: UPSTREAM=api.example.com LOGDIR=captures python3 proxy.py 8766, где api.example.com — хост, на который агент ходил до этого. Стриминг прокси не поддерживает: он дожидается всего ответа и отдаёт его целиком. Обоим агентам этого хватило.
Дальше в пустой папке задавал каждому один и тот же вопрос — «Ответь одним словом: работает» — и смотрел, что легло в captures/.
Условия. Claude Code 2.1.274 на ноутбуке, запущен через -p из окружения десктоп-приложения, с моими обычными настройками: один MCP-сервер (Figma), один плагин с хуком на старт сессии, модель с окном в миллион токенов. Codex CLI 0.154.0 на сервере, в изолированном CODEX_HOME без плагинов, модель попроще, reasoning low. Пара не идеальная, и соревнования тут нет. Мне была интересна анатомия запроса: из чего он состоит и какие части можно выкинуть. «Голые» конфигурации я тоже сравнил, это ниже.
Claude Code: 305 килобайт
Тело первого запроса по частям. Символы посчитаны по JSON, доли — от размера тела, поэтому они приблизительные.
Часть | Символов | Доля |
|---|---|---|
| 259 423 | ~85% |
из них 107 от MCP-сервера Figma | 138 477 | ~45% |
из них 30 встроенных | 120 672 | ~40% |
| 28 691 | ~12% |
| 12 830 | ~4% |
| 1 132 | меньше 1% |
| 29 | 0,01% |
Три наблюдения.
Первое. Системный промпт — далеко не главный расход. Четыре блока: строка с версией клиента для биллинга (81 символ), «You are a Claude agent…» (62), правила про честный отчёт о результате (907) и основной промпт (11 780). Итого 12,8 тысячи символов, четыре процента тела.
Второе. Главный расход — описания инструментов, и большая их часть в этой сессии мне не нужна. 107 инструментов Figma на 138 тысяч символов уехали в запрос, где я прошу ответить одним словом. Самое длинное описание среди встроенных — у инструмента Artifact, 29 185 символов, больше всего системного промпта. Для сравнения: Bash — 2 666, Read — 1 629, Write — 663.
Третье. Плагины и хуки попадают в запрос целиком. Хук на старте сессии добавил 29 тысяч символов отдельным сообщением с ролью system. Одна и та же простыня в каждом запросе каждой сессии.
По мелочи, что ещё уходит: metadata.user_id с идентификаторами устройства, аккаунта и сессии; max_tokens: 64000; thinking: adaptive; output_config.effort: high; в заголовке anthropic-beta перечислены четырнадцать бета-флагов. Маркеры cache_control с TTL в час стоят в трёх местах: после короткого блока идентичности, после основного системного промпта и после служебного сообщения. Значит, в кэш ложится всё, включая инструменты; на втором ходу это читается оттуда.
Ответил агент, кстати, по-китайски: «工作». В настройках у меня стоит язык ответов, и одно слово он выбрал на нём. Настройки, как видите, тоже едут в запрос.
Второй запрос, о котором я не просил
В логе прокси на каждый ход два POST. Второй — 18,5 килобайта: без инструментов, max_tokens: 3072, системный промпт на 16,7 тысячи символов. Открыл. Это классификатор: ему дают хвост ответа агента и просят выбрать одно из четырёх состояний (done, working, blocked и ещё одно), чтобы решить, слать ли мне push-уведомление на телефон. У меня включены уведомления о завершении задачи, вот они так и работают. Стоит это 6 234 токена из кэша плюс 68 входных на каждый ход. Немного. Но я об этом не знал, и в логах usage самого агента этот запрос не виден.
Codex CLI: 29 килобайт
Тот же вопрос. Тело 29 750 байт, сервер посчитал 9 817 токенов.
Часть | Символов | Доля |
|---|---|---|
| 12 950 | ~44% |
| 9 277 | ~31% |
| 5 490 | ~18% |
| 443 | ~1,5% |
| 29 | 0,1% |
| ~1 000 | ~4% |
Забавная деталь: системные промпты двух агентов почти одной длины, 12 830 и 12 950 символов. Вся десятикратная разница в размере запроса — в инструментах и в том, что вокруг них.
Десять инструментов Codex: exec_command, write_stdin, apply_patch, request_user_input, view_image, три штуки про goals, tool_search и web_search. У него тоже есть отложенные инструменты: tool_search ищет по описаниям и подключает нужное по запросу, вместо того чтобы тащить всё сразу. У Claude Code такой механизм есть в интерактивном режиме (часть инструментов помечена как deferred), но в моём запуске через -p все 137 уехали целиком.
На ноутбуке с плагинами Codex в прошлой статье показал 14 809 токенов на тот же вопрос: там длиннее список скиллов. Порядок тот же.
Снимаю слои
Больше всего меня интересовало, сколько из 113 тысяч токенов убирается флагами, без правки конфигов.
Конфигурация | Байт | Токенов в кэш | Инструментов |
|---|---|---|---|
Claude Code как есть: MCP + плагин | 305 042 | 113 460 | 137 |
| 173 513 | 61 274 | 29 |
то же + | 138 879 | 48 967 | 28 |
Codex CLI без плагинов | 29 750 | 9 817 | 10 |
Один MCP-сервер стоил 52 тысячи токенов на запрос. Плагин с хуком — ещё около 12 тысяч (часть этой разницы — более короткий системный промпт: без настроек агент сменил модель на дефолтную, и промпт у неё 9 231 символ). «Голый» Claude Code — 49 тысяч токенов, «голый» Codex — 10 тысяч. Разница в пять раз, и почти вся она в 28 встроенных инструментах на 117 тысяч символов.
Вариант без настроек, понятно, не для жизни: отвалились мои модель и язык. Но как нижняя граница он полезен.
Что это значит для окна и кэша
Окно. 113 тысяч токенов на старте — это 11% от миллиона. Будь окно в 200 тысяч, инструменты и служебный текст заняли бы 57% ещё до первого вопроса. Когда агент «тупеет к концу сессии», полезно помнить, с какого места он её начал.
Кэш. В первом запросе всё ушло в cache_creation_input_tokens, чтения ноль. Через пять минут я запустил новую сессию с той же конфигурацией без MCP, но уже с вызовом инструмента. Первый запрос этой сессии: cache_read 61 292, cache_creation 255. Другая сессия, другой session_id, а префикс прочитан из кэша целиком: кэш здесь адресуется содержимым. Поэтому на API-тарифе весь этот балласт пишется один раз в час, дальше читается по дешёвой ставке. Ровно до тех пор, пока префикс совпадает байт в байт. Подключили новый MCP-сервер посреди дня, обновили плагин — префикс другой, сто тысяч токенов пишутся заново.
Скорость. Запрос на 305 килобайт по логу прокси шёл 3,3 секунды, без MCP — 3,2. На ощущения объём не влияет. Влияет на окно и на счёт.
Что с этим делать
MCP-серверы подключать в конфиге проекта, а не глобально. Figma мне нужна в двух папках; остальным она стоила бы 52 тысячи токенов на каждый запрос.
Проверить хуки и плагины. Если хук вываливает в сессию десятки тысяч символов, лучше оставить в нём короткий указатель на файл: агент прочитает его, когда понадобится.
Раз в месяц запускать прокси и смотреть первый запрос. Пятнадцать минут, и видно, что накопилось. Скрипт для подсчёта:
import json, sys b = json.load(open(sys.argv[1])) sz = lambda o: len(json.dumps(o, ensure_ascii=False)) for k in ("system", "instructions", "messages", "input", "tools"): if k in b: print(f"{k:13} {sz(b[k]):>8}") for t in sorted(b.get("tools", []), key=sz, reverse=True)[:10]: print(f" {t.get('name') or t.get('type'):40} {sz(t):>8}")
Если пользуетесь агентом через
-pили SDK, а не интерактивно, помнить, что отложенных инструментов там может не быть и в запрос уходит всё.Считать вместе со вторыми запросами. Классификатор для уведомлений в usage агента не попадает, зато попадает в счёт.
Чего я не проверял
Интерактивный режим Claude Code. Там инструменты могут уходить как deferred, и первый запрос, вероятно, меньше. Мерил только
-p.Codex с плагинами и MCP на ноутбуке через прокси. 14 809 из прошлой статьи получены без прокси, по usage.
Claude Code в чистом терминале без десктоп-окружения. Часть из 30 встроенных инструментов (Artifact, Workflow, DesignSync) явно от десктоп-приложения; в обычном терминале их должно быть меньше. Запуск с очищенным окружением у меня потребовал логина заново, и я не стал.
Другие версии обоих клиентов. Набор инструментов меняется от релиза к релизу, у вас числа будут свои.
Если поставите прокси и получите свои цифры — напишите в комментариях, сколько инструментов и сколько токенов уходит у вас в первом запросе. Особенно интересно, у кого больше 137.
Короткие заметки и замеры между статьями пишу в канале: https://t.me/agent_field_notes

