От продуктового среза к UCP-контракту, который исполняет AI
У продукт-инженера есть наименьший ценный срез — кусок продукта, который решает настоящую проблему пользователя и проверяет гипотезу минимальными силами. Это результат продуктового мышления: вы уже отрезали лишнее, отложили «потом доделаем», оставили одну сквозную линию ценности. Про то, как резать до такого среза, — отдельный разговор в приоритизации наименьшего среза.
Дальше возникает вопрос, который и делает продукт-инженера продукт-инженером: как передать этот срез AI-агенту так, чтобы он собрал именно его, а не что-то похожее.
Это и есть мост. С одной стороны — продукт: проблема, пользователь, ценность. С другой — сборка: агент, который пишет код. Между ними — переход, на котором чаще всего всё и рассыпается.
Почему размытая задача даёт правдоподобное «не то»
Соблазн большой: срез в голове ясный, агент умный — почему не описать задачу в двух фразах и не получить результат?
Потому что «в двух фразах» агент достраивает недостающее сам. И достраивает не из вашего продукта, а из среднего по всему, что видел. Вы говорите «сделай оформление заказа» — а он выбирает, что такое заказ, какие поля обязательны, что происходит при повторном нажатии, куда уходят деньги при сбое. Каждое из этих решений он примет разумно. Но разумно вообще — не разумно для вашего среза.
Результат узнаваемый: приезжает код, который выглядит убедительно, компилируется, даже покрыт тестами — и делает не то. Не грубо не то, а на пару шагов в сторону: границы шире, чем вы хотели; поведение на краю не такое; наружу торчит не тот контракт. Вы это замечаете не на приёмке, а через две недели, когда достраиваете следующий срез поверх.
Причём чем мощнее агент, тем правдоподобнее выходит «не то» — оно всё лучше маскируется под «то». Размытость задачи не компенсируется умом исполнителя; она им усиливается.
Контракт до кодогенерации
Решение — не писать код лучше, а превратить срез в UCP-контракт до того, как агент начал собирать. Контракт — это не спецификация на сто страниц и не техническое задание. Это четыре вещи, зафиксированные словами настолько точно, чтобы по ним можно было и собрать, и потом проверить. Ровно те четыре, из которых состоит Use Case Pattern как единица работы.
1. Границы — что входит и что не входит. Самая недооценённая часть и самая полезная. Вы явно перечисляете, что в срез входит, и — отдельным списком — что в него сознательно не входит. «Оплата картой — да; сохранённые карты, рассрочка, возврат — нет, не в этом срезе». Список «не входит» дороже списка «входит»: именно в него агент по умолчанию залезает, достраивая правдоподобное. Закрытая граница — это то, что отличает срез от «фичи вообще».
2. Сценарий словами пользователя. Один UseCase, описанный так, как его проживает человек, а не как его исполняет система. «Покупатель нажимает «оплатить», видит подтверждение, деньги списываются один раз». Не «контроллер принимает POST и вызывает сервис». Сценарий держит вас и агента на одной странице про смысл, до того как зайдёт речь про механику. Если сценарий не формулируется одной внятной фразой — срез ещё не дорезан.
3. Интерфейс — вход, выход, ошибки, идемпотентность. Здесь срез становится контрактом на стыке. Не реализация — а сигнатура: что операция принимает, что возвращает, какими ошибками отвечает, и что происходит при повторе. Идемпотентность фиксируется именно тут, на уровне контракта, а не «разберёмся в коде»: повторный вызов с тем же ключом даёт тот же результат, а не второе списание. Это граница, которую видят соседние куски продукта, поэтому её нельзя оставлять агенту на усмотрение.
4. Критерии приёмки — проверяемые. Не «работает корректно», а перечень утверждений, каждое из которых можно проверить да/нет. «При повторном вызове с тем же ключом второго списания нет». «При недоступном шлюзе заказ остаётся неоплаченным, пользователь видит понятную ошибку». Критерии — это то, по чему вы примете результат. Если критерий нельзя проверить, это не критерий, а пожелание, и агент его тихо проигнорирует.
Как это выглядит
Контракт короткий. Он вне языка и вне стека — крафт агент знает сам, ваше дело задать рамку:
UseCase: Оплатить заказ
Границы:
входит — оплата картой одного заказа, разовое списание
не входит — сохранённые карты, рассрочка, частичная оплата, возврат
Сценарий:
Покупатель подтверждает оплату заказа в статусе «ожидает оплаты»
и получает подтверждение; деньги списываются ровно один раз.
Интерфейс:
вход — идентификатор заказа, ключ идемпотентности
выход — статус заказа, ссылка на платёж
ошибки — заказ не найден; заказ уже оплачен; шлюз недоступен
идемпотентность — повтор с тем же ключом возвращает первый результат,
повторного списания нет
Критерии приёмки:
1. Успешная оплата переводит заказ в «оплачен» и возвращает ссылку.
2. Повтор с тем же ключом не списывает второй раз.
3. Недоступный шлюз оставляет заказ «ожидает оплаты», ошибка понятна.
4. Оплата уже оплаченного заказа отклоняется, не списывает.
Двадцать строк. Но по ним агент собирает предсказуемо: границы не дают ему расползтись, сценарий держит смысл, интерфейс фиксирует стык, критерии дают чем закончить. И — что важнее — по этим же двадцати строкам вы потом принимаете результат. Контракт до сборки и есть то, обо что проверяется сборка после.
Почему это именно мост, а не отдельный этап
Заманчиво считать написание контракта лишним шагом между «придумал» и «собрал». На деле это не вставка, а перевод. Слева от моста — продуктовый язык: проблема, ценность, гипотеза. Справа — язык сборки: команда, порт, обработка ошибок. Контракт — единственное место, где одно превращается в другое без потери смысла.
Без него переход всё равно происходит — просто внутри агента, молча, по его усмотрению. Вы отдаёте продуктовое решение тому, кто не знает вашего продукта. С контрактом решение остаётся за вами, а агенту достаётся то, что он делает хорошо: собрать по чёткой рамке.
И этот же контракт замыкает цикл на другом конце. Критерии приёмки, которые вы записали до кодогенерации, становятся тем, по чему вы принимаете результат после, — про саму приёмку подробнее в приёмке результата AI. Контракт написан один раз, а работает дважды: как задание на входе и как проверка на выходе.
Коротко
- Наименьший ценный срез — продуктовое решение; отдать его агенту размытой фразой значит отдать это решение ему.
- Размытая задача даёт правдоподобное «не то», и чем мощнее агент, тем убедительнее маскировка.
- Мост — превратить срез в UCP-контракт до кодогенерации: границы (в том числе «не входит»), сценарий словами пользователя, интерфейс (вход-выход-ошибки-идемпотентность), проверяемые критерии приёмки.
- Контракт короткий и вне языка — рамка, не реализация.
- Он работает дважды: как задание агенту на входе и как основание приёмки на выходе.