Промпт как спецификация, а не заклинание
В чате цена плохого запроса — один прочитанный абзац. Вы читаете ответ, морщитесь, переспрашиваете. Потеряно полминуты.
В агенте цена плохого запроса другая. Агент не отвечает текстом — он читает файлы, редактирует их, запускает команды и оставляет после себя diff. Неточная формулировка превращается в четырнадцать изменённых файлов, из которых десять вы не просили трогать, новую зависимость в pyproject.toml, тест, который проходит потому что внутри стоит assert True, и отчёт, в котором написано «готово, всё работает». Дальше начинается самое дорогое: вы читаете это глазами и пытаетесь понять, какая часть правды в слове «работает».
Отсюда тезис главы, и он короткий:
Промпт агенту — это спецификация задачи, и качество спецификации меряется одним вопросом: можно ли из неё написать падающий тест до того, как агент начнёт работать. Если нельзя — вы написали не задание, а пожелание, и проверять результат будет нечем.
Про разницу между чатом и агентом мы говорили в главе «Агент и чат», про цикл наблюдение-действие — в «Модели исполнения». Здесь — про вход в этот цикл: про текст, который вы пишете, и про то, чем он отличается от заклинания.
Откуда взялись заклинания
Промпт-инженерия как народная практика выросла на магических формулах. «Ты — senior-разработчик с двадцатилетним опытом». «Думай шаг за шагом». «Это очень важно для моей карьеры». «Не галлюцинируй». «Отвечай как эксперт мирового уровня». Каждая такая фраза когда-то у кого-то что-то улучшила, была записана в тред и разошлась как рецепт.
Часть этих приёмов имеет реальные основания — и важно понимать, какие именно и в каких условиях.
Chain-of-thought действительно работал. Wei et al., «Chain-of-Thought Prompting Elicits Reasoning in Large Language Models» (2022) показали, что подсказка с промежуточными шагами рассуждения заметно поднимает точность на арифметике и логике. Kojima et al., «Large Language Models are Zero-Shot Reasoners» (2022) показали, что даже голая фраза «Let’s think step by step» даёт эффект без примеров. Это настоящие измеренные результаты — на моделях 2022 года.
Важная оговорка про версии. Модели, которые обучены рассуждать до ответа, делают эти шаги и без вашей просьбы. Просьба «думай шаг за шагом» для них в лучшем случае избыточна, в худшем — конфликтует со встроенным режимом рассуждения. Провайдеры прямо пишут об этом в своих руководствах — см. документацию Anthropic по промпт-инжинирингу. Иначе говоря: результат 2022 года верен для тех моделей и того времени, а не для вашей текущей.
Роли — приём с гораздо более слабой доказательной базой. Zheng et al., «When “A Helpful Assistant” Is Not Really Helpful: Personas in System Prompts Do Not Improve Performances of Large Language Models» (2024) прогнали больше сотни персон на фактологических вопросах и не нашли устойчивого выигрыша: результат по персонам скакал, лучшую нельзя было предсказать заранее, а средний эффект относительно нейтрального промпта был статистически неубедительным. Это не значит, что роль вредна. Это значит, что «ты — senior-разработчик» не является рычагом, на который стоит рассчитывать.
И самое неприятное. Sclar et al., «Quantifying Language Models’ Sensitivity to Spurious Features in Prompt Design» (2023) показали, что модели меняют качество от косметики промпта — от разделителя между полями, от пробела, от порядка секций. Это свойство инструмента, а не ваша вина. И вывод из него не «подбирайте формулировку до победного», а прямо противоположный: если результат чувствителен к мелочам формулировки, единственная защита — проверка результата, а не полировка формулировки.
Систематический разбор всего зоопарка приёмов — в обзоре «The Prompt Report» (Schulhoff et al., 2024). Базовая механика промптинга разобрана на портале в главах «Промптинг: RCTF и STAR» и «Основы промптинга»; здесь мы не повторяем её, а спрашиваем другое.
Что не так с заклинанием — не то, что оно не работает. Иногда работает. Не так то, что у заклинания нет критерия провала. «Ты сеньор, почини биллинг» нельзя не выполнить: любой diff формально является попыткой. Спецификация — это формулировка, которую можно не выполнить, и это можно показать командой в терминале.
Что здесь значит слово «спецификация»
Не техническое задание на сорок страниц. Не ГОСТ. Не «сначала два дня пишем документ». Рабочее определение:
Спецификация — это текст, из которого выводятся три вещи: наблюдаемое изменение поведения системы, границы допустимого изменения кода и проверка, которую можно запустить и увидеть pass или fail.
Три части, и все три обязательны. Наблюдаемое изменение без границ даёт агенту право переписать половину репозитория. Границы без проверки дают аккуратный маленький diff, про который непонятно, решает ли он задачу. Проверка без наблюдаемого изменения — это тест ради теста.
Классическая инженерия требований пришла к тому же формально: требование считается годным, если оно однозначно, реализуемо и верифицируемо — то есть существует процедура, которая показывает его выполнение (ISO/IEC/IEEE 29148, обзор стандарта). Практика «спецификации примерами» Гойко Аджича (Specification by Example) добавляет к этому идею, что лучшая форма требования — исполняемый пример. На портале это подробно разобрано в главах «Типы требований» и «Критерии приёмки».
Новизна не в идее. Новизна в том, что раньше эта дисциплина была нужна при передаче задачи другому человеку — а человек, получив расплывчатое задание, обычно приходит и переспрашивает. Агент чаще не приходит. Агент достраивает недосказанное сам и не помечает место, где достроил.
Обратите внимание на правую панель: конус сузился, но не схлопнулся. Жёлтая точка — diff, который проходит ваш критерий приёмки и при этом не делает того, что вы хотели. Это не риторическая фигура, а самая частая форма отказа при хорошо написанной спецификации, и к ней мы вернёмся ниже.
Путь от намерения к принятому изменению
«бронирование иногда дублируется»"] Q1{"Можно ли из текста
написать падающий тест
прямо сейчас?"} D["Разобраться самому:
воспроизвести, найти место,
сформулировать инвариант"] S["Спецификация
цель + границы + критерий"] Q2{"Есть неизвестные,
которые нельзя
выбрать за вас?"} A["Агент задаёт вопросы
и не начинает править"] W["Агент работает:
читает, правит, гоняет"] R["Прогон критерия приёмки
вашей командой"] Q3{"Критерий пройден
тем способом,
который вы имели в виду?"} REV["Ревью дифа:
обход критерия, лишние файлы,
ослабленные тесты"] OK["Принято"] BACK["Уточнить спецификацию,
а не переформулировать просьбу"] I --> Q1 Q1 -->|нет| D D --> Q1 Q1 -->|да| S S --> Q2 Q2 -->|да| A A --> S Q2 -->|нет| W W --> R R -->|fail| BACK R -->|pass| Q3 Q3 --> REV REV -->|чисто| OK REV -->|найдено| BACK BACK --> S
Самая важная стрелка на этой схеме — Q1 -- нет --> D. Она означает, что часть работы нельзя делегировать, потому что она предшествует делегированию. Пока вы не понимаете, что именно сломано и как это выглядит в наблюдаемом поведении, агент не может это понять за вас — он может только правдоподобно предположить. Иногда предположение попадает в цель, и это создаёт опасную иллюзию, что этап можно пропускать всегда.
Вторая важная деталь: петля возврата ведёт в S, в спецификацию, а не в новую формулировку просьбы. Разница принципиальная. «Попробуй ещё раз, теперь получше» — это заклинание в чистом виде: вы не добавили информации, вы добавили давления. «Критерий не прошёл, потому что при повторном ключе создаётся вторая запись; добавляю в спецификацию требование уникального индекса» — это работа со спецификацией.
Семь слотов рабочей спецификации
Ниже — слоты и, что важнее, наблюдаемые последствия их отсутствия. Колонка справа — не запугивание: это то, что видно в дифе, если слот пропущен.
| Слот | Что пишете | Что будет, если пропустить |
|---|---|---|
| Цель | Наблюдаемое изменение поведения: «при повторном вызове с тем же ключом резервирование не создаётся заново» | Агент подставит свою цель. Чаще всего — самую заметную из кода рядом |
| Место | Каталог, модуль, слой. И явное «не трогай»: миграции, публичный API, конфиги окружения | Diff расползётся. Агент «заодно» отрефакторит соседний файл, потому что там было некрасиво |
| Контракт | Сигнатура, предусловия, постусловия, список ошибок и что происходит с состоянием при каждой | Ошибочные пути появятся в теле функции и не появятся в тестах. Классика |
| Критерий приёмки | Конкретная команда и ожидаемый результат | «Готово» будет означать «я убедил себя» |
| Ограничения | Никаких новых зависимостей, стиль, лимит на число файлов | В package.json появится библиотека, которую вы будете сопровождать три года |
| Точки останова | «Если непонятно, какой ключ идемпотентности — спроси, не выбирай» | Агент выберет и не отметит, что выбрал |
| Формат отчёта | Требование помечать источник каждого утверждения | Отчёт будет уверенным и непроверяемым |
Слоты 3 и 7 стоит развернуть отдельно, потому что они прямо взяты из готового материала портала.
Контракт до реализации
В пакете products/workbench/templates/memory/ — это наш собственный набор шаблонов для агентской работы, лежащий в репозитории портала, — правило CODE-01 из VERIFIED-CODE.md формулируется так: сначала сигнатура, предусловия, постусловия и список ошибок, потом тело. Не как документация, а как спецификация, которой тело обязано соответствовать и из которой выводятся тесты. Наблюдаемое нарушение: функция, у которой пути ошибок существуют в теле и отсутствуют в контракте.
Как это выглядит в реальном промпте:
# Это кладётся в задание агенту как есть. Не «сделай идемпотентно»,
# а контракт, из которого выводится падающий тест.
def reserve(account_id: str, amount: Decimal, key: str) -> Reservation:
"""Удержать `amount` на счёте `account_id`, ровно один раз на `key`.
Предусловия:
amount > 0; key стабилен между повторами одного намерения.
Постусловия:
при успехе доступный баланс уменьшен на `amount` ровно один раз,
сколько бы раз ни вызвали с тем же `key`;
при повторе с тем же `key` возвращается тот же идентификатор брони.
Ошибки:
InsufficientFunds - состояние не изменено.
AccountLocked - состояние не изменено.
StorageUnavailable - состояние НЕИЗВЕСТНО; вызывающий обязан
повторить с тем же key.
"""
Третья ошибка — та, ради которой всё и пишется. «Состояние неизвестно» — это факт о системе, и контракт, который его скрывает, порождает вызывающий код, уверенный в откате. Агент, получив такой контракт, не сможет тихо решить, что при сбое хранилища ничего не произошло: у него в задании написано обратное.
Правило CODE-02 из того же файла добавляет второе лезвие: постусловие, которого не наблюдает ни один тест, — не постусловие. Каждый пункт контракта отображается в имя теста или вычёркивается из контракта. Это ровно то, что превращает вашу спецификацию в критерий приёмки почти механически.
Формат отчёта как часть задания
Файл REASONING-DISCIPLINE.md из того же пакета требует, чтобы каждое нетривиальное утверждение агента несло один из четырёх маркеров: verified (наблюдал в этой сессии, с адресом), derived (следует из перечисленных посылок), assumed (выбрал без доказательств, осознанно), unchecked (правдоподобно, не проверял, не действовал на основании). Пятого не бывает. Утверждение о внешней системе — API, флаге, значении по умолчанию, коде ошибки — считается verified только с адресом: путь и строка, команда и её вывод, или датированный URL.
Разница между двумя отчётами об одной и той же работе:
ПЛОХО
Исправил обработку повторных запросов. Теперь всё работает корректно,
дубликаты не создаются. Тесты проходят. Также немного отрефакторил
соседний модуль для читаемости.
ХОРОШО
Изменение: services/billing/reservation.py:88-131 — добавлен уникальный
индекс по (account_id, idempotency_key) и ветка возврата существующей брони.
[verified: pytest tests/billing/test_reservation.py -q — 14 passed]
[verified: миграция 0043_reservation_key.py применена на локальной БД,
\d+ reservations показывает индекс reservations_acct_key_uniq]
[derived: при гонке двух параллельных вызовов победит один INSERT,
второй получит IntegrityError и уйдёт в ветку чтения — из семантики
уникального индекса, отдельным тестом не проверено]
[assumed: ключ идемпотентности приходит от клиента и стабилен между
повторами — в задании не указано, выбрал сам, требует подтверждения]
[unchecked] Влияние индекса на время вставки не измерял.
Не трогал: публичный API, конфиги, соседние модули.
Второй отчёт длиннее и дешевле в ревью, потому что он делит утверждения на те, которые можно не перепроверять, и те, которые нужно. Строка [assumed: ...] — это найденная дыра в вашей спецификации, поднятая на поверхность вместо того, чтобы утонуть в diff’е. Про то, что вообще стоит хранить между сессиями, — глава «Память агента».
Критерий приёмки должен быть исполняемым
Это центральное место главы, поэтому по пунктам.
Критерий — это команда, а не прилагательное. «Работает корректно», «стало быстрее», «код чище» — не критерии. Критерий выглядит так:
# Критерий приёмки. Пишет человек, до старта агента.
# Агент не имеет права редактировать эти строки — только сделать так,
# чтобы они прошли. Изменение критерия = повод остановиться и спросить.
pytest tests/billing/test_reservation.py::test_repeat_key_holds_once -q
pytest tests/billing/test_reservation.py::test_storage_error_keeps_key -q
mypy services/billing --strict
# бюджет изменения: не больше трёх файлов, не больше 120 добавленных строк
git diff --stat -- services/billing | tail -1
# запрещённые вещи в диффе — должно быть пусто
git diff -U0 | grep -nE '^\+.*(pytest\.mark\.skip|xfail|# type: ignore|assert True|TODO)'
Тест пишет человек, а не агент — по крайней мере тот тест, который является критерием. Здесь развилка, в которой ошибаются чаще всего. Если агент пишет и реализацию, и тест, который её проверяет, то «все тесты зелёные» означает ровно одно: агент согласен сам с собой. Это не проверка, это тавтология. Практический компромисс, который работает:
- тест-критерий (один-три, самые важные) пишете вы, до старта, и он падает до начала работы — это ваш
redв терминах TDD; - остальное покрытие пишет агент, и оно проверяется на ревью как обычный код, а не как оракул истины.
Падающий тест до старта — это ещё и проверка вашего понимания задачи. Если тест не удаётся написать, значит вы не знаете, как выглядит правильное поведение. Тогда задача агента не «почини», а «исследуй и покажи»: другая задача, другой критерий (например, «воспроизведи баг скриптом, который падает»), другой объём ожиданий.
Индустриальный бенчмарк устроен ровно так же. SWE-bench (статья) оценивает модели на реальных GitHub-issue не тем, похож ли патч на человеческий, а тем, проходят ли после патча тесты из соответствующего PR. Оценка по исполнению, а не по правдоподобию — единственная форма оценки, устойчивая к красноречию. Подробнее про измерения — «Оценка и бенчмарки».
Где агент молча достраивает недосказанное
Недоспецификация не даёт ошибку. Она даёт заполненный пробел. Типология — по тому, что именно пропущено:
решает сам, не спрашивает А->>Р: правит обе А->>Р: нужен ключ — в модели поля нет Note over А: пробел: откуда ключ?
добавляет колонку и миграцию А->>П: pytest П-->>А: 2 упавших теста в соседнем модуле Note over А: пробел: что делать с чужими падениями?
правит соседние тесты под новое поведение А->>П: pytest П-->>А: зелено А-->>Ч: «Готово, идемпотентность добавлена, тесты проходят» Note over Ч,А: три решения приняты за вас,
ни одно не отмечено в отчёте rect rgba(127,127,127,0.12) Ч->>А: та же задача со слотами 2, 5, 6 А->>Ч: «В reservation.py две точки входа: reserve и reserve_bulk.
Спецификация покрывает обе? Поля под ключ нет —
добавлять миграцию запрещено ограничением. Останавливаюсь.» end
Нижний блок — не фантазия про послушного агента. Это поведение, которое включается двумя строками в задании: «если в задаче обнаружилась развилка, которую я не оговорил, — остановись и спроси» и «миграции схемы запрещены». Никакой магии здесь нет: вы просто перечислили запреты и назначили точки останова. Подробнее — «Планирование и декомпозиция».
Сводная таблица — что пропущено и как это ловится в diff’е:
| Пропущено в спецификации | Что сделает агент | Чем ловится |
|---|---|---|
| Какой из нескольких кандидатов править | Правит все или самый заметный | git diff --stat, число файлов больше ожидаемого |
| Разрешены ли новые зависимости | Добавит библиотеку | git diff -- '*requirements*' '*package.json*' '*.toml' |
| Разрешены ли миграции | Создаст миграцию | git status по каталогу миграций |
| Что делать с чужими падающими тестами | Правит тесты под новый код | git diff -- 'tests/*' — изменения в тестах, которых вы не просили |
Граничные случаи и null |
Выберет поведение молча | Чтение контракта: есть ли ветка, которой нет в задании |
| Когда прекратить попытки | Будет крутить цикл до лимита | Счёт токенов, длинный лог однотипных правок |
| Формат ответа и маркеры | Даст уверенный текст без адресов | Отчёт без единого пути к файлу |
Полная типология отказов — включая те, что не связаны со спецификацией, — в главе «Где агенты врут». Здесь важно другое: большая часть перечисленного лечится текстом, а не инструментами. Это дёшево.
Обход критерия: жёлтая точка
Теперь честная сторона медали. Хорошая спецификация с исполняемым критерием сужает конус, но оставляет класс решений, которые проходят критерий и не решают задачу. Не потому, что агент злонамерен — у него нет намерений. Потому что «пройти проверку» — это то, к чему сходится процесс, а «сделать правильно» — то, что вы имели в виду.
Практический список того, что видели в дифах:
- ожидаемое значение захардкожено в реализацию, потому что тест проверяет именно его;
- тест помечен
@pytest.mark.skipс комментарием «flaky»; - проверка обёрнута в
try/except, который глотает исключение и возвращает заглушку; # type: ignoreвместо приведения типов;- ассерт ослаблен: было
assert result == expected, сталоassert result is not None; - мок поставлен на ту самую функцию, поведение которой проверяется;
- тайм-аут увеличен вместо исправления гонки.
Отсюда правило, которое стоит внести прямо в спецификацию: изменения в файлах тестов и в конфигурации проверок — отдельный предмет ревью, всегда. И механическая защита в критерии приёмки:
# Тесты-критерии не должны меняться вообще
git diff --exit-code -- tests/billing/test_reservation.py \
|| echo "НАРУШЕНИЕ: агент правил тест-критерий"
# Ослабления в любых тестах — на просмотр
git diff -U0 -- 'tests/**' | grep -E '^\+' | grep -E 'skip|xfail|is not None|mock\.|assert True'
Это не паранойя и не обвинение инструмента. Это ровно та же дисциплина, с которой смотрят на PR стажёра, который очень хочет, чтобы CI позеленел. Как читать такой diff целиком — глава «Ревью кода от агента», общая практика ревью — «Ревью кода и стандарты».
Жизненный цикл задачи
Два состояния на этой схеме обычно отсутствуют в рассказах про агентов, и оба нужны.
Останов — это то, что вы обязаны задать сами: лимит попыток, лимит бюджета, лимит времени. Без него цикл «правлю — гоняю — не сходится — правлю» крутится, пока не упрётся в ограничение инструмента, и каждый оборот стоит токенов. Формулировка в задание: «если после двух попыток критерий не проходит — остановись, опиши, что мешает, и не продолжай». Про то, во что это обходится, — глава «Цена работы с агентом».
СделатьРуками — это полноценный исход, а не поражение. Переход в него из Останов после трёх бесплодных кругов экономит больше, чем любая оптимизация промпта.
Когда спецификация дороже задачи
Здесь заканчивается пропаганда и начинается арифметика. Написание спецификации — работа. Она стоит вашего внимания, самого дефицитного ресурса в этой схеме. Иногда она не окупается.
Левый нижний квадрант — не про лень, а про честность. Переименование символа делается рефакторингом IDE за две секунды и с гарантией корректности, которой у агента нет. Форматирование делает форматтер. Опечатка правится курсором. Промпт, описание границ, прогон, чтение дифа — всё это дороже.
Верхний левый квадрант — тот, где живёт большинство интересных задач: сделать руками дорого, но и описать трудно. Отсюда правило: не пишите спецификацию на то, чего не понимаете; сначала сведите неизвестное к известному, потом делегируйте известное. Плавающий тест сначала воспроизводят, потом делегируют починку с критерием «сто прогонов подряд зелёные».
Про цифры. Их в этой главе почти нет намеренно: публичные измерения продуктивности с ИИ-инструментами разъезжаются на порядок в зависимости от постановки, и переносить их на вашу кодовую базу нельзя. Один результат стоит упомянуть именно потому, что он неудобный: METR в исследовании «Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity» (июль 2025) получил, что опытные разработчики на задачах в собственных, хорошо знакомых им репозиториях выполняли задачи медленнее с ИИ-инструментами, при этом субъективно считая, что стали быстрее. Выборка небольшая, условия специфические (зрелые проекты, эксперты, инструменты начала 2025 года), обобщать нельзя — но одну вещь этот результат показывает надёжно: ощущение ускорения не является измерением ускорения. Если хотите знать, окупается ли у вас агент, — меряйте у себя, а не верьте ни энтузиастам, ни скептикам. Включая эту главу.
Постоянная часть спецификации: файл в репозитории
Часть слотов повторяется в каждой задаче: команды сборки и тестов, слои и их границы, запреты, стиль, порядок коммитов. Это не место промпта — это место файла в репозитории, который агент читает автоматически. У Claude Code это CLAUDE.md, у ряда других инструментов — AGENTS.md или свой формат правил; конкретные пути между версиями менялись, проверяйте текущую документацию своего инструмента, а не статьи полугодовой давности.
Что туда стоит класть:
- команды: как собрать, как прогнать тесты, как запустить линтер — с точными строками;
- границы слоёв: «доменный слой не импортирует инфраструктуру»;
- запреты: «не менять миграции», «не добавлять зависимости без вопроса»;
- договорённости об отчёте: маркеры источника, обязательные адреса;
- места, где живут неочевидные вещи: «конфиг тестовой БД в
docker/test.yml».
Что туда класть вредно:
- пересказ архитектуры, который устареет через месяц и станет активной дезинформацией;
- всё, что меняется от задачи к задаче — это в промпт, а не в файл;
- противоречивые правила, накопленные слоями: агент выберет одно из двух, и не обязательно ваше;
- объём ради объёма — файл читается в контекст каждый раз, у него есть цена (глава «Контекст»).
Готовый набор такой постоянной части у портала уже есть: пакет products/workbench/templates/memory/ — AGENT-MEMORY-CONTRACT.md (короткий блок, который вклеивается в контракт агента), MEMORY-PROTOCOL.md (когда писать заметку, а когда не писать вообще), REASONING-DISCIPLINE.md (маркеры и адреса), VERIFIED-CODE.md (контракт до реализации, никаких выдуманных API), COMPOSITION-AND-LAWS.md (законы композиции, которые превращаются в property-тесты). Общее свойство всех правил пакета: у каждого сформулировано наблюдаемое нарушение. Правило, нарушение которого нельзя увидеть в дифе или в отчёте, — украшение, и из пакета такие вычеркнуты. Это же критерий годится и для ваших собственных правил.
Практики «разработки от спецификации» в более широком смысле — когда спецификация становится первичным артефактом, а код производным — разобраны на портале в главе «SDD и оркестрация». Оговорка та же, что и везде в этом треке: подход даёт выигрыш там, где спецификацию действительно можно написать до кода, и превращается в церемонию там, где нельзя.
Полный пример: до и после
Задача одна и та же. Сначала — как обычно пишут.
Привет! У нас в биллинге проблема: если пользователь дважды нажимает
кнопку оплаты, создаются две брони. Ты опытный бэкендер, посмотри
пожалуйста и почини аккуратно, чтобы ничего не сломать. Сделай хорошо,
добавь тесты. Спасибо!
Что здесь есть: вежливость, роль, эмоциональный нажим («аккуратно», «хорошо»), описание симптома. Чего нет: границ, контракта, критерия, точек останова. Агент начнёт с чтения репозитория (токены), найдёт несколько мест, где создаются брони, выберет сам, изобретёт ключ идемпотентности, добавит миграцию, поправит соседние тесты и отчитается «готово».
Теперь то же задание как спецификация.
ЦЕЛЬ
Повторный POST /reservations с тем же Idempotency-Key не создаёт вторую
бронь: возвращается 200 с телом уже существующей брони.
МЕСТО
services/billing/reservation.py и services/billing/api.py.
Не трогать: миграции (каталог migrations/), публичные схемы в
services/billing/schemas.py, конфиги окружения.
КОНТРАКТ
reserve(account_id: str, amount: Decimal, key: str) -> Reservation
предусловия: amount > 0; key непустой, стабилен между повторами
постусловия: баланс уменьшен ровно один раз на key;
повтор с тем же key возвращает тот же reservation_id
ошибки: InsufficientFunds — состояние не изменено
AccountLocked — состояние не изменено
StorageUnavailable — состояние неизвестно, повтор с тем же key
КРИТЕРИЙ ПРИЁМКИ (я его написал, он сейчас падает, менять его нельзя)
pytest tests/billing/test_reservation.py::test_repeat_key_holds_once -q
pytest tests/billing/test_reservation.py::test_concurrent_same_key -q
mypy services/billing --strict
ОГРАНИЧЕНИЯ
Новых зависимостей нет. Миграций нет — колонка idempotency_key уже есть
в модели (services/billing/models.py:41), она пока не используется.
Бюджет: не больше 3 файлов и 120 добавленных строк.
ТОЧКИ ОСТАНОВА
Если для выполнения нужна миграция — остановись и скажи, не делай.
Если критерий не проходит после двух попыток — остановись и опиши,
что мешает.
Если в коде нашлась вторая точка создания брони — спроси, входит ли
она в задачу.
ОТЧЁТ
Каждое утверждение с маркером: verified (с путём и строкой или командой
и выводом), derived, assumed, unchecked. Отдельно перечисли всё, что
пришлось выбрать за меня.
Второй текст длиннее примерно втрое. Он написан за десять минут, из которых восемь ушло на написание падающего теста — то есть на то, чтобы самому понять, что значит «починить». Эти десять минут не подарок агенту: тест остаётся в репозитории, контракт уезжает в docstring, а секции «Цель» и «Ограничения» становятся описанием PR почти дословно. Спецификация переживает промпт — этим она и отличается от заклинания, которое выбрасывается вместе с сессией.
Есть и обратная сторона, о которой честно: если задача была на пять строк кода, эти десять минут вы потратили зря. Смотрите на квадрант выше.
Типичные ошибки
Описывать реализацию вместо поведения. «Добавь в reserve проверку через SELECT ... FOR UPDATE» — это код, написанный словами, и он дороже кода. Спецификация фиксирует наблюдаемое поведение и границы; выбор реализации — работа агента. Исключение: в незнакомой большой кодовой базе указание места экономит много поиска, и тогда «начни смотреть с reservation.py:88» оправдано.
Путать длину с точностью. Задание на две страницы, где нет ни одной команды, которую можно запустить, хуже задания на десять строк с критерием приёмки.
Ставить критерием «все тесты зелёные», когда тесты пишет агент. Разобрано выше: это согласие агента с самим собой.
Забывать негативную часть. «Что не делать» несёт больше информации, чем «что делать», потому что множество нежелательных изменений огромно, а желательных — узко.
Молчаливо менять спецификацию по ходу. «А, ну ещё сделай заодно кэш» посреди работы — это новая задача с новым критерием, а не уточнение. Диффы от таких добавок разъезжаются хуже всего.
Требовать уверенности вместо адресов. «Ты уверен?» — бесполезный вопрос: агент ответит «да» с той же лёгкостью, с какой ответил бы «нет». Полезный вопрос: «покажи вывод команды» или «дай путь и строку».
Переносить чужие рецепты промптов без проверки. Формулировка, которая помогла кому-то в треде год назад на другой модели и другом репозитории, — гипотеза, а не практика. Проверяется только у вас и только прогоном.
Считать спецификацию гарантией. Она сужает конус, не схлопывает его. Ревью дифа остаётся обязательным этапом — см. «Проверяемость».
Класть в задание секреты «для контекста». Ключи, токены, дампы с персональными данными не должны попадать в промпт — про это отдельно в главе «Безопасность».
Чек-лист перед запуском агента
Пробегитесь глазами. Если больше двух ответов «нет» — вы пишете заклинание.
- Могу ли я прямо сейчас написать тест, который падает и который пройдёт после выполнения задачи?
- Написан ли этот тест, и падает ли он?
- Названо ли место — каталог, модуль, файл?
- Написано ли явно, что трогать нельзя?
- Есть ли контракт: сигнатура, предусловия, постусловия, ошибки и состояние при каждой?
- Есть ли команда, по выводу которой я приму или отклоню работу?
- Указан ли бюджет: число файлов, строк, попыток?
- Названы ли развилки, на которых агент обязан остановиться и спросить?
- Потребовал ли я маркеров и адресов в отчёте?
- Проверил ли я, что руками это не быстрее?
Мини-итог
Промпт агенту — это спецификация задачи, и её качество меряется не красотой формулировок, а наличием критерия провала. Магические фразы и роли имеют слабую и версионно-зависимую доказательную базу; чувствительность моделей к косметике промпта — аргумент не за полировку текста, а за обязательную проверку результата.
Рабочая спецификация состоит из семи слотов: цель как наблюдаемое изменение, место и запреты, контракт с предусловиями и ошибками, исполняемый критерий приёмки, ограничения, точки останова, формат отчёта с маркерами источника. Критерий приёмки пишет человек и до старта — иначе «зелёные тесты» означают лишь, что агент согласен сам с собой. Даже хорошая спецификация оставляет класс решений, которые проходят критерий и не решают задачу, поэтому чтение дифа на предмет обхода критерия остаётся обязательным.
И граница честности: если описать задачу дороже, чем сделать её руками, — делайте руками. Ускорение, которое ощущается, и ускорение, которое измерено, — разные вещи, и меряется оно только у вас.
Источники
- Wei et al. Chain-of-Thought Prompting Elicits Reasoning in Large Language Models, 2022.
- Kojima et al. Large Language Models are Zero-Shot Reasoners, 2022.
- Zheng et al. When “A Helpful Assistant” Is Not Really Helpful: Personas in System Prompts Do Not Improve Performances of Large Language Models, 2024.
- Sclar et al. Quantifying Language Models’ Sensitivity to Spurious Features in Prompt Design, 2023.
- Schulhoff et al. The Prompt Report: A Systematic Survey of Prompting Techniques, 2024.
- Jimenez et al. SWE-bench: Can Language Models Resolve Real-World GitHub Issues?, 2023; swebench.com.
- METR. Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity, 2025.
- Anthropic. Prompt engineering overview и Claude Code best practices.
- Gojko Adzic. Specification by Example, Manning, 2011.
- ISO/IEC/IEEE 29148 — Systems and software engineering: requirements engineering.
- Пакет портала
products/workbench/templates/memory/:AGENT-MEMORY-CONTRACT.md,REASONING-DISCIPLINE.md,VERIFIED-CODE.md,COMPOSITION-AND-LAWS.md.
Что дальше
Спецификация — это текст, который попадает в контекст агента и занимает в нём место. Сколько его там, что вытесняется первым при компакции и какие части задания приходится повторять, потому что они не переживают сжатия, — в следующей главе.