# Сколько токенов съедает сама обвязка агента: Claude Code, Codex, Cline и OpenCode на одной задаче

Когда агент решает задачу, счёт растёт не только от вашего кода и вопросов. На каждом шаге в модель снова уходят системный промпт самого агента и описания всех его инструментов, и это часто весит больше, чем ваши файлы. Мы прогнали одну и ту же задачу на одном проекте и одной модели, `anthropic/claude-sonnet-5.5`, в четырёх агентах: Claude Code, Codex, Cline и OpenCode. Все запросы шли через RouterAI, поэтому токены и рубли считались по одному прайсу и по полю `usage` из ответов API.

Эта статья дополняет [сравнение моделей на одном агенте](/pages/stoimost-agentnoy-sessii-flash-vs-sonnet): там менялась модель, здесь модель одна, а меняется агент.

## Коротко {#korotko}

- Модель, проект и задача одинаковые, все проверки пройдены, а счёт отличался в 4,6 раза: от 2,49 ₽ (OpenCode) до 11,52 ₽ (Codex) в повторных прогонах.
- На обвязку (системный промпт и описания инструментов) приходится от 73% до 92% байтов запросов за сессию. Один «пустой» запрос стоит от 4,4 тыс. токенов у Cline до 18 тыс. у Claude Code.
- Кэш промпта влияет на счёт сильнее, чем размер обвязки: Claude Code с отключённым кэшем потратил 5,60 ₽ вместо 2,75 ₽.
- Три стандартных MCP-сервера добавили в OpenCode 6,9 тыс. токенов к каждому запросу. В Claude Code те же серверы добавили 0,4 тыс.: он держит схемы инструментов в стороне и подгружает по требованию.
- Через Responses API с моделью Claude мы увидели две странности: MCP-инструменты Codex не доходят до модели, а к каждому запросу добавляется 1,05 ₽. Подробности и оговорки в разделе про то, что мы не смогли измерить честно.

## Методика {#metodika}

**Дата замеров:** 31 октября 2026 года. **Окружение:** одноразовый контейнер `node:24-bookworm`, агенты ставились из npm, права и подтверждения отключены флагами, потому что контейнер изолирован.

| Агент | Версия | Как запускали |
| --- | --- | --- |
| Claude Code | 2.1.316 | `claude -p`, Anthropic Messages API |
| Codex | 0.166.0 | `codex exec`, Responses API |
| Cline | CLI 3.0.74 (пакет `cline`) | `cline -y`, OpenAI-совместимый провайдер |
| OpenCode | 1.19.5 | `opencode run`, OpenAI-совместимый провайдер |

Cline здесь консольный, а не расширение для VS Code, поэтому его системный промпт может отличаться от того, что вы видите в редакторе. Kilo Code и Hermes Agent в замеры не вошли.

**Модели.** Основная: `anthropic/claude-sonnet-5.5`. Для сравнения: `anthropic/claude-haiku-5.5`. Цены из каталога RouterAI на 31 октября 2026 года, ₽ за 1M токенов: Sonnet 5.5 — вход 209,45, выход 1047,24, чтение из кэша 10,47, запись в кэш 261,81; Haiku 5.5 — 10,47, 52,36, 1,05 и 13,09. Актуальные значения смотрите на [карточках моделей](https://routerai.ru/models).

**Проект и задача.** Демо-проект Invoices: три файла на Node.js и тест, 42 строки. Промпт одинаков для всех агентов:

> В этом Node.js-проекте добавь в invoices.js функцию overdueInvoices(), которая возвращает счета со статусом overdue, экспортируй её и добавь тест в invoices.test.js. Запусти node --test и убедись, что тесты проходят. В конце ответь одной фразой.

Результат считается верным, если `node --test` проходит и `overdueInvoices()` возвращает ровно один счёт, с id 3. Качество кода мы не оценивали, только эту проверку.

**Как считали.** Между агентом и RouterAI стоял прозрачный прокси. Он пересылал запросы без изменений и записывал для каждого из них размер тела, число инструментов, размер системного промпта и поле `usage` из ответа. Один запрос к модели считаем одним шагом. Цену задачи берём как разницу баланса до и после (`GET /api/v1/credits`); для Codex и Cline она совпала с суммой `usage.cost` из ответов. Sonnet прогонялся по три раза на агента, Haiku по два, везде берём медиану. Код прокси, скрипт прогона и сводки приведены [в конце статьи](#povtorit).

## Результаты {#rezultaty}

Задача на Sonnet 5.5, медиана трёх повторов; во всех 12 прогонах проверка прошла.

| Агент | Запросов | Вход за задачу, токенов | Выход, токенов | Цена в повторах, ₽ | Цена первого прогона, ₽ | Время, с | Обвязка в запросах |
| --- | ---: | --- | ---: | ---: | ---: | ---: | ---: |
| Claude Code | 6 | ≈114 тыс. (оценка) | 1 131 | 2,75 | 7,08 | 25,7 | 92% |
| Codex | 3 | 37 378 | 522 | 11,52 | 11,53 | 16,5 | 90% |
| Cline | 7 | ≈40 тыс. (оценка) | 1 291 | 4,24 | 8,60 | 30,7 | 73% |
| OpenCode | 7 | ≈70 тыс. (оценка) | 1 481 | 2,49 | 5,44 | 27,6 | 90% |

Как читать таблицу:

- **«Вход за задачу»** считается по размеру тела всех запросов, делённому на число байтов на токен. Этот коэффициент мы сняли с первого запроса каждого агента, где `usage` точен: от 2,55 до 3,36 байта на токен. Для Codex это значение прямо из `usage`. Почему не из `usage` у остальных, объясняем ниже.
- **«Обвязка в запросах»** — доля системного промпта и описаний инструментов в байтах тела запросов за всю сессию. Остальное — история диалога, файлы и результаты команд. У Cline доля ниже, потому что у него самая маленькая обвязка, а история за семь шагов выросла с 13,8 до 22,3 КБ.
- **Первый прогон и повторы.** Системный промпт и инструменты одинаковы от запуска к запуску, и провайдер кэширует эту часть. В первом, холодном прогоне те же задачи стоили 7,08 / 11,53 / 8,60 / 5,44 ₽, в повторах — 2,75 / 11,52 / 4,24 / 2,49 ₽. У Codex цена не менялась ни в одном прогоне: попаданий в кэш мы у него не увидели.
- OpenCode делает семь запросов, один из них служебный, без инструментов, на 0,8 тыс. токенов.

Больше всего вход у Claude Code, а в повторах он вдвое дешевле Cline, у которого вход втрое меньше. Больше всего платит Codex, хотя шагов у него меньше всех. Размер обвязки сам по себе счёт не объясняет, дальше разберём почему.

В других замерах порядок тот же. Systima в июле 2026 года на Claude Sonnet 4.5 [насчитала](https://systima.ai/blog/claude-code-vs-opencode-token-overhead) около 33 тыс. токенов в первом запросе Claude Code против около 7 тыс. у OpenCode. Agentic Control Plane в сентябре [сравнили](https://agenticcontrolplane.com/blog/what-coding-harnesses-send-before-your-prompt) первые запросы по байтам: 68 КБ у Claude Code, 37 КБ у Codex, 29 КБ у OpenCode. У нас те же агенты в более новых версиях: 46, 39 и 31 КБ, и 13,5 КБ у Cline.

## Сколько стоит «пустой» запрос {#pustoy-zapros}

Чтобы отделить обвязку от задачи, мы просили агента ответить одним словом: «готово». Это первый запрос с холодным кэшем, токены здесь точные.

| Агент | Токенов в запросе | Инструментов | Схемы инструментов | Системный промпт | Цена, ₽ |
| --- | ---: | ---: | ---: | ---: | ---: |
| Claude Code | 18 029 | 12 | 29,5 КБ | 15,0 КБ | 3,7 |
| Codex | 11 744 | 9 | 17,3 КБ | 19,8 КБ | 3,5 |
| OpenCode | 10 507 | 10 | 20,8 КБ | 9,5 КБ | 2,9 |
| Cline | 4 388 | 4 | 8,4 КБ | 4,6 КБ | 2,0 (два запроса) |

Токены Claude Code измерены с `DISABLE_PROMPT_CACHING=1`, иначе часть запроса уходит в запись кэша и считается отдельно. Для Sonnet 5.5 один такой запрос в 18 тыс. токенов по цене входа 209,45 ₽ за 1M даёт 18 029 × 209,45 / 1 000 000 = 3,78 ₽. Мы заплатили 3,78 ₽. Та же обвязка при чтении из кэша (10,47 ₽ за 1M) стоила бы около 0,19 ₽: поэтому кэш и важнее размера.

### Что убирается и что нет {#urezanie}

Те же замеры для Claude Code с разными флагами (первый запрос без кэша, токены из `usage`):

| Конфигурация | Токенов в запросе | Цена слова «готово», ₽ |
| --- | ---: | ---: |
| По умолчанию | 18 029 | 3,78 |
| + 3 MCP-сервера (`filesystem`, `memory`, `sequential-thinking`) | 18 434 | 3,87 |
| `--disable-slash-commands` | 21 696 | 4,55 |
| `--tools=Bash,Read,Edit` | 3 359 | 0,72 |
| `--bare` | 2 282 | 0,48 |

Три вывода из этой таблицы:

1. **Ограничение набора инструментов даёт минус 81% токенов** первого запроса (`--tools=Bash,Read,Edit`). Режим `--bare` по справке `claude --help` отключает хуки и другие необязательные части и урезает запрос до 2,3 тыс. токенов. Задача с `--bare` прошла проверку все три раза.
2. **`--disable-slash-commands` запрос не облегчил, а утяжелил**: схемы инструментов выросли с 29,5 до 44,7 КБ. Мы измерили это на 2.1.316 и не проверяли причину. Не выключайте скиллы «для экономии», не проверив на своём агенте.
3. **MCP в Claude Code почти ничего не добавил**: в теле запроса остались только имена 24 инструментов, а схемы подгружаются через `ToolSearch`. В OpenCode такие же три сервера добавили 27 инструментов и подняли первый запрос с 10 507 до 17 402 токенов, то есть на 66%. Для Cline мы MCP не измеряли.

В повторных прогонах основной задачи с включённым кэшем выигрыш скромнее: Claude Code с тремя инструментами — 2,51 ₽ вместо 2,75 ₽, в режиме `--bare` — 2,12 ₽ и 13,8 с вместо 25,7 с.

## Что не удалось измерить честно {#ogranicheniya}

**`usage` занижен при попадании в кэш.** На `POST /api/v1/messages` мы отправили три одинаковых запроса с `cache_control`. Первый вернул `cache_creation_input_tokens: 8494`, а второй и третий — `cache_read_input_tokens: 37`, хотя читали из кэша те же 8,5 тыс. токенов; списали при этом 2,23 ₽, 0,014 ₽ и 0,006 ₽. В агентных прогонах Claude Code на втором шаге `input_tokens` равен сотням при теле запроса в 46–52 КБ. Похожее мы увидели у OpenCode и Cline на тёплом кэше: в первом прогоне `usage` у OpenCode показывал 62 тыс. токенов, в повторе с тем же размером запросов — 16 тыс. Через Chat Completions и Responses на холодных запросах `usage` корректен. Поэтому токены за задачу мы оценили по размеру запросов, а цену взяли по балансу.

**Codex и MCP.** Codex при включённых MCP-серверах прислал 15 инструментов, четыре из них — группы (`namespace`): три сервера и `multi_agent_v1`. Мы спросили у самой модели, какие инструменты она видит. `anthropic/claude-sonnet-5.5` перечислила 11 — группы до неё не дошли. Тот же набор на `openai/gpt-5.4-mini` дошёл полностью, включая `mcp__filesystem.read_file`. Поэтому «+610 токенов от MCP в Codex» в наших замерах означает не дешёвые MCP, а то, что инструменты потерялись по дороге. Если вы запускаете Codex с моделью Claude, проверьте, видит ли она ваши MCP-инструменты.

**Доплата 1,05 ₽ за запрос в Codex.** Расхождение с ценой токенов у Codex постоянно: 1,0475 ₽ на каждый запрос, ровно столько стоит `web_search` в каталоге. Мы нашли условие: доплата появляется, когда в запросе одновременно есть `web_search` и любая группа инструментов; с функциями и `web_search` по отдельности её нет. За три шага задачи это 3,14 ₽ из 11,52 ₽, то есть 27%. Отключение `web_search` в Codex проблему не решило: агент перестал выполнять задачу (3 из 3 прогонов не прошли проверку). Мы фиксируем это как наблюдение за конкретной связкой Codex, Responses API и Claude, причину нужно уточнять у RouterAI.

**Малая выборка.** Три повтора на Sonnet и два на Haiku, одна задача, проект на 42 строки. На большой кодовой базе история диалога вырастет, и доля обвязки упадёт. Разброс в повторах заметный: у Claude Code от 2,62 до 4,19 ₽, у Cline от 3,54 до 5,16 ₽, у OpenCode от 2,41 до 2,55 ₽.

## Модель или обвязка {#model-ili-obvyazka}

Те же четыре агента на `anthropic/claude-haiku-5.5`, медиана двух прогонов, все 8 проверок прошли:

| Агент | Sonnet 5.5, ₽ | Haiku 5.5, ₽ | Запросов на Haiku | Время на Haiku, с |
| --- | ---: | ---: | ---: | ---: |
| Claude Code | 2,75 | 0,41 | 3 | 10,3 |
| Codex | 11,52 | 3,59 | 3 | 16,8 |
| Cline | 4,24 | 0,29 | 3,5 | 11,1 |
| OpenCode | 2,49 | 0,27 | 5 | 17,8 |

Разница между агентами на Haiku достигает 13 раз (0,27 против 3,59 ₽), на Sonnet — 4,6 раза. Из 3,59 ₽ у Codex 3,14 ₽ приходится на ту самую доплату за `web_search`, токены стоят около 0,45 ₽. Поэтому Sonnet в OpenCode (2,49 ₽) и в Claude Code (2,75 ₽) обошёлся дешевле, чем Haiku в Codex (3,59 ₽).

Это ровно то, что вы получаете, не глядя на агента. Но обобщать осторожно: дешёвая модель в нашей выборке выигрывает в каждом агенте, а «дорогая в бережливой обвязке дешевле дешёвой в жирной» верно только для пары с Codex, где виновата не размер контекста, а доплата.

## Практические выводы {#vyvody}

1. **Ограничьте инструменты.** В Claude Code это `--tools=Bash,Read,Edit` или `--bare`. Для задач без поиска по вебу, подагентов и планировщика остальные инструменты просто лежат в каждом запросе. Проверяйте результат по прокси: например, `--disable-slash-commands` у нас сделал хуже.
2. **Не подключайте MCP-серверы «на всякий случай».** В OpenCode три сервера добавили 27 инструментов и 6,9 тыс. токенов к каждому запросу. У каждого сервера в секции `mcp` конфига OpenCode есть поле `enabled`: выключайте то, чем не пользуетесь в этой сессии. Claude Code прячет схемы за `ToolSearch`, там цена ниже, но имена всё равно уходят в запрос.
3. **Следите за кэшем.** Смотрите в `usage` поле `cached_tokens` (или `cache_read_input_tokens`). Claude Code с кэшем стоил 2,75 ₽ против 5,60 ₽ без него, Sonnet 5.5 читает кэш за 10,47 ₽ вместо 209,45 ₽ за 1M. Если у агента `cached_tokens` всегда ноль, он, скорее всего, не размечает кэш. Но помните: на тёплом кэше `usage` занижает токены, цену проверяйте по балансу. Как устроено кэширование, описано [в глоссарии](/pages/prompt-caching).
4. **Одна задача — одна сессия.** История растёт с каждым шагом: от первого шага к последнему тело запроса выросло у Cline на 62% (7 шагов), у OpenCode на 22% (7 шагов), у Claude Code на 5% (3 шага), у Codex на 8% (3 шага). Число шагов в этих прогонах разное, поэтому цифры сравнивайте осторожно.
5. **Выбирайте агента и модель вместе.** Прогоните свою типичную задачу в двух-трёх агентах на одной модели, прежде чем сравнивать модели между собой.

### Как заранее оценить бюджет сессии {#byudzhet}

Для одного запроса:

```text
цена ≈ токены_из_кэша × цена_чтения + новые_токены × цена_входа + выход × цена_выхода
```

Один раз за сессию к этому добавляется запись префикса в кэш (у Sonnet 5.5 она дороже входа: 261,81 против 209,45 ₽ за 1M). Сессия — сумма по шагам. Практический порядок такой:

1. Запустите агента на пустой задаче («ответь одним словом») через прокси или посмотрите `usage` первого запроса: это ваш базовый размер обвязки `H`.
2. Прогоните одну типичную задачу и запишите число шагов `N` и размер запроса на последнем шаге.
3. Умножьте: без кэша счёт сессии порядка `N × (H + средняя история) × цена_входа`, с кэшем та же часть `H` дешевле в 10–20 раз.

После сессии разницу с планом покажет `GET https://routerai.ru/api/v1/credits`: этот способ описан в [статье про Claude Code](/pages/vibe-coding-claude-code) и используется в статус-баре RouterAI.

## Как повторить замеры {#povtorit}

Нужны Docker, Node.js 22+ и ключ из [личного кабинета](https://routerai.ru/docs/guides/overview/authentication). Агенты подключаются к RouterAI по инструкциям: [Claude Code](/pages/vibe-coding-claude-code), [Codex](/pages/vibe-coding-codex), [Cline](/pages/vibe-coding-vscode-cline), [OpenCode](/pages/vibe-coding-opencode). Отличается только базовый адрес: он указывает на прокси, а первый сегмент пути (`/claude`, `/codex`, `/cline`, `/opencode`) прокси отрезает и пересылает остальное на `routerai.ru`.

```bash
npm install -g @anthropic-ai/claude-code@2.1.316 @openai/codex@0.166.0 opencode-ai@1.19.5 cline@3.0.74
export ROUTERAI_API_KEY=sk-xxxxxxxxxxxxxxxx
export MODEL=anthropic/claude-sonnet-5.5

# Claude Code: переменные окружения (их выставляет bench.sh)
#   ANTHROPIC_BASE_URL=http://127.0.0.1:8787/claude/api
#   ANTHROPIC_AUTH_TOKEN=$ROUTERAI_API_KEY   ANTHROPIC_API_KEY=

# Cline: один раз настроить провайдера
cline auth openai -k "$ROUTERAI_API_KEY" -m "$MODEL" -b http://127.0.0.1:8787/cline/api/v1
```

`~/.codex/config.toml` для Codex:

```toml
model_provider = "routerai"
model = "anthropic/claude-sonnet-5.5"

[model_providers.routerai]
name = "RouterAI"
base_url = "http://127.0.0.1:8787/codex/api/v1"
wire_api = "responses"
supports_websockets = false

[model_providers.routerai.auth]
command = "sh"
args = ["-c", "echo $ROUTERAI_API_KEY"]
```

`~/.config/opencode/opencode.json` для OpenCode:

```json
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "routerai": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "RouterAI",
      "options": {
        "baseURL": "http://127.0.0.1:8787/opencode/api/v1",
        "apiKey": "{env:ROUTERAI_API_KEY}"
      },
      "models": { "anthropic/claude-sonnet-5.5": { "name": "Sonnet 5.5" } }
    }
  }
}
```

Прокси `proxy.mjs` (запуск: `LOG_DIR=/work/logs node proxy.mjs`; с `SAVE_BODY=1` сохраняет тела запросов):

```js
import http from 'node:http';
import https from 'node:https';
import fs from 'node:fs';
const LOG = process.env.LOG_DIR || '/work/logs';
const NL = String.fromCharCode(10);
fs.mkdirSync(LOG, { recursive: true });

function usageOf(text) {
  const u = {};
  const objs = [];
  if (text.trimStart().startsWith('{')) {
    try { objs.push(JSON.parse(text)); } catch {}
  } else {
    for (const l of text.split(NL)) {
      if (!l.startsWith('data:')) continue;
      const s = l.slice(5).trim();
      if (!s || s === '[DONE]') continue;
      try { objs.push(JSON.parse(s)); } catch {}
    }
  }
  u.raw = objs.map(o => o.usage || (o.message && o.message.usage) || (o.response && o.response.usage)).filter(Boolean).pop();
  return u;
}

let CUR = '';
http.createServer((req, res) => {
  if (req.url === '/health') { res.writeHead(200); res.end('ok'); return; }
  if (req.url.startsWith('/_label/')) { CUR = req.url.slice(8); res.writeHead(200); res.end(CUR); return; }
  const chunks = [];
  req.on('data', c => chunks.push(c));
  req.on('end', () => {
    const agent = req.url.split('/')[1];
    const path = req.url.slice(agent.length + 1);
    const label = CUR || agent;
    const body = Buffer.concat(chunks);
    let info = { bodyBytes: body.length };
    try {
      const j = JSON.parse(body.toString());
      const msgs = j.messages || j.input || [];
      const sysMsgs = Array.isArray(msgs) ? msgs.filter(m => m.role === 'system' || m.role === 'developer') : [];
      info = { bodyBytes: body.length, model: j.model, stream: !!j.stream, tools: (j.tools || []).length, toolsBytes: JSON.stringify(j.tools || []).length, sysBytes: JSON.stringify([j.system || '', j.instructions || '', sysMsgs]).length, msgs: Array.isArray(msgs) ? msgs.length : 1 };
    } catch {}
    if (process.env.SAVE_BODY) fs.writeFileSync(`${LOG}/${label}-req-${Date.now()}.json`, body);
    const headers = { ...req.headers, host: 'routerai.ru', 'accept-encoding': 'identity', 'content-length': body.length };
    delete headers.connection;
    const t0 = Date.now();
    const up = https.request({ host: 'routerai.ru', path, method: req.method, headers }, r => {
      res.writeHead(r.statusCode, r.headers);
      r.setEncoding('utf8');
      let text = '';
      r.on('data', c => { text += c; res.write(c); });
      r.on('end', () => {
        res.end();
        const rec = { agent, ts: new Date().toISOString(), path, status: r.statusCode, ms: Date.now() - t0, ...info, usage: usageOf(text) };
        if (r.statusCode !== 200) rec.err = text.slice(0, 300);
        fs.appendFileSync(`${LOG}/${label}.jsonl`, JSON.stringify(rec) + NL);
      });
    });
    up.on('error', e => { res.writeHead(502); res.end(String(e)); });
    up.end(body);
  });
}).listen(8787, '127.0.0.1');
```

Скрипт прогона `bench.sh <агент> <метка>`; эталонный проект должен лежать в `/work/repo-orig`:

```bash
#!/usr/bin/env bash
set -u
AGENT=$1; LABEL=$2; shift 2
M=${MODEL:?set MODEL}
PROXY=http://127.0.0.1:8787
PROMPT=${PROMPT:-'В этом Node.js-проекте добавь в invoices.js функцию overdueInvoices(), которая возвращает счета со статусом overdue, экспортируй её и добавь тест в invoices.test.js. Запусти node --test и убедись, что тесты проходят. В конце ответь одной фразой.'}

balance() {
  curl -s -m 10 -H "Authorization: Bearer $ROUTERAI_API_KEY" https://routerai.ru/api/v1/credits |
    python3 -c 'import json,sys; d=json.load(sys.stdin)["data"]; print(d.get("credits", d.get("total_credits", 0)))' 2>/dev/null
}

WORK=/work/runs/$LABEL
rm -rf "$WORK" && mkdir -p "$WORK" && cp -r /work/repo-orig "$WORK/repo" && cd "$WORK/repo" || exit 1
curl -s "$PROXY/_label/$LABEL" > /dev/null
B0=$(balance); B0=${B0:-0}
T0=$(date +%s.%N)
case $AGENT in
  claude)
    ANTHROPIC_BASE_URL=$PROXY/claude/api ANTHROPIC_AUTH_TOKEN=$ROUTERAI_API_KEY ANTHROPIC_API_KEY= \
    ANTHROPIC_DEFAULT_SONNET_MODEL=$M ANTHROPIC_DEFAULT_HAIKU_MODEL=$M ANTHROPIC_DEFAULT_OPUS_MODEL=$M \
    CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 IS_SANDBOX=1 \
    timeout 900 claude -p "$PROMPT" --model "$M" --dangerously-skip-permissions --no-session-persistence \
      --output-format json "$@" > "$WORK/out.txt" 2>&1 ;;
  codex)
    timeout 900 codex exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox -m "$M" "$@" "$PROMPT" > "$WORK/out.txt" 2>&1 ;;
  opencode)
    timeout 900 opencode run -m "routerai/$M" --dir "$WORK/repo" "$@" "$PROMPT" > "$WORK/out.txt" 2>&1 ;;
  cline)
    timeout 900 cline -P openai -m "$M" -y -c "$WORK/repo" "$@" "$PROMPT" > "$WORK/out.txt" 2>&1 ;;
esac
T1=$(date +%s.%N)
sleep 3
B1=$(balance); B1=${B1:-0}

PASS=n/a
if [ "${CHECK:-1}" = 1 ]; then PASS=no
if node --test > "$WORK/test.txt" 2>&1 &&
   node -e 'const r=require("./invoices").overdueInvoices(); process.exit(r.length===1&&r[0].id===3?0:1)' 2> /dev/null; then PASS=yes; fi; fi
python3 -c "import json,sys; print(json.dumps({'label':'$LABEL','agent':'$AGENT','seconds':round($T1-$T0,1),'balance_delta':round(float('$B0')-float('$B1'),2),'check':'$PASS'}))" >> /work/logs/_runs.jsonl
tail -n 1 /work/logs/_runs.jsonl
```

Дополнительные флаги агента передаются третьим аргументом и дальше, например `bench.sh claude trimmed --tools=Bash,Read,Edit`. Сводка по логам, `python3 summary.py <метка>`:

```python
import json, sys

for label in sys.argv[1:]:
    rows = [json.loads(l) for l in open(f"/work/logs/{label}.jsonl")]
    rows = [r for r in rows if r.get("status") == 200 and r.get("model")]
    body = sum(r["bodyBytes"] for r in rows)
    harness = sum(r["toolsBytes"] + r["sysBytes"] for r in rows)
    usage = [r["usage"].get("raw") or {} for r in rows]
    prompt = sum(u.get("prompt_tokens", u.get("input_tokens", 0)) for u in usage)
    out = sum(u.get("completion_tokens", u.get("output_tokens", 0)) for u in usage)
    cost = sum(u.get("cost") or 0 for u in usage)
    print(f"{label}: шагов {len(rows)}, тело запросов {body} Б, "
          f"обвязка {harness / body:.0%}, prompt по usage {prompt}, "
          f"выход {out}, cost по usage {cost:.2f} ₽")
```

Три предостережения при повторе. Во-первых, `prompt по usage` у агентов с тёплым кэшем занижен, как описано выше: сверяйте с изменением баланса. Во-вторых, замеры нужно делать по очереди, а не параллельно, иначе дельта баланса смешает разные прогоны. В-третьих, эндпоинт `/credits` изредка отвечает не JSON: в двух наших прогонах дельта вышла абсурдной (около 53 тыс. ₽), мы такие прогоны исключили.

## Итог {#itog}

На одной модели и одной задаче агент менял счёт в 4,6 раза, а на Haiku — в 13 раз. Но объяснение не сводится к «у кого обвязка толще»: у Claude Code она самая тяжёлая, а в повторах он вдвое дешевле Cline, потому что префикс читается из кэша. Практичнее всего оказались три шага: сократить набор инструментов, не держать лишние MCP-серверы и проверять в `usage`, что кэш действительно срабатывает. Цены, `usage` и баланс в RouterAI в рублях, работает из России без VPN, поэтому такие замеры можно повторить на своих задачах за вечер.

> Все модели из статьи доступны в [каталоге RouterAI](https://routerai.ru/models): оплата в рублях, без VPN. [Создайте аккаунт](https://routerai.ru/users/sign_up), прогоните скрипт на своей задаче и сравните агентов по реальному счёту.
