У промпта есть свойство, которого у кода нет: он портится, когда его никто не трогает. Репозиторий чистый, деплоев нет, а качество ответов медленно ползёт вниз. Первая реакция — искать баг в никуда: откатывать нечего, бисектить не по чему, а git blame показывает человека, который когда-то поправил запятую и ни при чём.
Отсюда растёт популярный совет: обращайтесь с промптом как с кодом — положите в git, водите через ревью, покройте тестами. Мы считаем этот совет наполовину верным. Код выживает не потому, что лежит в git, а потому, что вокруг него петля обратной связи: сборка, тесты, исключение в рантайме. Когда мир меняется, петля срабатывает сама. У промпта из этой петли перевезли только хранение — сигнализацию бросили. Ниже — механика, из которой следует, почему промпт гниёт быстрее, какие знакомые приёмы не работают и при каком условии мы неправы.
Почему он гниёт быстрее
Код тоже гниёт, вопрос в скорости. Скорость задаёт то, как быстро мир сообщает коду, что его допущения устарели. У кода мир кричит: падает сборка, краснеет тест, летит исключение. У промпта расхождение с реальностью не становится событием никогда — и это не фигура речи, а устройство исполнения.
Промпт не парсится. Код исполняется программой с фиксированной грамматикой: неверное допущение либо не пройдёт компиляцию, либо упадёт в рантайме. Промпт исполняется статистически: любая строчка «валидна», любой сбой мягкий. Ответ стал чуть хуже — это не исключение, это нормальный вторник.
Зависимость едет под вами. Модель — это зависимость без контракта. У библиотеки есть версия и сигнатуры: если поведение меняется, об этом объявляет смена мажора. Модель меняет поведение без объявления: тот же промпт, тот же вход — другое распределение выходов. Представьте библиотеку, у которой патч молча меняет семантику функции. Против такой зависимости гниёт любой код; промпты просто живут в этом мире по умолчанию.
Контракт растворён в формулировках. Внутри промпта зашиты допущения: какие входы бывают, какой у них формат, чего модель не должна делать никогда. Отдельно они нигде не записаны — размазаны по тексту. Когда входы меняются, допущения устаревают молча. git blame здесь врёт: он показывает, кто последним правил текст, а не кто изменил поведение. Поведение изменил вендор модели, а виноватым в репозитории выглядит человек, тронувший запятую.
Рост идёт в одну сторону. Каждый инцидент добавляет строчку «никогда не делай X». Удалить старую никто не решается: проверять удаление дорого, а несущая она или балласт — неизвестно. Промпт превращается в осадочную породу прошлых инцидентов, и слои начинают спорить: новое предписание противоречит старому запрету, а модель разрешает спор непредсказуемо. Чем длиннее текст, тем слабее держится каждый приказ — и тем охотнее добавляют ещё строку, потому что «а вдруг поможет». Петля закручена в обратную сторону.
И главное — нет локальности. У правки кода есть радиус поражения: области видимости, типы и модули подсказывают, что может задеть изменение, и тест рядом с изменением ловит его дёшево. В промпте каждое слово находится в зоне видимости каждого: добавленный пример может перевернуть смысл инструкции в другом конце текста, «теста рядом» не существует в принципе. Минимально достаточная проверка любой правки — весь проверочный набор целиком.
Отсюда арифметика гниения. Стоимость проверки правки не зависит от размера правки. Значит, правки проверяют редко или на глаз. Каждая непроверенная правка оставляет систему в состоянии «мы не знаем, что она теперь делает», и накопленное незнание — это и есть гниение. У кода проверка локальна и дёшева, поэтому петля замыкается на каждый коммит. У промпта проверка глобальна и дорога, поэтому петля разомкнута. Он гниёт быстрее не по чьей-то вине — по арифметике.
Тупик: что пробуют первым
Первым делом переносят привычный цикл: промпт в git, ревью на диффы, эталонные тесты. Выглядит добросовестно. Не работает — и если не понять почему, следующая попытка унаследует ту же ошибку.
Git даёт историю без ответственности. Ценность истории кода в воспроизводимости: сбой воспроизводится, бисект находит виновный коммит. Поведение промпта ревизией не определяется — модель под ним уже другая. Откатываешь «сломавший» коммит — ничего не меняется, бисект водит по кругу. История есть, ответственности нет: красиво переплетённый дневник.
Ревью видит слова, а не поведение. Правка в пару слов может изменить поведение сильнее, чем большой рефакторинг, но ревьюер видит в диффе изменённые слова и ставит approve — инструмента увидеть эффект у него нет. Плюс бытовой разлад: правят обычно в плейграунде, а в репозиторий копируют результат; место проверки и источник истины расходятся.
Главная ловушка — эталонные тесты: набор входов и точных ожидаемых выходов. Если тест сверяет точный текст ответа, он краснеет от любой легитимной вариации — модель вправе сказать то же самое другими словами. Если тест сверяет только формат — схему, обязательные поля, валидность, — он пропускает всю смысловую часть гниения. Выходов два, и оба плохие: тесты, которые всегда красные, или тесты, которые всегда зелёные. И те и другие хуже отсутствия тестов, потому что приучают команду: красный — это шум. Сигнал, который всегда шумит, отравит и те правильные проверки, которые вы поставите позже.
Второй по частоте ход — переписать промпт начисто: структура, заголовки, аккуратные формулировки. Не помогает, потому что гниение живёт не в формулировках, а в разомкнутой петле проверки. Переписанный промпт начинает гнить с первого дня, только теперь никто не помнит, какие старые строки были несущими: переписка снесла археологию, не проведя раскопок.
Что реально держит промпт
Если гниение — арифметика стоимости проверки, лечение тоже арифметическое: сделать проверку настолько дешёвой, чтобы она тикала сама. Всё остальное — следствия.
Контракт — рядом с промптом, но отдельно от него. Записать: какие входы промпт считает возможными, что обязан выдать, чего не должен делать никогда. Это спецификация, по которой потом пишутся проверки, — документация для людей получается сама собой. Для агентов ровно этот принцип мы разбирали в материале о том, чего агент не делает: список запретов пишут заранее и проверяют механически, а не вспоминают после инцидента.
Контрольная выборка — и таймер. Набор фиксированных входов, настолько маленький, что его не жалко гонять часто. Идея та же, что у эталонных тестов, но сверяется не точный текст, а контракт из предыдущего абзаца. При каждой правке промпта выборка прогоняется целиком — локальной проверки не существует, мы это уже выяснили. Но главный сдвиг рамки в другом. CI для кода привязан к коммитам, потому что кроме вас код никто не меняет. Промпт меняется и без ваших коммитов: вендор модели делает свой «коммит» в вашем рантайме, а ваш CI об этом не узнает никогда. Значит, выборка должна гоняться по расписанию — без правок, без повода. Не ворота на входе, а канарейка в шахте. Это, по сути, единственное настоящее отличие дисциплины промптов от дисциплины кода.
Бюджет считается в одну строку: стоимость прогона = размер выборки × токены на прогон × цена токена. Подставьте свои. Правило одно: выборка должна быть такой, чтобы таймер не требовал ничьего разрешения. Если прогон надо обосновывать — выборка велика, а не дисциплина слаба. Как считать такой бюджет на реальной нагрузке — тема нашего материала про стоимость инференса.
Проверки механические — прежде судейских. Сначала те, которые не гниют: валидность схемы, обязательные поля, запрещённые действия, отказы, длина. Это дёшево и, что важнее, чувствительно к сдвигу модели — об этом разговор в материале о том, что мерить у модели кроме точности. Судья на модели — второй эшелон: он сам промпт и гниёт по тем же законам, поэтому его фиксируют и держат примитивным.
Остальное — служебные правила, по строке на каждое. Новая строка в промпте должна знать, какую старую она заменяет; не можете назвать — не добавляйте. Промпт, не влезающий на экран, — уже не промпт, а легаси: сносить и перепланировать, а не надстраивать. Инварианты гниют медленно, примеры — быстро, потому что кодируют допущения о том, какие бывают входы; разнести их по разным местам и проверять с разной частотой дешевле, чем гонять всё одним куском. И владелец: алерты таймера должны приходить человеку, а не в канал, где их никто не читает.
Позиция редакции
Наш тезис: «промпт как код» — метафора, которая перевезла багаж наполовину. Хранение перевезли — git, ревью, история; жизненный цикл бросили на старой квартире. Код живёт не в git, а в петле, которая срабатывает сама, когда меняется мир. У промпта мир меняется чаще — под ним едет модель, — а петля дороже, потому что локальности нет. Поэтому храните промпт как код, а проверяйте как зависимость без контракта: по расписанию, а не только по коммиту. Пока проверка привязана к вашим правкам, гниение замечается по жалобам — то есть там, где чинить уже дороже, чем переписывать.
Мы неправны при понятном условии: модель под промптом неподвижна и воспроизводима. Зафиксированный контракт с вендором, локально поднятые веса, запертая версия, детерминированные параметры выборки — тогда у зависимости появляется версия, у поведения — воспроизводимость, у бисекта — смысл. Промпт честно становится кодом против зафиксированной библиотеки, и привычного цикла «коммит — тест — ревью» достаточно; статья не про ваш случай. Узкое второе условие: если выход промпта исчерпывается механически проверяемым форматом, эталонные тесты ловят всё, и гнить нечему.
Во всех остальных случаях — контракт, выборка и таймер: из механики следует именно это.