Дешёвые ИИ-агенты сделали ровно то, что я им написал. Ошибка была в задании
Обычно я планирую сложные вещи дорогой моделью, а писать код поручаю ей же. В этот раз решил разделить роли: план и брифы — дорогой модели, а сам код по готовому брифу пусть пишут модели подешевле. Логика на бумаге железная: самое рискованное место — придумать, что делать, а не напечатать это кодом. Раз риск в планировании, там и держим сильную модель. Исполнение — рутина, с ней справится модель попроще, дешевле и быстрее.
Написал четыре подробных брифа, отдал их дешёвым исполнителям. За два дня все четыре куска доехали до продакшена отдельными пул-реквестами. Автоматический прогон тестов зелёный, ревью — тоже отдельная модель — замечаний по стилю и паре мелочей, ничего критичного. По всем формальным признакам можно было закрывать задачу и переходить к следующей.
Я решил всё равно прочитать каждый дифф руками, просто по привычке — не доверяю зелёным галочкам целиком. И в каждом из четырёх пул-реквестов нашлась настоящая прореха.
В куске про здоровье аккаунта запись статуса синхронизации не имела защиты от сбоя — если что-то в процессе падает, отметка о последней успешной синхронизации просто не пишется, и никто об этом не узнает: в интерфейсе аккаунт выглядит тихим, а не сломанным. В куске с действиями над одним аккаунтом ручка возвращала внутреннюю ошибку сервера на кривой идентификатор вместо понятного отказа — сработало бы это только на мусорном вводе, но именно такой ввод рано или поздно прилетает от чужого клиента или бага на фронте. В куске со сводкой по клиентам SQL-запрос, который считает деньги, не был покрыт ни одним интеграционным тестом — юнит-тесты гоняли логику на моках, а реальный запрос к базе не проверял никто. И там же, в сводке, доля стоимости считалась по клиенту вообще без проверки, активна ли у него подписка — то есть отменённый клиент мог показываться в отчёте с ненулевой стоимостью, как будто платит.
Первая мысль была привычной: исполнители накосячили, надо было выбрать модель посильнее. Но когда я перечитал брифы, стало неловко. Про защиту от сбоя записи я не написал ни слова — просто «пиши статус синхронизации туда же». Про обработку кривого идентификатора в брифе тоже ничего не было — я не подумал о плохом вводе, потому что в голове тестировал сценарий только с настоящими данными. Про интеграционный тест на денежный запрос я упомянул «покрыть тестами», не уточнив, каким именно — исполнитель честно написал юнит-тесты и на этом остановился, а я бы на его месте, скорее всего, сделал то же самое. Про статус подписки в расчёте доли я вообще не думал в момент постановки задачи: у меня в голове все клиенты в сводке были активными по умолчанию.
Ни один из четырёх исполнителей не отклонился от брифа ни на строчку. Они сделали именно то, что было написано, причём чисто и без явных ошибок в самом коде. Прорехи появились не потому, что дешёвая модель хуже пишет код — а потому, что она не задаёт вопросов, которые задал бы человек, если бы усомнился в задании. «А что если запись не пройдёт?», «а что если id битый?», «а тест на реальной базе тоже нужен?», «а отменённых клиентов сюда точно надо?» — эти вопросы должен был задать я сам себе на этапе брифа, а вместо этого рассчитывал, что их зададут ревью или тесты. Но ревью и тесты проверяют, соответствует ли код заданию, а не то, полно ли само задание. Дыру в требовании автоматика в принципе не видит — она не знает, что там должно было быть что-то ещё.
Разделение «риск — в планировании, исполнение — рутина» оказалось наполовину неверным. Планирование действительно рискованнее исполнения. Но экономия на исполнителе ничего не меняет в этом риске — она просто переносит его без изменений в готовый код, потому что дешёвый исполнитель точно транслирует любую дыру брифа, ни на секунду не остановившись усомниться. С дорогой моделью на исполнении шанс, что она сама заметит нехватку в задании и переспросит, был бы выше — не потому что она умнее пишет код, а потому что она охотнее спорит с постановкой.
Все четыре дыры я закрыл до конца дня отдельными правками поверх тех же пул-реквестов: fail-safe на запись статуса, отдельная обработка некорректного идентификатора, интеграционный тест на реальной базе для денежного запроса, проверка активной подписки перед показом доли стоимости в сводке. Работающего кода это не сломало нигде — все правки прошли как ревью, так и повторный прогон тестов.
Экономить на исполнителе я не перестал — просто перестал доверять зелёным тестам как последней инстанции, если сам не перечитал бриф на предмет того, что в нём не сказано. Дешёвая модель сделала свою работу идеально. Вопрос был не к ней.