Модель сдаёт работу текстом: «исправлено», «проверки прошли», «добавлены регрессионные тесты». Такой ответ удобно читать, но по нему нельзя установить, исчез ли баг. В наших прогонах итоговое сообщение, успешное завершение агента и приёмка изменения регулярно расходятся. Для приёмки нужны дерево кода, патч и воспроизведение. К ним прилагаются результаты проверок и отдельное решение о поставке. Сообщение исполнителя помогает найти эти артефакты, но не заменяет их.
Расхождение видно в журнале. В сохранённом срезе results.jsonl — 65 записей. Агент завершился с rc=0 в 22 из них; в 18 из этих 22 машинная оценка grade.passed отрицательная. Положительных оценок во всём файле четыре, записей с invalid=true тоже четыре. Это подсчёт строк файла, а не рейтинг моделей: журнал смешивает повторные попытки, старую постановку и задачи с заготовками. По этим строкам видно: код завершения исполнителя не годится в качестве кода приёмки.
В Hermes это видно на конкретном исправлении. В contrib.jsonl, строки 2–3, Sol/medium и Terra/medium исправляют числовой default_assignee в Hermes. Восемь выбранных тестовых файлов проходят у обоих. Отдельное воспроизведение возвращает rc=1: диспетчер уже читает значение правильно, но раскладка выбирает default вместо ожидаемого 2024. Записано passed=true, однако accepted=false. Система оценки должна сохранять расхождение и его основание: что прошло проверку, а что было отвергнуто.
Наша процедура описана в codex_routing/CONTEXT.md. Перед запуском готовится задача и снимается база на чистом дереве. Исполнитель меняет код; затем проверяющий запускает воспроизведение и тесты репозитория. После машинной приёмки отдельная модель спорит с решением. Для вклада в чужой проект остаётся ещё приёмка владельцами. Локально принятое изменение они могут не влить.
Машинная часть начинается с проверяемого различия между состояниями до и после исправления. В data/repro/ лежат скрипты, у которых rc=1 означает воспроизводящийся баг, а rc=0 — выполненный контракт. Например, hermes-agent_129590.py проверяет чтение числового идентификатора диспетчером и выбор профиля раскладкой. Существование профиля подставляется явно; отсутствие профиля проверяется отдельным путём. Воспроизведение проверяет условия задачи, включая оба варианта существования профиля. Одной ловли исключения недостаточно.
В DSPy контракт касался допустимого seed=0: выражение через or подменяло ноль зерном конструктора. Приёмка проверяет ноль, None и ненулевое значение. В contrib.jsonl, строки 4 и 7, оба решения проходят воспроизведение и восемь опорных тестовых файлов, новых падений нет. Проверка соседних значений нужна здесь потому, что исправление должно сохранить откат к настройке конструктора при None.
На стенде закрытых тикетов используется другой способ получить независимую проверку: fail-to-pass тесты из известного фикс-коммита. harness.py выгружает исходное дерево через git archive, даёт его модели, а после работы накладывает только тестовые файлы из исправляющего коммита. Реализация эталонного фикса в дерево оценки не переносится. Зелёный результат означает, что код исполнителя выдержал независимую проверку. Её взяли отдельно от его рассказа и тестов.
Ошибка сборки или сбора тестов тоже мешает приёмке. Успех в grade() требует rc==0; отсутствие строк --- FAIL не спасает дерево, которое не компилируется. Есть ограничение формата: поле tests_failed считается по маркерам Go. У первых записей Python-тикета TCK-020 оно равно нулю, хотя grade.tail сообщает «6 failed, 3 passed» (results.jsonl, строки 1–3). Даже машиночитаемый отчёт приходится сверять с тем, как сформированы его поля.
Чистый старт обязателен для каждого плеча. Иначе второй исполнитель получает часть решения первого, а сравнение перестаёт измерять одинаковую задачу. CONTEXT.md отдельно запрещает смешивать старую конструкцию стенда с новой, где для создаваемых модулей уже есть заготовки объявлений. Запуск после исчерпания лимита должен быть invalid, а не «модель не решила». Эти условия нужно проверить до оценки качества патча.
База показывает, какие проверки работали до вмешательства модели. Правило сформулировано прямо: «Приёмка = воспроизведение починилось И нет НОВЫХ падений против базы» (CONTEXT.md). Уже красный тест не становится регрессией исполнителя только потому, что снова упал. В contrib.py опорными выбираются файлы, зелёные на базе; остальные попадают в excluded. Поэтому результат нужно читать вместе с baseline_ts, списком исключений и реально выполненными файлами.
У нынешней реализации есть ещё одно ограничение. При наличии воспроизведения accepted требует устранения воспроизводимого бага, непустого патча и отсутствия новых падений. Само acc.passed в этой ветке не обязательно. Общий бюджет может оставить файлы в skipped. Успешное воспроизведение не доказывает прохождение пропущенных проверок. Без воспроизведения код требует положительного тестового результата, но и там допускается частичный прогон. Это видимый предел проверки в contrib.py, который нельзя скрывать словом «зелёный».
Тесты контрибьют-лейна запускаются пофайлово. Причина зафиксирована в CONTEXT.md и комментарии gate() в contrib.py: общий запуск иногда падает из-за протечки состояния между файлами. Такая изоляция уменьшает известный шум, хотя сама по себе не доказывает отсутствие взаимодействий между компонентами. Для приёмки нужно сохранить точную команду и область проверки; восемь зелёных файлов не означают зелёный полный набор тестов проекта.
После этого нужен спорщик. В lane.py исполнитель и ревьюер выбираются из разных плеч: Sol проверяет Terra, Terra проверяет Sol. В contrib.py задача ревью сформулирована как попытка опровергнуть решение: проверить соответствие issue, регрессии и правила репозитория, прочитать код, запустить тесты, по возможности воспроизвести баг своим способом. Ревьюер отдельно работает с инструментами. Пересказа отчёта исполнителя недостаточно.
Другая сборка должна уменьшить риск того, что приёмка повторит способ чтения задачи исполнителем. Здесь выбраны разные идентификаторы моделей. Одной новой сессией такое разделение не ограничивается. Артефакты не измеряют, насколько это снижает корреляцию ошибок. Артефакты показывают и предел разделения: разные ревьюеры могут одинаково применить ошибочное правило. Ревью даёт дополнительное возражение, которое тоже должно иметь проверяемое основание.
В contrib.jsonl сохранены три ревью, все с вердиктом «ОТКЛОНИТЬ»: одно для DSPy и два для LangGraph. Это три проверки двух задач, а не три независимых дефекта. Их последующий разбор в rejects_analysis.md позволяет отделить неполную поставку от выдуманного критерия отказа. Проверять основания нужно и после успешной машинной приёмки, и после уверенного отказа ревьюера.
Неполный дифф. В DSPy новый тест существовал, но не отслеживался системой контроля версий. Обычный git diff origin/main показывал только изменение рабочего кода. Сохранённый патч собирался другим способом и включал новый файл. rejects_analysis.md уточняет: фраза «в представленном диффе нет теста» верна, фраза «тест не написан» — нет. В сохранённом вердикте ревьюер даже заметил неотслеживаемый тест; проблема заключалась в составе представленного изменения.
После этого исправили передачу диффа ревьюеру: review_diff() делает git add -A, затем diff --cached относительно базы, как при сохранении патча (contrib.py, CONTEXT.md). Правило исполнителю из разбора звучит конкретно: перед сдачей проверить git status --short и убедиться, что все намеренные тесты входят в патч. В отказе нужно указывать неполный набор изменений. Утверждение об отсутствии теста здесь неверно. Полнота пакета и качество покрытия — разные проверяемые свойства.
Прогон по дублю чужого PR. По CONTEXT.md, семь из восьми задач ночной очереди уже закрывал чужой открытый PR. Даже технически удачный патч в такой очереди мог оказаться повторением занятой работы. В contrib.jsonl нет отдельной разметки дублей для каждого запуска, поэтому эту сводку нельзя превращать в точную статистику потерь по моделям.
Процедуру изменили: задачу проверяют срезом свежих PR через open_pr_refs и точечным поиском через issue_claimed/claimed_precise; lane.ready() пропускает занятое issue (CONTEXT.md, lane.py). Формулировка для очереди: собирать её только из свободных задач. Свободна ли задача, проверяют до запуска: от этого зависит полезность будущего вклада. Воспроизведение и проверка регрессий оценивают уже полученный код.
Ложный красный из-за параллельных прогонов. Такой случай указан в задании, но в перечисленных исходах нет привязанного к нему журнала, состояния общей базы и повторного изолированного вердикта. Подтверждены сообщения об ограничениях окружения и протечка состояния между тестовыми файлами; они не устанавливают столкновение параллельных запусков. По этим данным нельзя установить, какой тикет был ошибочно отвергнут по этой причине и какое правило поменяли после него.
Чтобы подтвердить этот случай, нужны команды конкурирующих запусков, общий ресурс и результат повторной проверки в изоляции. Предлагаемая формулировка процедуры — «при общем изменяемом ресурсе сериализовать проверки либо выделить каждому запуску собственное состояние». Это вывод для будущей приёмки, а не уже выполненное исправление, подтверждённое источниками. Это различие между фактом и гипотезой нужно сохранить и в описании процедуры.
rc=124 как таймаут. В основном коде это явно выделенный исход: harness.py возвращает timeout=true, contrib.py использует 124 при истечении времени проверки. Конкретная ошибочная трактовка сохранена в дополнительном артефакте REAL_TICKETS_STATUS.md: парс-смоук свежераспакованного дерева byte//run не успел закончиться, а первая приёмка объявила FAIL (parse) и TREE BROKEN.
После этого добавили вторую попытку с паузой 90 секунд; она видна и в real_tickets.py. Теперь таймаут парс-смоука на свежем дереве не считается доказательством битого кода без повторной проверки. Но таймаут самого исполнителя остаётся незавершённой работой: тот же отчёт фиксирует остановку микстуры на TCK-201 после 2400 секунд и отдельный провал нужной проверки интерфейса. Код возврата нужно читать вместе с фазой, где он возник.
«Production diff без нового теста = отказ». В LangGraph два ревьюера потребовали тест, отличающий явный внутренний флаг exiting от прежней проверки идентичности metadata. rejects_analysis.md установил, что изменение сохраняло публичную семантику, а существующий интеграционный тест уже проверял выход, повторный запуск и восстановление состояния. Шаблон PR проекта требовал форматирования, проверки линтером и тестового прогона; обязательного нового теста на каждый рефакторинг там не было.
Исправленная формулировка в разборе: не отклонять только из-за отсутствия нового теста; проверить существующее покрытие и неотслеживаемые файлы. Новый тест нужен, если изменённый наблюдаемый контракт не покрыт, его требует задача или репозиторий либо он необходим для различения регрессии. Для чистого рефакторинга исполнитель должен указать существующую проверку сохраняемой семантики. Эти основания можно проверить. Подсчёта изменённых тестовых файлов недостаточно.
Предложенное изменение ещё не полностью внедрено. rejects_analysis.md предлагает поменять оба промпта. В прочитанном contrib.py промпт ревью по-прежнему содержит общее «покрыт ли случай тестом», без полного уточнения из разбора. Значит, исправление сборки диффа уже реализовано, а внедрение новых формулировок полностью не подтверждено. Описывать оба пункта как завершённое исправление процесса нельзя.
Само воспроизведение тоже нужно перепроверять. В contrib.jsonl, строка 6, патч Hermes от Terra принят повторно: тот же путь, размер 7865 байт, reaccept=true, без нового агентного запуска. Нынешний скрипт воспроизведения явно подставляет существование числового профиля и объясняет это контрактом кода. Но журнал не сохраняет хеш патча и прежнюю версию проверяющего скрипта. Журнал подтверждает новый вердикт. Полностью восстановить причину расхождения с первоначальным отказом по нему нельзя.
Человек получает результат через ещё один слой — тикет-гейт. В orchestrate.py видны предварительная проверка, запуск в рабочем каталоге, таймаут, отчёт и перенос тикета по состояниям. report.py сохраняет модель, исполнителя, сессию, время, расход ресурсов, результат и ошибку. Статус done в runner.py означает отсутствие is_error у результата запуска. Проверки патча в этой классификации нет: успешное завершение рабочего процесса нельзя подменять приёмкой задачи.
При восстановлении запусков у гейта тоже есть ограничения. reaper.py возвращает зависший запуск в очередь по сроку аренды либо разбирает осиротевшее активное состояние по сохранённому отчёту. Проверка наличия живого Claude там грубая. Предельный возраст запуска допускает повторную постановку даже при живом PID. Эти механизмы двигают очередь. Они не доказывают, что повторный старт чистый, а сохранившийся код корректен. Решение о результате требует артефактов конкретной попытки.
Проверить KOD-приёмку на «одном экране» по источнику нельзя: claude_gate/lanes/kod/accept.sh в доступном дереве отсутствует. Приписать ему состав вывода невозможно. Сохранившийся lanes/uq/accept.sh предназначен для такого просмотра: показывает состояние тикета, поля отчёта и расход, последние коммиты, незакоммиченные файлы, хвосты вывода линтера и pytest, состав последнего коммита. Это пример соседнего лейна. Реализацию KOD по нему восстановить нельзя.
Такой экран помогает человеку сопоставить поставку с тикетом, но имеет ограничения. UQ-скрипт обрезает вывод и запускает проверки через конвейеры без pipefail; его собственный успешный выход не равен успешному pytest. Последний коммит тоже надо связать с нужной попыткой. Человек проверяет, достаточно ли оснований для приёмки. Нужно связать показанный код с задачей, сверить критерии и записанные исключения.
В мобильном лейне критерии привязаны к продукту: mobile_harness/CONTEXT.md требует машинных проверок через uiui.py, JVM-тесты и dex/mapping. Наличие библиотеки или строки внутри APK не доказывает, что код жив и достигается пользователем. В контексте указаны 171 JVM-тест и ноль провалов для последнего известного прогона на 24 сентября, с требованием перепроверить перед новыми работами. Эта дата ограничивает применимость числа к последующим изменениям.
Человеческое решение оставляет отдельный след. В MH-001.ok записано принятие координатором и перенос критерия зелёного APK в MH-003 из-за задачи сборки AAR. Это явное изменение границы поставки. Дополнительный mh_accept.sh фиксирует другой урок: принятое, но незакоммиченное изменение потерялось при гигиене следующего запуска. Теперь приёмка и коммит объединены шагом «принял = закоммитил»; сам этот скрипт проверки продукта не выполняет.
Для каждой попытки нужна чистая исходная ревизия. Результат проверяют воспроизведением или независимым поведенческим тестом, сравнивают с базой и не допускают новых падений. Полный патч сохраняют. Завершение агента, машинная приёмка и ревью получают отдельные статусы; спорщик другой сборки указывает основания отказа. Пропуски, таймауты и исключения должны оставаться видимыми. Человеку нужен связанный пакет доказательств и место для записанного решения, а принятую работу нужно сохранить до следующего запуска.
Что проверить скептику
— Пересчитать results.jsonl, разделяя старые задачи, scaffold, повторные попытки и invalid; сравнить rc агента с grade.
— В contrib.jsonl различить passed, accepted, reaccept и ревью; прочитать воспроизведение, baseline_ts, исключения и пропуски.
— Проверить, что review_diff() и сохранённый патч включают одинаковые файлы, особенно новые тесты.
— Сопоставить отказы с разбором и правилами репозитория; отделить предложенные промпты от внедрённых.
— Для 124 установить фазу и повторный исход; для параллельных падений потребовать отсутствующие журналы изоляции.
— Восстановить отсутствующий KOD accept.sh; проверить, что done гейта не выдан за принятие и что принятый код сохранён коммитом.
Разбор опирается на артефакты студии. Пометки файлов указывают на наши исходники в репозитории. Веб-страниц у них нет.