Мы много разрабатываем с помощью coding agents: веб‑приложения, MCP‑серверы, пайплайны обработки данных, системы из нескольких агентов. И чем лучше становилась инфраструктура вокруг агентов, тем больше в ней появлялось инструкций, проверок, субагентов и автоматики. По идее, разработка должна была становиться дешевле. А расход контекста рос.

Первое подозрение было очевидным: слишком раздутые промпты. Мы искали не там.

38 килобайт текста со смыслом «всё хорошо»

Агент закончил изменение и запустил тесты. 178 тестов, все зелёные. На stdout прилетело 38 365 байт TAP:

# Subtest:...

ok 1 ‑...

‑-

duration_ms:...

type: 'test'

..

# Subtest:...

ok 2 ‑...

Для человека итог помещается в одну строку: 178 tests, 178 passed, 0 failed. Но модель получает не смысл, а текст. И весь этот текст становится частью её следующего шага — и следующего, и следующего, пока задача не закончится.

Стали смотреть внимательнее, и тесты оказались не единственным местом. Агент может второй раз прочитать файл, который не менялся. Повторить команду и получить тот же результат. Несколько раз получить один и тот же опциональный контекст. Запустить субагента на исследование, а потом самостоятельно пройти почти тот же путь.

И вот это уже интереснее привычного разговора про длину промпта. Токены расходует не только то, что написал пользователь. Их расходует весь рабочий процесс агента.

Потом нашлось кое‑что похуже

В одном из проектов мы довольно тщательно описали правила работы агентов. Перед изменением изучить документацию. После изменения проверить результат. Для некоторых задач позвать дополнительного агента. Перед завершением провести верификацию.

Появилось это не на пустом месте: каждое правило когда‑то закрывало реальную ошибку. А вместе они создали новую.

Получилась корпоративная бюрократия, только автоматизированная. Агент делает работу. Проверяет её. Получает текст проверки обратно в контекст. Иногда запускает дополнительную проверку. Субагент исследует область, которую потом заново исследует основной агент. А в некоторых сценариях сама попытка закончить работу приводила к следующему кругу.

Самое обидное, что мы в это время оптимизировали модели и промпты. Хотя заметную часть расходов создавал наш собственный workflow.

Мы убрали принудительные продолжения, сделали успешные служебные проверки молчаливыми, ограничили субагентов и оставили полноценную верификацию там, где она действительно нужна. Но возник следующий вопрос: а можно ли находить такие вещи автоматически?

MCP как точка наблюдения

У нас уже был Local Commander — локальный MCP‑сервер, через который агент контролируемо работает с машиной: читает и ищет файлы, смотрит Git, запускает разрешённые команды и процессы, ходит на localhost, забирает состояние проекта.

И тут выяснилось, что MCP интересен не только как способ дать модели инструменты. Он стоит между агентом и значительной частью среды разработки. А на этой границе видно важное: что агент запросил и сколько информации вернулось обратно.

Так появился экспериментальный Optimize layer. Резать вывод мы сначала не стали вообще — он работает в shadow mode. Реальный ответ инструмента уходит агенту неизменным, а рядом система считает: размер результата, повторялся ли такой результат раньше, читался ли неизменившийся ресурс, существует ли для этого типа ответа безопасное компактное представление и сколько байт оно заняло бы.

Это принципиальный момент. Если сразу начать резать логи регулярками, очень легко «соптимизировать» ровно ту строку, которая объясняет настоящую ошибку.

Почему не LLM для сжатия логов

Очевидное решение выглядит так:

tool output → LLM summarizer → coding agent

Но тогда ради экономии токенов мы добавляем ещё один вызов модели. И кроме стоимости появляется проблема посерьёзнее: summarizer должен сам решить, какая информация существенна.

Для многих форматов это вообще не задача для нейросети. TAP, JSON, вывод Git, результаты тестовых раннеров — структурированные данные с известной грамматикой.

Поэтому первый compactor мы сделали детерминированным. Для Node TAP он разбирает конкретную грамматику успешного запуска: проверяет exit code, проверяет stderr, проверяет, что поток не обрезан, сверяет счётчики tests/pass/fail, сохраняет дополнительные notices. Компактный результат формируется, только если структура понятна целиком:

PASS node‑tap

tests=178 suites=...

pass=178 fail=0

duration_ms=...

Встретилось что‑то неизвестное — возвращается оригинал. Не «попробуем догадаться», а честный fail closed.

Первый результат

На реальном прогоне нашего репозитория:

178/178 tests passed

original stdout: 38 365 bytes

compact candidate: 1 036 bytes

reduction: 37 329 bytes (97,3%)

Плюс сохранились 14 дополнительных сообщений, которые парсер не счёл безопасным выбрасывать.

Цифра красивая, и вот здесь очень легко начать продавать её самому себе. Мы не доказали, что разработка подешевела на 97,3%. Мы доказали другое: в конкретном результате инструмента 97,3% передаваемого текста оказалось необязательным для представления успешного результата. Это разные утверждения.

Поэтому bytes, tokens и billing impact мы считаем отдельно. Если провайдер не сообщил фактический usage — стоимость остаётся unknown, а не нулём.

А что с ошибками

Тут нашёлся полезный контрпример. Прогнали mixed failure: исходный вывод 956 байт, после безопасного сокращения 908. Экономия почти никакая.

И это нормально. Stack trace, сообщение исключения и диагностический блок — как раз то, что модели нужно. Парсер удалил только распознанный успешный блок, а failure оставил практически целиком.

Если гнаться за «97% на каждом результате», такой оптимизатор очень быстро начнёт ухудшать разработку. Цель другая: удалять информацию только тогда, когда можно доказать, что удаляется представление, а не факт.

Следующий источник — повторное чтение

Логи оказались только первым случаем. Shadow profiler начал искать повторяющиеся результаты.

Скажем, агент прочитал config.ts. Через три tool calls запросил тот же файл снова. Между этими событиями файл не менялся — это видно по fingerprint.

Но даже здесь мы не говорим «второе чтение было лишним». Мы говорим: внутри одной задачи неизменившиеся данные были переданы модели повторно. Разница выглядит бюрократической, но она важна — возможно, агенту действительно понадобилось перечитать файл. Следующий шаг: понять, где можно безопасно подставить snapshot, ссылку на предыдущий результат или клиентское кеширование.

То же самое с одинаковыми результатами команд и повторяющимся опциональным контекстом.

Один и тот же принцип в других проектах

Когда мы посмотрели на остальные свои системы, обнаружился повторяющийся мотив.

В системе обработки данных мы стараемся не запускать повторную генерацию, если исходные данные и версия обработки не изменились. В агентной ферме стоимость ограничена не только выбором модели — там есть бюджеты на вызовы и отдельные запуски. В другом проекте агенты сохраняют checkpoint: что сделано, что проверено, где остановились, какой следующий шаг, — чтобы следующий агент не реконструировал историю задачи из сотни сообщений. В системе управления дизайном агенту передаётся ограниченный implementation package вместо предложения заново исследовать весь интерфейс.

Механизмы разные, идея одна: самый дешёвый токен — тот, который вообще не пришлось отправить модели.

Как попробовать это без MCP

Local Commander для эксперимента не нужен. Возьмите одну реальную задачу, которую агент выполняет 20–30 минут, сохраните tool trace и разберите его. Я бы начал с пяти вопросов.

1. Что агент прочитал повторно? Ищите одинаковые файлы, большие документы, результаты поиска. Ничего не удаляйте автоматически — сначала просто посчитайте объём.

2. Какие команды вернули много текста? Первые кандидаты: test, build, lint, git, пакетный менеджер, поиск. Посмотрите, сколько из этого вывода реально понадобилось для следующего решения.

3. Сколько текста означает «успех»? Типичная картина: 200 tests passed и десятки килобайт перечисления этих двухсот успехов. Для ошибок правила должны быть заметно консервативнее.

4. Что делают субагенты? Сравните их работу с последующими действиями основного агента. Если исследователь прочитал пять файлов и отчитался, а основной агент потом читает те же пять файлов — вы не распараллелили работу. Вы оплатили её дважды.

5. Какие инструкции запускают работу, а не ограничивают её? Для нас это оказалось самым неожиданным. Фраза «перед завершением обязательно…» выглядит безобидно. Но если она дёргает tools, verifier, reviewer или следующего агента — это уже не текст. Это программа, и у неё есть стоимость.

Простая метрика

С долларов я бы не начинал. Для первого аудита хватит четырёх чисел:

tool_result_bytes

repeated_result_bytes

repeated_unchanged_read_bytes

compact_candidate_bytes

potential_reduction = original_bytes — safe_compact_bytes

Реальный provider usage считается отдельно. И только после этого можно отвечать на вопрос, подешевела ли разработка. Потому что оптимизатор, сэкономивший 20 тысяч входных токенов и потративший 10 тысяч на модель, которая анализировала эту экономию, бывает очень успешен исключительно на собственном дашборде.

Чего мы пока не умеем

Ограничение существенное: MCP видит только ту часть работы, которая проходит через него. Он не видит полный system prompt клиента, внутреннюю память агента, provider caching и нативные инструменты самого coding agent. Так что универсальным счётчиком всех токенов Local Commander мы не называем.

Следующий этап — сравнивать целые задачи: обычный запуск против Optimize, на одном снимке задачи, с одинаковым acceptance test. И сравнивать не красивый размер лога, а total input tokens, cached input, output tokens, tool calls, model calls, wall time, cost и результат приёмки. Если качество то же, а стоимость ниже — тогда можно говорить об экономии.

А до тех пор 97,3% остаются ровно тем, чем являются: очень большим количеством текста, который однажды заставили прочитать довольно дорогую модель.

Пожалуй, это и есть главный вывод всего расследования. Разговор про оптимизацию LLM обычно быстро сворачивает к моделям, prompt engineering и цене миллиона токенов. Мы теперь начинаем со скучного вопроса: а зачем модель вообще должна была это читать?