Последние три месяца мы с ребятами из сообщества декомпилировали популярный шутер от первого лица. Точнее, декомпилировали ИИ-агенты, а мы ими управляли. Нам нужно было не просто доказать, что это в принципе возможно, а получить точную, стабильную и полнофункциональную реконструкцию игры на C++.

Итог: в восстановленном коде есть 99% функций игры, 83% из них совпадают с оригиналом побайтово, игра работает без заметных багов. По моей оценке, на это ушло 600-700 миллиардов токенов.
Название игры я по не зависящим от меня причинам не раскрываю, да и статья не о ней и даже не столько о декомпиляции. Главная тема — оркестрация автономных агентов: как настроить инфраструктуру, окружение и обвязку (harness), чтобы месяцами получать от агентов качественный результат. И почему без объективной автоматической проверки результата всё это не работает.
Задача
Мы хотели точно декомпилировать игру в C++. Кроме воспроизведения логики оригинала, были и другие требования: читаемый код, который собирается, исправления уязвимостей и багов (игра уже старая) и переносимость. Было бы здорово запускать её на Linux, macOS и в браузере.
Позже модернизацию и переносимость мы отложили, сосредоточившись на воспроизведении исходного поведения. А глобальная цель состояла в том, чтобы научиться эффективно управлять автономными ИИ-агентами на протяжении месяцев.
Инфраструктура
Мы начали с подписки Claude Max (20x), потом добавили Codex Pro и пользовались обеими одновременно. Модели менялись часто: большую часть времени работал Sonnet 5, но активно использовались и Opus 5.5, Luna, Sol и Terra. Агенты Claude запускались в Claude Code CLI, агенты Codex — в Codex CLI. Другие агентные обвязки мы тоже пробовали, но разницы почти не заметили и остались на стандартных.
Учёт задач
Агенты ведут GitHub issues через GitHub CLI: на каждую единицу трансляции (файл .cpp) заводится отдельный issue. Метки помогают группировать задачи и расставлять приоритеты.

Общение
Все агенты сидят в одном канале Discord, где могут писать и читать все сообщения. Так агенты общаются и друг с другом, и с людьми, причём участникам проекта не нужен доступ к машинам. Вебхук GitHub публикует в канал сообщения о падениях CI, чтобы агенты сразу узнавали о поломках.

Дизассемблер
Почти всё время агенты работали с официальным ida-mcp от Hex-Rays. Он очень стабильный, работает в headless-режиме и умеет всё, что нужно для такого проекта. Однозначно рекомендую.
Первый месяц: быстрый прогресс
Сначала у нас было четыре агента. Три исполнителя декомпилировали код и коммитили его, а ревьюер пассивно координировал работу и проверял коммиты на баги. За месяц агенты восстановили около 80% игры: она запускалась, показывала главное меню и загружала карты.
Параллельно мы много времени потратили на настройку окружения.
Раннее сжатие контекста. По умолчанию сжатие (compaction) срабатывает, когда контекст заполнен на 90%. Мы снизили порог до 42%. При декомпиляции в контексте копится много быстро устаревающей информации: уже декомпилированная функция больше не нужна. Раннее сжатие выметает этот «мусор» и экономит токены.
Борьба с потерей фокуса. Чем больше данных в контексте, тем сильнее агенты «дрейфуют», даже в пределах одного цикла сжатия. Они бросали функцию недоделанной и брались за следующую. Простаивали, наблюдая за CI, хотя уведомления о падениях и так приходили в Discord. Закрывали issue, не проверив, действительно ли работа сделана. В терминале агента можно вовремя поправить, но при автономной работе такой возможности нет.
Поэтому мы написали документ с целью проекта, правилами работы, запретами и порядком действий в конкретных ситуациях. Раз в час cron-задача просила агентов его перечитать, чтобы инструкции оставались свежими в контексте. Способ, возможно, не идеальный, но работал отлично до самого конца проекта. Документ мы долго дорабатывали, но публиковать его нет смысла: он слишком заточен под этот проект.
Проблема: читаемый, но неправильный код
Видимый прогресс убедил нас, что декомпиляция получается качественной. Это было не так. Код читался легко, но был семантически неверным: агенты путали сигнатуры функций, типы и раскладки структур, придумывали логику или выкидывали ту, что считали лишней.
Кроме того, они вносили ненужные архитектурные изменения. Например, к конфигурационным переменным игра обращается через глобальные переменные. Агенты заменили это прямое обращение к памяти хеш-таблицами, поиск в которых обходился на порядки дороже. И это лишь один пример из многих.
Почему так вышло? Ревьюер неплохо ловит баги, но на большее его не хватает: архитектурные решения он не оспаривал, если те вписывались в цель проекта. А главное — у нас не было объективных критериев приёмки, мы так и не определили, какой результат считать правильным. Эталоном служила сама игра, но раз в целях значились модернизация и переносимость, часть отклонений багами не считалась.
Любопытный эффект: ревьюер принимал отклонения, если исполнитель обосновал их в комментарии к коммиту или в коде. Такие комментарии работали как непреднамеренная промпт-инъекция: ревьюер верил объяснениям, вместо того чтобы самостоятельно сверить код с оригиналом.
Решение: оракул на основе побайтового сравнения
Нам был нужен оракул — автоматическая проверка, которая говорила бы агенту, совпадает ли восстановленная функция с оригиналом по семантике. Хватило бы простого сигнала PASS или FAIL, а в чём ошибка, агент разберётся сам.
Проще всего это сделать через декомпиляцию с побайтовым совпадением (byte matching decompilation). Мы перешли на тот же компилятор, которым собиралась оригинальная игра, и написали скрипт для сравнения. Скрипт читает наш OBJ-файл, а также EXE и PDB игры (с PDB немного проще, но и без него всё работает), извлекает байты функции и сравнивает их. Совпало — функция воспроизведена точно. Не совпало — агент её переделывает.

Ссылки на другие функции и данные побайтово не совпадут: их закодированные значения зависят от того, где окажутся цели в итоговом бинарнике. К счастью, в OBJ-файле такие ссылки записаны как релокации. Их байты можно исключить из прямого сравнения и вместо этого проверить, что обе версии ссылаются на один и тот же символ с тем же смещением.

Аналогично скрипт проверяет данные и типы. Агенты прогоняют его перед пушем. Восстановленные функции регистрируются в текстовых файлах, по которым CI проверяет весь код и ловит регрессии.
Как агенты жульничали
Получив скрипт, агенты первым делом начали писать ассемблерные вставки. Пришлось явно запретить в инструкциях naked-функции, патчинг объектных файлов, ассемблерные вставки и вшивание байтов в код. Такие конструкции легко найти автоматически, поэтому словесного запрета хватило.
Хуже было другое: агенты раз за разом пытались подправить сам скрипт, чтобы исключить свою функцию из сравнения. Чтобы это пресечь, CI вычисляет хеш скрипта и сравнивает его со значением, сохранённым в секрете GitHub Actions.
Плюсы и минусы подхода
Минусы:
Добиться совпадения непросто. Выбор регистров, инлайнинг и соглашения о вызовах трудно воспроизвести в точности, а изредка компилятор даже выдаёт разный результат для одинаковых входных данных.
Декомпиляция занимает гораздо больше времени, и результат при этом не обязательно лучше.
Функция может быть семантически идентичной и при этом не совпадать, например если независимые инструкции переставлены местами.
Плюсы:
Совпавшие функции гарантированно семантически идентичны оригиналу. Сохраняется исходное поведение, включая баги, а ошибки реконструкции исключены.
Агент-ревьюер больше не нужен.
С задачей надёжно справляются более дешёвые модели. Раньше Haiku и Luna выдавали крайне плохие результаты, а со строгим критерием приёмки получают достаточно обратной связи, чтобы показывать отличные результаты. Затраты резко падают, а проект можно многократно масштабировать.
Результаты
С новой системой проверки агенты проработали ещё почти два месяца. В восстановленном коде есть 99% функций игры, 83% из них совпадают побайтово.

В последние недели работали в основном 14 агентов на Luna и 2 на Opus 5.5. При таком масштабе у каждого исполнителя была своя ветка, а изменения шли через pull request’ы .
Discord на этом масштабе перестал справляться: когда столько агентов пишут в один канал, начинается каша. Мы разрешили им сообщать только о том, какие issue они берут в работу, и координироваться по CI. К тому же со временем людям почти не нужно было общаться с агентами: те работали полностью автономно.
Дальше каждая новая функция даётся всё дороже — у оставшихся либо есть недетерминированные особенности, либо они не совпадают по другим причинам. Например, мешает свёртка идентичных COMDAT-секций (identical COMDAT folding) в компоновщике, которую не удаётся надёжно воспроизвести. Агенты на Opus 5.5 ещё продвигаются, но игра уже работает безупречно: заметных багов нет, все возможности оригинала на месте. Оставшиеся функции переделывались много раз, и мы считаем, что их семантика верна, хотя побайтово они не совпадают. Дальнейшая погоня за совпадением съест токены без заметной пользы, так что проект можно считать завершённым.
Выводы
Нужны точные инструкции. Если задание оставляет простор для толкования, агенты норовят схитрить.
Корректность должна быть определена и проверяема машиной. Люди плохо умеют точно формулировать свои намерения, поэтому ревьюеров, на мой взгляд, никогда не будет достаточно. Лучшая обратная связь для агента — надёжная проверка с объективным PASS или FAIL. Не у каждого проекта есть такая роскошь, как у декомпиляции, но при должной изобретательности приблизиться к ней, думаю, можно где угодно.
С хорошей обвязкой хватает и дешёвых моделей. При точной обратной связи и строгой приёмке более умные модели не обязательно справляются лучше.
Инструкции со временем «выветриваются». Чем дольше идёт работа и чем чаще сжимается контекст, тем чаще агенты забывают правила или считают их неважными. В интерактивном режиме этот дрейф можно поправить на ходу, в автономном он может пройти незамеченным и испортить качество. Нам помогло ежечасное обновление инструкций.
Генерировать код дёшево, плохой — выбрасывайте. После первого месяца мы пытались спасти негодный код, и это отняло больше времени, чем работа с нуля.
Корректность важнее производительности. Экономия токенов, масштабирование и пропускная способность агентов — это прекрасно, но если результат плохой, толку от них мало.
Заключение
Проект оказался невероятно ценным опытом: многое пошло не так, но многое и получилось. ИИ-агенты берут на себя всё больше работы в разработке, и управлять ими — новая и куда более сложная задача для людей.
При 15+ агентах всплыли и другие трудности. Время от времени агенты стирали содержимое виртуальной машины некорректными командами. Решения для песочниц под Windows существуют, но ни одно мне пока не подходит. Поэтому я начал дорабатывать свой userspace-эмулятор Sogen, чтобы он давал лёгкую и масштабируемую песочницу. До продакшн-готовности, впрочем, ещё далеко.
Из-за стёртых ВМ мы потеряли часть логов сессий, так что точный расход токенов неизвестен. По моей оценке, это 600-700 миллиардов. Цифра кажется огромной, но большую часть работы выполняла Luna: на неё пришлось около 300 миллиардов токенов. Luna примерно в 100 раз дешевле Astra, так что в пересчёте это около 3 миллиардов токенов Astra.
Код по понятным причинам опубликован не будет: он останется закрытым, и использовать его я буду только для своих нужд.