3,583 papers
arXiv:2610.30635 77 22 окт. 2026 г. FREE

Specification–Oracle Gap: агент пишет правильный код и всё равно проваливает тесты

КЛЮЧЕВАЯ СУТЬ
Агент написал рабочий код и получил ноль: он назвал константу не так, как ждёт тест. Аудит «зависимости тестов минус зафиксированное» позволяет найти такие дыры в ТЗ до запуска агента. Выпиши всё, на что ссылаются тесты, и вычти то, что уже есть в коде, названо в ТЗ или выводится из соглашений проекта. Остаток — разрыв. В Go, Rust и Java один такой пропуск валит весь пакет: 80,6% провалов против 48,4% у единиц без скрытых имён.
Адаптировать под запрос
⚡

TL;DR

Если задачу агенту описали словами, а проверяют заранее написанными тестами, тесты часто требуют того, чего в описании нет: точное имя константы, путь к файлу, дословный текст сообщения. Автор называет это разрывом между спецификацией и проверкой (specification–oracle gap). Агент может реализовать поведение верно, назвать константу по-своему и получить «ноль». Тест тогда проверяет не «умеет ли агент сделать задачу», а «угадал ли агент названия из чужого кода».

Боль в том, что провал выглядит как «агент не справился», хотя он справился. В Go, Rust и Java тесты пакета компилируются вместе. Одно неугаданное имя валит весь пакет. В статье такие «заблокированные» единицы падали в 80,6% прогонов, сопоставимые без скрытых имён — в 48,4%. Агенты угадывали только 45% скрытых имён. Выводимые по соглашениям проекта (например, Allow → AllowCtx) они воспроизводили в 79% случаев.

Метод — аудит по формуле: собрать всё, что тесты используют (имена, пути, дословные строки), вычесть то, что уже есть в кодовой базе, названо в ТЗ или выводится из соглашений проекта. Остаток и есть разрыв. Автор делает это скриптом, а читатель может применить ту же логику вручную или через LLM. Вывод для практики: всё, от чего зависят тесты, должно быть в ТЗ или в кодовой базе.

🔬

Схема метода

ШАГ 1: Выписать всё, на что ссылаются тесты → идентификаторы, пути импорта, дословные строки
ШАГ 2: Вычесть то, что «зафиксировано» →
        • уже есть в кодовой базе
        • названо в ТЗ дословно
        • выводится из соглашений проекта (префикс/суффикс, место файла, сигнатура интерфейса)
ШАГ 3: Остаток = разрыв → G(тест) = Зависимости(тест) \ Зафиксированное
ШАГ 4: Оценить радиус поражения →
        • имя или путь: падает весь пакет (всё компилируется вместе)
        • дословная строка: падает только один тест
ШАГ 5: Закрыть разрыв → дописать имена/пути/строки в ТЗ (или убрать зависимость из теста)

Шаги 1–3 в статье автоматизирует статический скрипт на пяти языках. Модель при этом не вызывается.

🚀

Пример применения

Задача: Вы фрилансер и делаете для интернет-магазина на FastAPI уведомления в Telegram о оплаченных заказах (оплата через ЮKassa). У заказчика уже лежит папка tests/ с приёмочными тестами. Вы отдаёте задачу Claude Code. Тесты импортируют notifications.telegram.TelegramNotifier, константу ORDER_PAID_TEMPLATE и сравнивают текст «Заказ №{id} оплачен: {сумма} ₽». В вашем ТЗ про это ни слова. Пока вы не проверили, агент может сделать работающий код, который провалит приёмку.

Промпт:

Ты проверяешь техническое задание перед передачей агенту.

<ТЗ>
При получении вебхука ЮKassa со статусом payment.succeeded отправлять
менеджеру сообщение в Telegram: номер заказа и сумму. Токен бота брать
из переменных окружения. Если Telegram недоступен — не ронять обработку
вебхука, логировать ошибку.


<Тесты>
{вставь содержимое tests/test_notifications.py и tests/conftest.py}


<Структура_репозитория>
{вставь вывод tree -L 3 и содержимое app/config.py}


Шаг 1. Выпиши ВСЁ, на что ссылаются тесты: импорты и пути модулей,
имена классов, функций, констант, полей, а также строки, которые
сравниваются с выводом дословно.
Шаг 2. Для каждого пункта определи: 
 (а) уже есть в репозитории,
 (б) названо в ТЗ дословно или теми же словами по порядку,
 (в) однозначно выводится из соглашений репозитория (укажи, из каких),
 (г) нигде не зафиксировано.
Шаг 3. Выдай таблицу только по пунктам (г): что именно, в каком тесте,
что случится, если агент назовёт иначе (упадёт один тест или весь модуль).
Шаг 4. Предложи дополнение к ТЗ — блок «Обязательные имена и тексты»,
который закрывает все пункты (г).

Результат: Модель выдаст четыре блока: список зависимостей тестов, их классификацию по (а)–(г), таблицу «скрытых» пунктов с радиусом поражения и готовый блок для вставки в ТЗ. По вашему примеру ожидайте в таблице путь модуля, имя класса, константу шаблона и дословный текст сообщения. Конкретные формулировки зависят от ваших тестов.

🧠

Почему это работает

Слабость. ТЗ описывает что должно происходить, а тесты проверяют как это названо и где лежит. Всё, что в ТЗ не сказано, агент выбирает свободно, и это нормально. Тест же считает свободный выбор ошибкой. Агент не видит, что тесты зависят от конкретного имени: «правильного» ответа у него нет, только догадка. В скомпилированных языках догадка ещё и срабатывает на весь пакет сразу.

Сильная сторона. Агенты хорошо следуют явным указаниям и соглашениям, которые видны в кодовой базе. Выводимые имена они воспроизводили в 79% случаев. Если имя написано в ТЗ, оно перестаёт быть угадайкой: названные в ТЗ имена не ухудшали результат.

Как метод это использует. Он переводит зависимости тестов из «скрытых» в «явные»: сначала находит остаток, потом закрывает его текстом. Не нужно просить агента «быть внимательнее». Нужно убрать место для угадывания.

Рычаги: - Что считать «зафиксированным» → расширь (соглашения проекта) или сузь (только дословные упоминания в ТЗ), если хочешь строже или мягче аудит. - Радиус поражения → в Go/Rust/Java/TypeScript сначала закрывай пункты, которые валят целый модуль. Дословные строки — потом. - Сообщения об ошибках → если агент видит и может исправить падения при импорте, цена разрыва ниже (так в SWE-Chain). - Порядок задач в цепочке → если задача опирается на имена из предыдущей, повторяй их в её ТЗ.

📋

Шаблон промпта

Ты — аудитор разрыва между ТЗ и тестами.

<Определения>
Зависимость теста — всё, что решение должно предоставить, чтобы тест прошёл:
идентификаторы (функции, классы, константы, методы, поля),
пути модулей и файлов, которые импортируются или подменяются,
строки, которые сравниваются с выводом дословно.
Зафиксировано — то, что (1) уже есть в исходной кодовой базе,
(2) названо в ТЗ дословно или теми же словами по порядку (для файла — только
если назван файл), (3) однозначно выводится из соглашений, уже
используемых в кодовой базе.
Разрыв = Зависимости \ Зафиксировано.


<ТЗ>{текст_тз}
<Тесты>{код_тестов}
<Кодовая_база>{структура_и_ключевые_файлы}
<Предыдущие_задачи>{ТЗ_предыдущих_задач_или_«нет»}

<Процедура>
1. Выпиши все зависимости каждого теста.
2. Для каждой определи класс:
   ЗАФИКСИРОВАНО_БАЗА / ЗАФИКСИРОВАНО_ТЗ / ВЫВОДИМО (укажи соглашение) / РАЗРЫВ.
3. Для каждого РАЗРЫВА определи радиус поражения:
   имя или путь → падает вся единица, которая собирается вместе ({единица_сборки});
   только строка → падает один тест.
4. Отдельно отметь «скрытые в названии теста» случаи: часть имени теста
   строится из строки, которую знает только эталонное решение.


<Вывод>
Таблица: зависимость | тест | класс | радиус поражения.
Затем блок «Обязательные имена, пути и тексты» для вставки в ТЗ — только по РАЗРЫВАМ.
Не включай в блок то, что выводимо или уже зафиксировано.

Подставьте: {текст_тз} — задание агенту, {код_тестов} — файлы тестов, {структура_и_ключевые_файлы} — вывод tree и 2–3 файла, по которым видны соглашения проекта, {единица_сборки} — пакет, модуль или тестовый файл, который у вас собирается целиком.

🚀 Быстрый старт — вставь в чат:

Вот шаблон аудита разрыва между ТЗ и тестами. Адаптируй под мою задачу: [твоя задача].
Задавай вопросы, чтобы заполнить поля.

[вставить шаблон выше]

LLM спросит про язык и систему сборки, где лежат тесты и что проверяют, какие в проекте соглашения об именах и есть ли предыдущие задачи в цепочке. Это нужно, чтобы правильно определить «выводимое» и посчитать радиус поражения: на Go и Python он разный.

⚠️

Ограничения

⚠️ Вывод для практики переносится, а не проверен: Статья анализирует бенчмарки. Что добавление имён в ТЗ повышает долю успешных задач у вас, авторы как вмешательство не измеряли. Они показали, что скрытое имя ведёт к провалу, а в нескольких прогонах указанное имя помогало пройти тесты.

⚠️ Основной анализ — на одном бенчмарке: Глубокие выводы сделаны на семи репозиториях. Сравнение с четырьмя другими бенчмарками авторы называют предварительным: единицы проверки и размеры разные.

⚠️ Зависит от того, как устроены тесты: Если тесты проверяют поведение через публичный интерфейс (scikit-learn, ripgrep), разрыва почти нет. Если тесты лезут во внутренности пакета (go-zero) — он большой.

⚠️ Зависит от того, что видит агент: Если среда показывает агенту ошибки импорта и он может их исправить, цена разрыва падает.

⚠️ Аудит несовершенен: Скрипт даёт ложные срабатывания (около 11%) и пропуски (около 7%). Ручной или LLM-аудит нужно проверять глазами.

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

⚠️ Часть работы недоступна в тексте: Раздел с рекомендациями для создателей бенчмарков в материале обрывается.

🔍

Как исследовали

Автор взял SWE-Milestone — бенчмарк, где агент в одном репозитории проходит цепочку зависимых изменений (98 этапов в семи проектах на пяти языках). Проверяют заранее написанными тестами из исходного проекта, а описание этапа генерирует LLM. Он написал статический скрипт, который для каждого теста находит все имена, пути и дословные строки и вычитает то, что есть в исходном коде, в ТЗ или выводится по соглашениям. Скрипт проверили на выборках, размеченных вручную: точность 89,2%, полнота 92,9%.

Затем взяли 35 опубликованных прогонов (Claude Code, Codex, Gemini CLI, OpenHands, 27 моделей). В 25 из 98 этапов нашёлся тест со скрытым неустранимым именем или путём. Единицы сборки с такой зависимостью падали в 80,6% случаев, сопоставимые без неё — в 48,4%. Отношение 1,67 осталось после поправки на размер и репозиторий. Названные в ТЗ имена на падения не влияли (1,08).

Дальше — контрфактуал без агентов. В эталонном решении переименовали одну скрытую константу, и тесты не компилировались в 126 из 128 случаев. Это подтверждает механизм: виновато именно имя, а не «сложность задачи».

Удивило то, что рейтинг почти не меняется, если убрать затронутые тесты. Зато в go-zero средняя оценка прогонов вырастает с 38,33 до 42,08.

Ещё одна находка: от способа построения ТЗ зависит многое. Где ТЗ — заметки о релизе или краткое изложение от LLM, треть задач и больше затронута. Где задачи отобраны людьми (SWE-bench Verified) или написаны под тесты (DeepSWE), затронуто почти ничего. Правда, у редких случаев цена высокая: в SWE-bench Verified две задачи никто из 78 сильных систем не решил.

💡

Адаптации и экстраполяции

🔧 Техника: добавить блок «Контракт имён» в ТЗ → закрыть разрыв до запуска

Перед запуском агента вставляйте в задачу такой блок (это перенос идеи статьи, а не её шаблон):

<Контракт_имён>
Обязательные имена и пути (не переименовывать):
- модуль: {путь}
- класс/функция/константа: {имя}
Тексты, которые сравниваются дословно:
- {строка}
Свободный выбор: всё остальное — называй по соглашениям проекта.

🔧 Техника: правило в CLAUDE.md / AGENTS.md → агент сам не угадывает

Если в тестах (или в примерах использования) есть имя, путь или дословная
строка, которых нет в ТЗ и в кодовой базе, НЕ придумывай их.
Остановись и перечисли такие пункты вопросами.

Эффект: агент превращает «скрытое» в вопрос вместо молчаливой догадки. Это моя вариация, в статье не проверялась.

Экстраполяция: аудит в цепочке задач. Если вы даёте агенту несколько задач подряд, добавьте в промпт аудита блок <Предыдущие_задачи>. Большинство «жёстких» скрытых имён в статье были класса A2: имена, которые ввела предыдущая задача, не упомянув в своём ТЗ, а последующие тесты на них полагаются.

🔗

Ресурсы

  • What the Specification Did Not Say: Specification–Oracle Gaps in Test-Graded Coding-Agent Benchmarks — Fei Ding, Alibaba Group
  • SWE-Milestone (Deng et al., 2026), SWE-bench Verified (OpenAI, 2024), SWE-EVO (Le et al., 2025), SWE-Chain (Lam et al., 2026), DeepSWE 1.1 (Huang et al., 2026)
  • Авторы обещают публикацию аудита, исправленных списков фильтров и дополнений к спецификациям

📋 Дайджест исследования

Ключевая суть

Агент написал рабочий код и получил ноль: он назвал константу не так, как ждёт тест. Аудит «зависимости тестов минус зафиксированное» позволяет найти такие дыры в ТЗ до запуска агента. Выпиши всё, на что ссылаются тесты, и вычти то, что уже есть в коде, названо в ТЗ или выводится из соглашений проекта. Остаток — разрыв. В Go, Rust и Java один такой пропуск валит весь пакет: 80,6% провалов против 48,4% у единиц без скрытых имён.

Принцип работы

Формула простая: разрыв = зависимости тестов минус зафиксированное. Зависимость — это имя, путь импорта или строка, которую тест сравнивает дословно. Зафиксировано то, что уже есть в кодовой базе, названо в ТЗ или однозначно следует из соглашений проекта. Дальше считаешь радиус поражения. Неугаданное имя или путь роняет весь пакет, потому что тесты компилируются вместе. Неугаданная строка роняет один тест. Всё, от чего зависят тесты, должно лежать в ТЗ или в кодовой базе. Это как экзамен, где проверяющий смотрит только лист №3, а в билете не сказано, на каком листе писать. Ответ может быть блестящим, но не на том листе.

Почему работает

ТЗ описывает, что должно происходить. Тесты проверяют, как это названо и где лежит. Всё несказанное агент выбирает сам, а тест считает такой выбор ошибкой. Скрытые имена агенты угадывали лишь в 45% случаев. Имена, выводимые из соглашений проекта (например, Allow превращается в AllowCtx), они воспроизводили в 79%. Имена, названные в ТЗ, результат не ухудшали. Агент не путает логику, ему просто негде взять правильный ответ, и угадывание превращается в монетку. Поэтому не нужно просить его «быть внимательнее». Нужно убрать место для угадывания. Честная оговорка: авторы анализировали бенчмарки. Что добавление имён в ТЗ повышает долю успешных задач, они как вмешательство не измеряли.

Когда применять

Разработка с агентами (Claude Code, Cursor и им подобные) → задачи, где приёмочные тесты написаны заранее, особенно когда тесты лезут во внутренности пакета и проверяют точные тексты сообщений. Сначала закрывай пункты, которые валят целый модуль (в Go, Rust, Java, TypeScript). Дословные строки оставляй на потом. Если задача опирается на имена из предыдущей в цепочке, повторяй их в её ТЗ. НЕ подходит, когда тесты проверяют поведение через публичный интерфейс: там разрыва почти нет. Скрипт авторов ошибается: около 11% ложных срабатываний и около 7% пропусков. Проверку через LLM тоже смотри глазами.

Мини-рецепт

1. Собери материал: ТЗ, файлы тестов, вывод tree и 2-3 файла, по которым видны соглашения проекта.
2. Запусти аудит: пусть модель выпишет все имена, пути импорта и строки, которые тесты сравнивают дословно.
3. Раздай ярлыки: каждому пункту нужен один из четырёх: есть в базе, названо в ТЗ, выводится из соглашений (с указанием каких) или РАЗРЫВ.
4. Посчитай радиус поражения: имя или путь роняет весь пакет, строка роняет один тест. Укажи, что у тебя собирается целиком: пакет, модуль или тестовый файл.
5. Закрой дыры: вставь в ТЗ блок «Обязательные имена, пути и тексты» только по разрывам. Выводимое туда не тащи.
6. Проверь глазами: модель может увидеть разрыв там, где его нет, или пропустить настоящий. Пробегись по таблице сам.
7. Для цепочки задач: добавь в запрос ТЗ предыдущих задач. Иначе модель решит, что имя из прошлого шага «ничьё».

Примеры

[ПЛОХО] : Сделай уведомления в Telegram о оплаченных заказах из вебхука ЮKassa. Тесты лежат в tests/, прогони их в конце.
[ХОРОШО] : Ты проверяешь ТЗ перед передачей агенту. Вот ТЗ: {текст}. Вот тесты: {tests/test_notifications.py и conftest.py}. Вот структура репозитория: {tree -L 3 и app/config.py}. Выпиши всё, на что ссылаются тесты: пути модулей, имена классов и констант, строки, сравниваемые дословно. Каждому пункту поставь: есть в репозитории, названо в ТЗ, выводится из соглашений (каких) или нигде не зафиксировано. По незафиксированным дай таблицу: что, в каком тесте, упадёт один тест или весь модуль. Затем напиши блок «Обязательные имена и тексты» для вставки в ТЗ. Что получится на выходе: модуль notifications.telegram, класс TelegramNotifier, константа ORDER_PAID_TEMPLATE и текст «Заказ №{id} оплачен: {сумма} ₽» окажутся в блоке, который уйдёт агенту вместе с ТЗ. Точные формулировки зависят от ваших тестов.
Источник: What the Specification Did Not Say: Specification-Oracle Gaps in Test-Graded Coding-Agent Benchmarks
ArXiv ID: 2610.30635 | Сгенерировано: 2026-10-23 07:30

Проблемы LLM

ПроблемаСутьКак обойти
Агент делает задачу верно, но проваливает автоматическую проверку из-за непрописанных имёнВ ТЗ описано поведение. Проверка требует точного имени константы, пути файла или дословного текста. Агент называет всё по-своему. Работа верная, а результат «ноль». Произвольное имя агент угадывает примерно в половине случаев. В Go, Rust, Java и TypeScript тесты пакета собираются вместе. Одно неугаданное имя валит весь пакет. Провал выглядит как «агент не справился». Причину не видно, пока не сравнишь тесты и ТЗНе проси агента «быть внимательнее». Убери место для угадывания. Выпиши всё, от чего зависит проверка, и впиши недостающее в ТЗ. Как это сделать, см. метод ниже

Методы

МетодСуть
Аудит разрыва между ТЗ и проверкой — находит скрытые требования до запуска агентаЧто делать: собери всё, на что ссылаются тесты (или чек-лист приёмки). Вычти то, что уже зафиксировано. Остаток и есть разрыв. Разрыв = Зависимости проверки \ Зафиксированное. Шаги: (1) Выпиши имена, пути импорта и строки, которые сравниваются дословно. (2) Для каждого пункта определи: уже есть в кодовой базе, названо в ТЗ, выводится из соглашений проекта, или нигде не сказано. Соглашения — это префиксы, суффиксы, место файла, сигнатура интерфейса. (3) Оцени радиус поражения. Имя или путь валят весь пакет. Дословная строка валит один тест. (4) Допиши в ТЗ блок «Обязательные имена, пути и тексты» только по остатку. Запрос к модели: дай ТЗ, тесты и структуру репозитория (tree плюс 2–3 файла с образцами). Попроси классифицировать каждую зависимость: БАЗА / ТЗ / ВЫВОДИМО (укажи соглашение) / РАЗРЫВ. Затем таблицу «зависимость, тест, радиус поражения». Затем готовый блок для вставки в ТЗ. Почему работает: агент свободно выбирает всё, что не названо. Тест считает свободный выбор ошибкой. Явный список убирает свободу там, где она вредна. Порядок: сначала закрывай пункты, которые валят целый пакет. Строки потом. Когда да: тесты написаны заранее и не меняются. Тесты лезут во внутренности кода. Есть цепочка задач, где одна опирается на имена из другой. Повторяй такие имена в ТЗ следующей задачи. Когда нет: тесты проверяют только поведение через публичный интерфейс. Там разрыва почти нет. Осторожно: модель-аудитор даёт ложные срабатывания и пропуски. Проверяй список глазами
📖 Простыми словами

What the Specification Did Not Say: Specification-Oracle Gaps in Test-Graded Coding-Agent Benchmarks

arXiv: 2610.30635

Когда AI-агенты заваливают тесты по кодингу, они лажают не в логике. Корень проблемы — specification–oracle gap, дикий разрыв между ТЗ и проверкой. Промпт ставит задачу что сделать, а скрытые тесты требуют угадать, как именно назвать скрытую константу. Агент пишет идеальный рабочий код, обзывает переменную по-своему — и срезается на ноль баллов. Текущие бенчмарки меряют не навыки программирования, а способность к телепатии.

Это как идеально сдать экзамен по вождению, но услышать: «Вы не сдали, потому что не назвали машину ласточкой». Экзаменатор втайне ждал именно этого слова, но забыл предупредить. В итоге модель наказывают за свободу выбора там, где не было рамок. Это не тупость AI, это полный провал самих тестов — они требуют дословного совпадения мыслей, о котором никто не просил.

Что реально ломает оценку: хрупкие ассерты, жесткие пути импортов вроде TelegramNotifier и слепая привязка к конкретным шаблонам текста. Если тест втихаря ждет константу ORDER_PAID_TEMPLATE, а в ТЗ про неё тишина, агент выберет любое адекватное имя и проиграет. В компилируемых языках одна такая оплошность мгновенно роняет 100% сборки пакета целиком.

Проблему вскрыли на бенчмарках, но это главная боль боевого продакшена. Натрави того же Claude Code на проект с готовой папкой тестов — и рабочий функционал с треском провалит пайплайн просто из-за несовпадения имен. Принцип универсален для любой AI-автоматизации: если валидация строже спецификации, ты получаешь херню на приёмке вместо готовой фичи.

Главный вывод прост: хватит слепо верить лидербордам и требовать от моделей ясновидения. Либо сразу вшивай в ТЗ точные интерфейсы и сигнатуры, либо учи автотесты проверять логику и результат, а не имена файлов. Иначе ты просто сливаешь бюджет на угадайку с закрытыми глазами, пока агент пытается додумать за тебя ТЗ.

Работа с исследованием

Адаптируйте исследование под ваши задачи или создайте готовый промпт на основе техник из исследования.

0 / 2000
~0.5-2 N-токенов ~10-30с
~0.3-1 N-токенов ~5-15с