Ты даешь задание агенту. Он пишет код. Код не работает. История повторяется
Знакомая картина? Ты просишь Claude Code или GitHub Copilot Custom Agent написать функцию валидации email. Агент выдает что-то вроде /^[^@s]+@[^@s]+.[^@s]+$/. Тесты падают на user@localhost. Ты объясняешь ошибку. Агент извиняется, правит. Новый код ломается на плюсах в адресе. Этот бесконечный пинг-понг съедает часы.
Проблема не в тупости моделей. Современные LLM вроде GPT-5, Claude 4 или открытых аналогов знают синтаксис лучше многих джунов. Проблема в двусмысленности человеческого языка. \"Проверь email\" - это что? Проверить формат? Существование домена? Отсутствие в спам-базе? Агент угадывает. Часто неудачно.
Стандартный промпт \"напиши функцию для X\" - это игра в испорченный телефон. Ты думаешь об одном, модель понимает другое, код делает третье. Итерации исправлений лишь добавляют шум в контекст.
Контракт вместо разговоров: что такое исполняемая спецификация
Забудь на время о промптах. Представь документ в формате YAML или JSON, который делает две вещи:
- Описание поведения на понятном языке (\"Given-When-Then\" из BDD).
- Автоматически исполняемые тесты, которые можно запустить против сгенерированного кода.
Это не ТЗ для разработчика. Это машиночитаемый контракт между твоим намерением и реализацией агента. Ты не говоришь \"сделай валидацию\". Ты говоришь: \"вот 10 конкретных примеров входных данных и ожидаемый результат для каждого. Сгенерируй код, который пройдет все эти тесты\".
Из чего состоит этот YAML-контракт
Спецификация - это не просто список тест-кейсов. Это структура, которую агент может парсить и анализировать. Вот базовая схема на 2026 год, адаптированная под понимание современными LLM.
# spec/email_validator.spec.yml
api_version: 'spec.dev/v1'
target:
component: validator
function: is_valid_email
language: python
framework: pydantic
description: |
Проверяет, соответствует ли строка формату адреса электронной почты.
Учитывает RFC 5322, но с практическими ограничениями (не допускает устаревшие форматы).
behavior:
- scenario: \"Валидные стандартные email-адреса\"
given: \"функция is_valid_email\"
cases:
- input: \"user@example.com\"
expected: true
comment: \"Базовый кейс\"
- input: \"first.last@sub.domain.co.uk\"
expected: true
comment: \"Много точек и субдоменов\"
- input: \"user+tag@example.com\"
expected: true
comment: \"Плюс-адресация разрешена\"
- scenario: \"Невалидные email-адреса\"
given: \"функция is_valid_email\"
cases:
- input: \"plainaddress\"
expected: false
- input: \"@no-local-part.com\"
expected: false
- input: \"user@.no-domain-part\"
expected: false
- input: \"user@domain..com\"
expected: false
- input: \"user@localhost\"
expected: false
comment: \"Локальные адреса не считаем валидными для нашей бизнес-логики\"
- scenario: \"Пограничные кейсы и безопасность\"
given: \"функция is_valid_email\"
cases:
- input: \"\"
expected: false
comment: \"Пустая строка\"
- input: null
expected: false
comment: \"Null значение, тип входных данных - строка, но на случай ошибки выше\"
- input: \"x\" * 320 + \"@example.com\"
expected: false
comment: \"Превышение максимальной длины локальной части (RFC-ограничение)\"
constraints:
performance:
max_time_ms: 10
security:
forbid_regex_dos: true
max_input_length: 1024
generated_code_validation:
run_tests_command: \"pytest spec/email_validator_test.py -xvs\"
coverage_threshold: 100
linter: ruff
formatter: blackОбрати внимание на детали. target.component и function говорят агенту, что именно строить. behavior.scenarios - это живая документация. constraints диктуют нефункциональные требования. generated_code_validation - инструкция по автоматической проверке. Это полный цикл.
1 Почему YAML, а не промпт в свободной форме?
Структура убивает неопределенность. LLM, особенно новые архитектуры с улучшенным пониманием контекста, отлично извлекают данные из YAML/JSON. Им не нужно догадываться о твоих приоритетах - все явно. Кроме того, такой файл становится артефактом проекта. Его можно версионировать, ревьюить, использовать в CI/CD для автотестов сгенерированного кода. Это основа для агентной инженерии как дисциплины.
Пошаговый план: внедряем исполняемые спецификации в рабочий процесс
Теория - это хорошо. Давай сделаем так, чтобы это работало сегодня. Я использую эту схему с GitHub Copilot Custom Agents и открытыми моделями через инструменты вроде Continue.dev.
1 Шаг 1: Пишем спецификацию ДО того, как звать агента
Переверни привычный процесс с ног на голову. Не \"агент, напиши код, а я потом придумаю тесты\". Сначала садишься и накидываешь сценарии в YAML. Что система должна делать в разных условиях? Какие крайние случаи? Используй формат \"scenario\" и \"cases\". Это заставляет тебя самого продумать логику до мелочей. (Совет: если лень писать YAML с нуля, попроси того же агента по шаблону сгенерировать каркас спецификации - ирония).
2 Шаг 2: Создаем промпт-драйвер, который читает спецификацию
Твой промпт теперь выглядит не как монолог, а как системная инструкция. Вот его ядро:
Ты - code-solver агент. Твоя задача - реализовать функцию, строго соответствующую приложенной исполняемой спецификации.
СПЕЦИФИКАЦИЯ (YAML):
{{ВСТАВЬ_СОДЕРЖАНИЕ_SPEC.YAML}}
ИНСТРУКЦИИ:
1. Внимательно проанализируй target, behavior и constraints.
2. Сгенерируй код ТОЛЬКО на указанном языке, который удовлетворит ВСЕМ тест-кейсам.
3. Учтй нефункциональные ограничения (производительность, безопасность).
4. После генерации, предоставь команду для запуска автотестов, чтобы я мог немедленно проверить корректность.
Начни с анализа спецификации. Опиши своими словами, что должна делать функция и какие ключевые edge-кейсы учтены.Этот промпт - драйвер. Он превращает спецификацию в задачу. Агент сначала анализирует, потом генерирует. Это снижает вероятность импульсивного неправильного кода. Если интересно, как строить такие стабильные промпты для разных моделей, у меня есть отдельный разбор.
3 Шаг 3: Автоматизируем проверку - превращаем spec в тесты
Спецификация - это данные. Напиши небольшую утилиту (или попроси агента), которая конвертирует YAML в юнит-тесты. Для Python это может быть pytest, для JS - Jest. Главное - автоматически. Пример для нашего YAML:
# spec_to_test.py (утилита конвертации)
import yaml
import jinja2
# Загружаем spec
with open('email_validator.spec.yml') as f:
spec = yaml.safe_load(f)
# Шаблон pytest
template = \"\"\"
import pytest
from validator import is_valid_email
{% for scenario in spec.behavior %}
# {{ scenario.scenario }}
{% for case in scenario.cases %}
def test_{{ scenario.scenario | slugify }}_{{ loop.index }}():
result = is_valid_email({{ case.input | tojson }})
assert result == {{ case.expected }}, f\"Failed for input: {{ case.input }}\"
{% endfor %}
{% endfor %}
\"\"\"
# Генерируем и сохраняем файл тестов
# ... (пропускаю детали рендеринга)Запустив эту утилиту, ты получаешь email_validator_test.py с десятками точных тестов. Сгенерированный код агента должен проходить их все. Нет места для интерпретаций.
4 Шаг 4: Интеграция в CI и мониторинг дрейфа
Положи spec-файлы в Git. Настрой GitHub Actions или GitLab CI так, чтобы при изменении спецификации:
- Запускался агент на генерацию новой реализации.
- Автоматически запускались сгенерированные тесты.
- Если тесты падают - падает и сборка.
Это превращает спецификацию в источник истины. Если бизнес-логика меняется (например, нужно разрешить email с кириллицей), ты правишь YAML, а CI заставляет агента перегенерировать код под новые требования. Никакого ручного переписывания тестов и надежд на память. Это прямой ответ на проблему технического долга от AI-генерации.
Где собака зарыта: нюансы и грабли, на которые ты точно наступишь
Метод не панацея. Вот что может пойти не так, и как избежать.
| Ошибка | Почему возникает | Как исправить |
|---|---|---|
| Агент генерирует код, который проходит тесты, но нарушает constraints (например, медленный) | LLM плохо учитывают нефункциональные требования, если они не проверяются автоматически. | Добавь в generated_code_validation этап бенчмарка или статического анализа сложности. Используй pytest-benchmark. |
| Спецификация становится монолитом на 500 строк, непонятным ни тебе, ни агенту. | Желание покрыть все случаи сразу. Агент теряется в деталях. | Дроби спецификации по принципу одной ответственности. Один файл - одна функция или один сценарий. Ссылайся между ними через $ref. |
| Агент предлагает \"костыльное\" решение, которое технически проходит тесты, но уродливо. | Он оптимизирует под прохождение тестов, а не под чистоту кода. | Добавь в constraints секцию code_quality с требованиями по линтеру (ruff, eslint). В промпте явно попроси избегать антипаттернов. |
| Тесты, сгенерированные из spec, сами содержат баги (некорректные assertion). | Утилита конвертации написана с ошибками или edge-кейсы в input данных (например, спецсимволы) сломали генерацию кода теста. | Пиши утилиту конвертации максимально просто. Протестируй ее на десятке разных spec. Или используй готовые инструменты, если они появятся к 2026 (пока их нет). |
Самый опасный нюанс: иллюзия полноты. Ты написал 20 тест-кейсов в spec и думаешь, что покрыл все. Агент генерирует код, который их проходит. Но в продакшене всплывает двадцать первый случай, который ты не предусмотрел. Спецификация - не замена мышлению. Это инструмент для повышения точности, а не магический черный ящик. Всегда оставляй место для ручного анализа сложных сценариев.
Вопросы, которые ты уже хочешь задать
Это же замедляет работу! Раньше я просто просил агента, а теперь надо писать YAML...
В краткосрочной перспективе - да, добавляет шаг. В долгосрочной - экономит часы на дебаг и перегенерацию. Когда спецификация написана, ты можешь давать ее разным агентам (Copilot, Claude, локальной модели) и получать сопоставимый результат. Это инвестиция в стабильность. Особенно важно в команде, где несколько человек работают с агентами.
А если моя задача - не изолированная функция, а целая фича с API, базой и интерфейсом?
Спецификации масштабируются. Создай набор файлов: api_contract.yml, database_schema.yml, ui_flow.yml. Используй ссылки между ними. Современные мультиагентные системы, те же OpenCode или Claude Code, могут распределять такие спецификации между специализированными агентами (бэкенд, фронтенд, тестировщик). Главное - держать контракты между модулями в машиночитаемом виде.
Поддерживают ли это современные IDE и плагины?
На 2026 год - да, экосистема растет. Плагины для VS Code и JetBrains учатся читать spec-файлы и предлагать на их основе контекстные промпты. GitHub Copilot Custom Agents позволяет загружать спецификации как контекст. Ожидай, что к концу года появятся специализированные инструменты для управления библиотекой исполняемых спецификаций.
Можно ли использовать для legacy кода, который нужно понять или переписать?
Обратный процесс: попроси агента проанализировать существующую функцию и на ее основе сгенерировать исполняемую спецификацию. Это создаст документацию и набор регрессионных тестов. Потом можно модифицировать уже spec и перегенерировать улучшенный код. Отличный способ начать рефакторинг.
Что будет дальше? Спецификация как интерфейс
Я вижу будущее, где исполняемые спецификации станут стандартным интерфейсом между человеком и машиной в разработке. Ты не будешь писать промпты на естественном языке для рутинных задач. Ты будешь выбирать из каталога шаблонов спецификаций (\"валидация данных\", \"REST endpoint CRUD\", \"миграция базы\"), кастомизировать их под свой контекст и отправлять агенту на выполнение.
Агенты, в свою очередь, станут лучше понимать эти структуры. Модели нового поколения, обученные не только на коде, но и на корреляции между spec и реализацией, будут выдавать код с первой попытки. Возникнет рынок проверенных спецификаций для типовых задач. И главное - исчезнет мучительная боль \"он меня не понимает\".
Начни с малого. Возьми следующую мелкую задачу для агента. Остановись. Опиши ее в YAML по нашему формату. Дай агенту spec и промпт-драйвер. Сравни результат с тем, что было раньше. Один раз попробовав, назад ты уже не захочешь.
P.S. Если твой агент генерирует код, который работает, но выглядит как кошмар, и ты хочешь понять, на каком именно шаге он сломался - вспомни про техники визуализации рассуждений агента. Спецификации уменьшат количество таких кошмаров, но не сведут его к нулю. Мы все еще в начале пути.