В какой-то момент мне стало интересно провести довольно простой эксперимент. Не сравнивать модели на абстрактных задачах. Не просить обе написать один и тот же код. А дать им роли, которые обычно существуют в нормальном процессе разработки.
Один пишет. Другой проверяет.
Получилось примерно так:
TASK
↓
Claude Code
↓
implementation
↓
Codex
↓
review
↓
Claude Code
↓
response to review
А потом я поменял их местами. Codex реализовал похожую задачу. Claude Code пошёл на review.
Меня интересовало не то, кто «победит». Гораздо важнее было посмотреть: что увидит независимый агент; будут ли замечания действительно полезными; начнёт ли автор соглашаться со всем подряд или, наоборот, защищать собственное решение; и вообще — имеет ли смысл AI code review, если сам код уже написал AI.
Оказалось, что имеет. Но совсем не так, как я сначала представлял. К самой идее разделения ролей я пришёл раньше — в заметке про нескольких AI-разработчиков.
Я специально выбрал обычную задачу
Мне не хотелось брать алгоритмическую головоломку или специально подготовленный benchmark. В реальной работе большинство задач выглядит гораздо скучнее.
Например:
Нужно добавить возможность вручную повторить обработку неуспешной операции.
Есть существующий процесс. Есть состояния. Есть несколько сервисов. Есть ограничения. Есть тесты. Нужно аккуратно встроить новое поведение в старую систему.
То есть обычная backend-задача. Такая, где легко написать технически нормальный код и при этом немного не попасть в архитектуру проекта. Именно такие задачи мне интереснее всего проверять с AI.
Первым писал Claude Code
Я дал ему задачу примерно в том формате, к которому уже привык:
Сначала исследуй текущую реализацию. Найди существующий flow. Опиши, что собираешься менять. После этого реализуй. Не делай unrelated refactoring. Запусти тесты и статический анализ.
Claude сначала прошёлся по проекту. Нашёл сервис обработки. Посмотрел состояния. Нашёл существующие тесты. Предложил добавить отдельную операцию повторного запуска через уже существующий application service. План выглядел нормально. Я разрешил реализацию.
Через некоторое время у меня был готовый diff. Новый action. Небольшое изменение сервиса. Проверка допустимого состояния. Тесты. Обновлённое отображение в интерфейсе. Все проверки прошли.
Claude посмотрел собственный diff и написал примерно ожидаемое: реализация соответствует существующей архитектуре, переиспользует текущий pipeline и покрыта тестами. Если бы я работал только с одним агентом, на этом задача вполне могла закончиться. Но дальше я отдал diff Codex.
Codex не получил историю обсуждения
Это было принципиально. Я не хотел писать: Claude исследовал проект, решил использовать этот сервис, потому что... Потому что тогда я заранее помещаю reviewer в ту же систему координат.
Вместо этого он получил исходную задачу, ограничения, доступ к проекту, готовый diff. И короткую инструкцию:
Проведи независимый code review. Не исходи из того, что выбранный подход правильный. Проверь не только bugs, но и само решение. Если есть более простой вариант — скажи.
Мне хотелось получить не проверку домашнего задания по ответам в конце учебника. А свежий взгляд.
Первое замечание было довольно скучным
Codex обнаружил, что один из новых тестов проверяет успешный сценарий, но не проверяет повторный вызов. То есть:
операция failed
↓
retry
↓
success
А это — нет:
операция failed
↓
retry
↓
retry ещё раз
Само по себе замечание нормальное. Но ничего особенно интересного. Такое вполне мог заметить и первый агент при self-review. Я уже начал думать, что эксперимент сведётся к дополнительному линтингу. Но следующее замечание было гораздо интереснее.
«Почему это действие находится здесь?»
Codex обратил внимание на место, в котором Claude добавил проверку допустимости retry. Технически она работала. Но reviewer написал примерно следующее:
Проверка находится слишком близко к HTTP-слою. При вызове операции из другого entry point ограничение можно будет обойти.
Вот это уже было интересно. Claude добавил проверку в action и частично в application service. Текущий пользовательский сценарий был защищён. Но само бизнес-правило «эту операцию можно повторять только в определённом состоянии» не было полностью закреплено на уровне, через который гарантированно проходят все вызовы.
Сегодня других entry points нет. Завтра может появиться CLI или message handler. Формально feature работает. Но invariant расположен не очень надёжно. Это уже не замечание уровня «тут можно переименовать переменную». Это вопрос к архитектуре решения.
Потом Codex нашёл дублирование
В новой реализации Claude добавил небольшой mapper для формирования данных повторной операции. Codex нашёл похожую логику в соседнем модуле. Не полную копию. Но достаточно близкую, чтобы задать вопрос: нельзя ли переиспользовать существующий механизм подготовки payload вместо создания второго варианта?
Я сам это место не вспомнил. Claude при первоначальном research тоже его не обнаружил. Оно находилось немного дальше от основной области задачи. И тут cross-review впервые дал мне реальную пользу. Не потому, что reviewer нашёл явную ошибку. Он расширил область исследования.
Но дальше началось самое интересное
Я вернул замечания Claude. Не в формате «исправь всё, что написал Codex». А:
Вот независимый review. Проанализируй каждое замечание. Скажи, с чем согласен, с чем нет и почему. Пока ничего не меняй.
Мне было очень интересно, как агент отнесётся к критике собственного решения. Будет автоматически соглашаться? Или начнёт его защищать? Ответ оказался гораздо менее бинарным.
С первым замечанием Claude согласился сразу
Недостающий тест действительно стоило добавить. Здесь никакой дискуссии. Примерно: да, повторный retry сейчас явно не проверяется. Это полезный тест, потому что он фиксирует ожидаемое поведение. Нормально.
Второе замечание он сначала оспорил
Про расположение бизнес-проверки Claude ответил примерно так: текущая реализация безопасна, потому что операция сейчас вызывается только через этот application service, а action не содержит всей проверки самостоятельно.
То есть он не сказал «Codex неправ». Но начал объяснять, почему существующий вариант допустим. Это было именно то, что мне хотелось увидеть. Не автоматическое «отличное замечание, сейчас исправлю». А попытку защитить решение.
Я попросил: проверь ещё раз все usages этого сервиса и возможные entry points. Не исходи из того, что текущая структура останется неизменной.
После дополнительного исследования Claude изменил позицию. Он нашёл ещё один внутренний вызов, который в будущем действительно мог попасть в тот же сценарий. И написал: с учётом этого замечание справедливое. Проверку лучше перенести глубже.
То есть получилось небольшое техническое обсуждение между двумя агентами. И второй оказался полезен не потому, что выдал финальную истину. А потому, что заставил первого перепроверить собственное предположение.
А третье замечание Claude отклонил
С дублированием история оказалась обратной. Claude посмотрел существующий mapper, на который указал Codex, и объяснил: они выглядят похоже, но обслуживают разные контракты. Объединение потребует добавить условное поведение и сильнее свяжет два независимых сценария.
После просмотра кода я был скорее согласен с Claude. Codex нашёл структурное сходство и предложил переиспользование. Но это было как раз то переиспользование, которое легко создаёт abstraction раньше времени.
Если бы я дал команду «исправь все замечания reviewer», код, возможно, стал бы хуже. Вот здесь у меня появился важный вывод.
AI-review не является списком обязательных исправлений
Это кажется очевидным, если речь идёт о человеческом code review. Reviewer может ошибаться. Может не знать контекст. Может предложить спорный refactoring. Может предпочитать другой стиль. Почему с AI должно быть иначе?
Но психологически замечания модели легко воспринимать как результаты статического анализатора: найдено 6 проблем. Хочется просто исправить все шесть.
На практике review от AI гораздо ближе к комментариям другого разработчика. Каждое замечание — это гипотеза. Её нужно проверить. Некоторые полезны. Некоторые нейтральны. Некоторые ухудшают решение.
Я начал классифицировать замечания
Не формально, но мысленно. После нескольких таких экспериментов комментарии довольно хорошо делились на категории.
Реальные дефекты
Например: здесь возможен повторный вызов без проверки состояния. Или: при ошибке transaction остаётся открытой. Это самые простые замечания. Есть конкретный сценарий. Его можно воспроизвести или проверить тестом.
Пропущенные сценарии
Например: тесты не покрывают повторный retry. Или: не проверяется отсутствие прав. Тоже полезно.
Архитектурные вопросы
Например: почему invariant находится в controller? Здесь уже нужен разговор.
Альтернативные решения
Например: вместо нового service можно расширить существующий pipeline. Это не проблема. Это вариант.
Предпочтения
Например: я бы вынес это в отдельный Value Object. Иногда полезно. Иногда это просто дело вкуса reviewer. И вот последняя категория оказалась довольно большой.
AI очень любит улучшать код на review
Это забавно. Просишь «найди проблемы» и через некоторое время получаешь: можно дополнительно повысить maintainability, выделив отдельную abstraction...
То есть reviewer быстро превращается в архитектора. Особенно если явных bugs немного. Тогда начинается охота за потенциальными улучшениями. Новый interface. Отдельный factory. Более универсальный service. Дополнительная типизация. Красивое переиспользование.
Поэтому я довольно быстро стал добавлять в review важное ограничение:
Не предлагай refactoring только ради чистоты. Отмечай его отдельно от correctness issues.
Это сильно улучшило полезность результата.
Потом я поменял агентов местами
Следующий эксперимент был зеркальным.
TASK
↓
Codex
↓
implementation
↓
Claude Code
↓
review
↓
Codex
↓
response
Я специально не пытался подобрать задачу, на которой один из них должен был показать себя лучше. Хотел посмотреть именно на динамику. И она немного изменилась.
Claude больше внимания уделил намерению кода
В том конкретном эксперименте Codex сделал довольно компактную реализацию. Минимальный diff. Хорошие тесты. Без лишних abstractions. Claude на review практически не спорил с техническим качеством. Но задал несколько вопросов уровня: соответствует ли это поведение исходному пользовательскому сценарию?
Например, Codex разрешил действие сразу после определённого перехода состояния. Технически это было допустимо. Но Claude заметил, что в соседнем тесте похожее действие становилось доступным только после завершения фонового процесса.
Это не было прямым доказательством ошибки. Но хороший сигнал: проверьте бизнес-смысл этого момента. И действительно, после дополнительной проверки оказалось, что действие было доступно слишком рано.
Codex в ответ сначала тоже защищал реализацию
И это повторилось. Он объяснил: текущее условие соответствует прямому требованию задачи. Формально да. В тексте задачи ограничение не было написано.
Но после просмотра соседнего процесса он признал: есть неявная зависимость от завершения фоновой обработки, которую исходная постановка не отражала.
Опять та же история. Reviewer не обязательно нашёл bug напрямую. Он обнаружил место, где автор слишком узко понял задачу.
Но Claude тоже выдал несколько бесполезных замечаний
Например, предложил дополнительно вынести часть логики в отдельный класс. Я спросил: какую конкретную проблему это решает в рамках текущей задачи? После уточнения оказалось, что серьёзной проблемы нет. Основной аргумент был: это может упростить будущие расширения.
Классическая история. Будущих расширений пока нет. Текущая реализация простая. Отдельный abstraction только увеличил бы количество кода. Замечание ушло. И это тоже важный результат эксперимента. Второй агент — не oracle.
Иногда агенты начинают разговаривать о гипотетическом будущем
Это вообще одна из самых частых проблем AI-review. Если в будущем появится второй provider... Если количество реализаций увеличится... Если потребуется расширить...
Конечно. В будущем может произойти всё что угодно. Но хороший review должен в первую очередь смотреть на текущую задачу. Поэтому я всё чаще спрашиваю: есть ли проблема сейчас и есть ли в проекте доказательство, что это расширение действительно ожидается?
Если нет, замечание остаётся в категории «возможная идея», а не «нужно исправить».
Самым полезным оказался не review, а спор после него
Это было немного неожиданно. Я думал, что ценность схемы такая:
AI пишет
↓
другой AI находит ошибки
Но реальная ценность чаще появлялась здесь:
AI пишет
↓
другой AI задаёт вопрос
↓
первый AI вынужден заново обосновать решение
Потому что в этот момент первый агент перестаёт просто продолжать предыдущую работу. Ему приходится возвращаться к исходным предположениям. Почему выбран этот слой? Почему такой контракт? Почему не существующий механизм? Почему это состояние считается допустимым?
Иногда после такой проверки решение остаётся прежним. И это хорошо. Значит, у него появилось более сильное обоснование. Иногда меняется. Тоже хорошо.
Я специально перестал писать «исправь замечания»
Теперь стараюсь разделять две команды. Сначала:
Проанализируй review. По каждому пункту: согласен или нет; почему; какое подтверждение есть в коде или тестах; требуется ли изменение.
И только потом: исправь подтверждённые проблемы.
Это небольшая разница в формулировке. Но она сильно меняет результат. Иначе первый агент очень часто начинает послушно выполнять предложения второго. А тогда независимый review превращается просто во второго автора.
Ещё полезнее оказалось просить reviewer искать не только bugs
Теперь мой запрос к review чаще состоит из нескольких частей. Что-то вроде:
Проверь:
1. correctness
2. edge cases
3. соответствие исходной задаче
4. соответствие архитектуре проекта
5. существующие механизмы, которые можно было переиспользовать
6. ненужную сложность
7. тесты, которые подтверждают реализацию, но не требование
Последний пункт мне нравится особенно. Потому что AI действительно хорошо умеет написать тесты под собственный код. Проблема в том, что они могут идеально подтвердить неправильное решение.
Зелёный тест ещё не означает согласие двух агентов
Представим, что автор решил: retry разрешён для всех failed операций. И написал тест:
failed operation
↓
retry
↓
success
Тест зелёный. Автор доволен. Но независимый reviewer может спросить: а все ли failed операции вообще можно повторять?
Вот здесь review выходит за рамки проверки реализации. Он проверяет саму модель задачи. Именно ради этого мне несколько агентов интереснее всего.
Кто оказался лучше?
После нескольких таких экспериментов мне всё меньше хочется отвечать на этот вопрос. В одной задаче Claude находил более естественную архитектуру. В другой Codex делал более компактный diff. Где-то один лучше замечал соседние зависимости. Где-то другой задавал более неприятные вопросы к бизнес-логике.
Но важнее другое. Они ошибались по-разному. Вот это оказалось полезным. Если бы оба всегда приходили к одному и тому же решению одним и тем же путём, особого смысла в cross-review не было бы. Ценность появляется именно в различии.
Разные ошибки создают шанс поймать друг друга
Первый агент может слишком быстро выбрать abstraction. Второй — слишком агрессивно искать возможность её удалить. Первый может сфокусироваться на архитектуре. Второй — на минимальном diff. Первый хорошо знает историю решения, потому что сам её построил. Второй не знает её вообще и поэтому задаёт «глупые» вопросы.
Иногда именно такой вопрос оказывается самым полезным: а зачем это вообще нужно? Автор уже перестал его задавать двадцать минут назад.
Но два AI всё равно могут дружно ошибиться
Это тоже важно. Cross-review не создаёт математическую гарантию правильности. Два агента могут одинаково неверно понять требование, не найти один и тот же скрытый dependency, поверить одинаковому тесту, пропустить бизнес-ограничение, которого нет в проекте, согласиться с красивым, но неправильным архитектурным решением.
Можно добавить третьего агента. Потом четвёртого. Но в какой-то момент получится не безопасность, а AI-комитет. Поэтому я не воспринимаю cross-review как способ убрать человека из процесса. Для меня это ещё один слой проверки. Не последний.
Человек всё ещё обладает самым неудобным контекстом
Оба агента могут согласиться: реализация корректна. А я смотрю и говорю: нет. Клиент вообще не должен видеть это состояние.
Почему? Потому что знаю продукт. Знаю историю. Знаю договорённость. Знаю, что через месяц это API будет использовать другой сервис. Не всё из этого уже записано в repository.
Пока такой контекст существует, финальное решение всё равно остаётся моей задачей. Но теперь к этому решению я прихожу не один. У меня есть два довольно сильных оппонента, которые могут независимо посмотреть на код. Это уже много.
В итоге мой процесс стал выглядеть примерно так
Для достаточно заметной задачи:
TASK
↓
контекст и ограничения
↓
Agent A: research
↓
Agent A: plan
↓
Agent A: implementation
↓
Agent A: self-check
↓
Agent B: independent review
↓
Agent A: response to review
↓
подтверждённые изменения
↓
tests / verification
↓
мой финальный review
Иногда я добавляю ещё один шаг: Agent B → alternative implementation, если решение действительно спорное. Но далеко не для каждой задачи. Иначе процесс быстро станет тяжелее самой разработки.
Мне понравилось, что первый агент не всегда соглашался
До эксперимента я немного боялся обратного. Что AI-review превратится в такую цепочку: reviewer говорит «здесь проблема», author отвечает «согласен», reviewer — «перепиши всё», author — «отличная идея». Тогда никакой инженерной дискуссии не возникает. Просто две модели последовательно меняют код.
На практике при правильной формулировке автор вполне способен аргументированно не соглашаться. И это, пожалуй, один из самых полезных результатов. Мне не нужны два агента, которые всегда подтверждают друг друга. Мне нужны два агента, которые способны создать конфликт предположений. Потому что именно там чаще всего и находится что-то интересное.
Я больше не хочу, чтобы AI говорил мне только «всё хорошо»
Когда один агент пишет: все тесты прошли, реализация готова — это приятно. Когда второй отвечает: я нашёл три потенциальные проблемы — уже не так приятно. Особенно если задача казалась законченной.
Но постепенно я понял, что именно второй ответ мне полезнее. Не потому, что обязательно есть три проблемы. Может быть, две окажутся ерундой. Но появляется причина ещё раз посмотреть на решение. Проверить assumption. Найти тест. Попросить автора объяснить выбор. И иногда обнаружить то, что иначе спокойно ушло бы в production.
Наверное, именно так у меня и появилась AI-команда
Не в красивом смысле «вот три виртуальных разработчика, которые теперь всё делают за меня». А гораздо прозаичнее.
Один написал код. Второй пришёл и сказал: а я бы здесь не был так уверен. Первый ответил: вот почему я сделал именно так. Второй возразил. Я посмотрел на аргументы. Что-то приняли. Что-то выбросили. Что-то переписали.
Очень знакомый процесс. Почти обычный code review. Только оба участника могут прочитать половину repository за несколько минут. И оба могут очень убедительно ошибаться.
Поэтому сейчас мне интереснее не «кто лучше пишет код»
Claude Code или Codex. Сегодня один. Завтра другой. Модели изменятся. Инструменты тоже.
Мне гораздо интереснее другой вопрос: что происходит, когда один достаточно сильный AI-разработчик вынужден объяснять свою работу другому достаточно сильному AI-разработчику?
Пока мой ответ такой: иногда они находят реальные ошибки; иногда спорят об архитектуре; иногда генерируют ненужный refactoring; иногда оба пропускают главное; а иногда один короткий вопрос второго агента заставляет полностью пересмотреть решение первого.
И ради последнего случая я пока продолжаю этот эксперимент. Потому что один AI может очень хорошо проверить, соответствует ли код его собственному замыслу. А второй способен задать гораздо более неприятный вопрос:
А правильным ли был сам замысел?