Суть
Без флагов любое изменение поведения агента — это деплой. Значит, эксперимент требует релиза, откат требует релиза, а во время инцидента вы ждёте сборку, пока пользователи получают сломанные ответы.
Флаг разрывает эту связь. Вариант уже в проде, просто выключен; переключение занимает секунды и не трогает код.
Флаг = объект конфигурации, а не переключатель
Главное отличие от обычных фича-флагов. Строка model_version = "v3.2" бесполезна: поведение агента определяется связкой целиком, и промпт под одну модель на другой работает иначе.
flags:
support_agent:
stable:
provider: anthropic
model: claude-sonnet-5
prompt_id: support_v3.1
temperature: 0.2
candidate:
provider: anthropic
model: claude-haiku-4-5
prompt_id: support_v3.2
temperature: 0.0
rollout: { candidate: 5 } # процент трафика; sticky по user_id
Такой флаг переключает всё сразу и откатывает тоже всё сразу — рассинхрона «новая модель со старым промптом» не возникает.
Что это даёт команде
Эксперименты без релизов. Несколько моделей и промптов работают параллельно; смена конфигурации — правка флага, а не выкатка. Аудитория расширяется ступенями: сотрудники → 10% пользователей → все.
Инструмент митигации. Во время инцидента флаг выключает сломанную фичу быстрее любого отката деплоя. Kill switch по умолчанию — обязательная часть конфигурации, а не опция: именно он спасает в сценариях вроде «бот пообещал внедорожник за доллар».
Динамическое распределение. Продвинутый вариант — multi-armed bandit сам сдвигает трафик в сторону выигрывающего варианта, без ручных ступеней.
Где хранить сам текст промпта. Оба варианта рабочие, и выбор между реестром и git-ссылкой второстепенен — принципиально то, что флаг хранит идентификатор версии, а не текст. Копия текста внутри флага гарантированно разъедется с оригиналом. Реестр удобнее, когда промпты меняют не только разработчики и нужна история правок вне репозитория; git-ссылка проще, когда промпты живут рядом с кодом и выкатываются вместе с ним.
Отдельная оговорка, которую легко пропустить: идентификатор версии, вставленный в начало промпта, ломает кэширование префикса для всего, что за ним следует (Prompt Caching). Версия должна быть в конфигурации и в трейсе, а не внутри текста, который уходит в модель.
Комбинаторика флагов не тестируется полным перебором — при пяти флагах это 32 конфигурации, при десяти уже больше тысячи, и большинство из них никогда не встретится в проде. Работают три ограничения вместо перебора.
Безопасный дефолт как инвариант. Проверяется одна вещь: при всех флагах в положении по умолчанию система ведёт себя корректно. Тогда любая незнакомая комбинация деградирует к известному состоянию, а не к неопределённому.
Тестируются только реально включённые сочетания. Флаг существует ради конкретного эксперимента, поэтому в проде одновременно живут единицы комбинаций, а не все возможные — их и покрывают, взяв список из конфигурации, а не из математики.
Флаг имеет срок жизни. Комбинаторика растёт потому, что флаги не удаляют после завершения эксперимента. Удаление отработавшего флага — часть выкатки, а не уборка на потом; без этого дисциплина из раздела ниже перестаёт работать за пару месяцев.
Снимаемость — критерий выбора механизма, а не следствие
Срок жизни объявить легко, а вот выполнить его мешает то, как флаг вставлен. Отсюда правило, действующее в момент заведения, а не в момент уборки: из двух работающих способов выбирается тот, который потом дешевле убрать.
Практическое следствие в коде: проверка в рантайме предпочтительнее условной компиляции. Обе гасят ветку, но директива компиляции расползается по сборочной конфигурации, требует пересборки на каждое переключение и оставляет следы в местах, которые поиском по имени флага не находятся. Условная компиляция берётся только там, где без неё не собирается: код под конкретную платформу, зависимость, которой при выключенном флаге не существует.
Удаление выполнимо ровно настолько, насколько перечислены точки вставки. «Убрать флаг» — не одно действие, а обход списка мест, где он живёт, и список этот пишется при заведении, а не вспоминается через полгода:
| Где живёт | Что остаётся, если пропустить |
|---|---|
| объявление в перечислении флагов | мёртвое имя, которое ещё кто-то прочитает как живое |
| списки аудиторий: сотрудники, превью, релиз | флага нет, а он в чьём-то списке — расхождение конфигурации с кодом |
| проверки в рантайме по всей кодовой базе | ветка условия, у которой один исход |
| предикаты доступности в UI и горячих клавишах | пункт, который включается и выключается ничем |
| ветки «иначе» — поведение при выключенном | мёртвый код, который следующий агент воспроизведёт как образец (AI Tech Debt) |
Перечень ищется по имени флага, а не по каталогу владельца: общий флаг читается и из соседних модулей, и обход только «своей» директории оставляет живые проверки в чужих.
Это тот же обратный ход, что у правил в файле инструкций (Agent First Repository): у операции добавления обязана быть парная операция снятия, и записаны они вместе, иначе снятие не выполняется никогда.
Дисциплина
- Флаг — временная конструкция. Каждый оставленный флаг удваивает число возможных состояний системы; через полгода никто не помнит, какая комбинация тестируется. У флага должен быть владелец и срок жизни.
- Флаг не заменяет гейт. Он даёт возможность переключить, но решение переключать принимается по порогам (Canary Release LLM).
- Kill switch проверяется заранее. Механизм, который впервые дёргают в момент аварии, — это не механизм, а надежда.
Связано с
- Canary Release LLM — флаги как исполнительный механизм ступенчатой выкатки
- Shadow Mode — включение зеркалирования тем же способом
- Incident Management AI — kill switch как первый шаг митигации
- Agent Deployment Cloud — почему конфигурация отделена от образа
- Agent First Repository — тот же обратный ход для правил в файле инструкций