RU
Когда потребитель — агент: нормативная модель для сервисов, которых читает машина

Когда потребитель — агент: нормативная модель для сервисов, которых читает машина

2026-08-01

Вот поисковый эндпоинт, вернувший пустой массив. Человек-разработчик смотрит на это и сразу начинает задавать правильные вопросы: запрос кривой, индекс пустой, фильтр слишком узкий, или сервис только что поднялся? Он открывает вторую вкладку. Смотрит дашборд. Спрашивает коллегу.

Агент не делает ничего из этого. Он читает [], заключает «ничего нет» и действует. Если массив был пуст потому, что индекс не достроился, агенту только что уверенно соврал сервис, который по общепринятым меркам не сделал ничего плохого.

Этот разрыв и есть тема поста. Всё дальше следует из одной посылки, и её стоит проговорить до последствий:

Агент задаёт сервису вопрос и действует по ответу немедленно, без присмотра, и не видит того, что этот ответ породило. Значит, ответ обязан нести это в себе: ответ есть утверждение о собственной надёжности, и сервис отвечает за это утверждение не меньше, чем за содержимое.

Я оформил эту посылку в закрытый нормативный корпус из пятнадцати областей, пока строил Hilum Tools, — потому что раз за разом выводил одни и те же правила заново в каждой подсистеме и каждый раз получал их чуть по-разному. Ниже — то, что переносится.


Почему обычных контрактов не хватает

REST, GraphQL и типизированный RPC описывают форму. Они сообщают вызывающей стороне, что ответ — массив объектов с такими-то полями. И ничего не говорят о том, полон ли ответ, насколько стары данные под ним, состоялся ли вообще поиск и насколько сервис уверен, что это правильный ответ.

Для человека это упущение переносимо, потому что недостающее суждение поставляет он сам. Он знает, что деплой был час назад. Он замечает, что результатов подозрительно мало. У него есть контекст, которого протокол никогда не нёс.

У агента такого бокового канала нет. Всё, чего ответ не сказал, агент достроит самым правдоподобным допущением — а «самое правдоподобное» это ровно тот станок, на котором в масштабе производится уверенная неправота. Суждение обязан нести протокол, потому что поставлять его больше некому.

Пять правил, которые следуют из посылки

Полный корпус длиннее, но почти всё практическое выводится отсюда.

1. Пустота должна быть типизирована

[] — самый опасный ответ в машинном API, потому что его порождают минимум четыре разные ситуации:

  • Поиск прошёл, корпус здоров, ничего не совпало. Настоящее отсутствие.
  • Поиск прошёл по индексу, который пуст или ещё строится. Неизвестность, а не отсутствие.
  • Поиск не состоялся — бэкенд был недоступен, ошибку проглотили выше. Отказ, переодетый отсутствием.
  • Фильтр отсёк всё до того, как поиск случился. Отсутствие, но вашего собственного производства.

Человек различает это интуицией и расследованием. Агенту нужно, чтобы различие лежало в полезной нагрузке. Каждый пустой результат должен нести, какой из четырёх это случай. Это небольшое изменение типа ответа, и оно убирает целый класс уверенных ошибок.

2. Каждый ответ заявляет своё покрытие и свой возраст

Два поля — и это разница между ответом, вокруг которого агент может построить план, и ответом, которому он может только слепо верить.

Покрытие — какая доля предполагаемой вселенной была реально просмотрена. «Я обошёл всё дерево» и «я обошёл одиннадцать файлов, которые успели проиндексироваться до падения» — радикально разные ответы одинаковой формы.

Возраст — насколько устарел материал под ответом. Не отметка времени ответа, а отметка времени данных. Индекс, переставший обновляться сорок минут назад и отвечающий с полной уверенностью, хуже того, который об этом говорит, — потому что второй позволяет вызывающей стороне решить, важны ли сорок минут для этого вопроса.

3. «Нет» и «не знаю» — разные ответы

Их склейка — самая частая ошибка в этой категории и с худшими последствиями, потому что эти два ответа требуют от вызывающей стороны противоположного. «Нет» значит перестать искать здесь. «Не знаю» значит искать в другом месте, или спросить позже, или эскалировать.

Дайте им отдельные представления. И сделайте путь «не знаю» дешёвым в производстве и дешёвым в чтении: если сервису тяжело признать неопределённость, под нагрузкой он тихо перестанет её признавать — ровно тогда, когда она нужнее всего.

4. Каждый ответ заявляет, во что обошёлся и что оставил за бортом

Агент работает под бюджетом, которого изнутри вашего сервиса не видно. Если ответ — это отбор (обрезан лимитом, ограничен потолком по токенам, просэмплирован), ответ обязан об этом сказать и примерно назвать, что осталось снаружи.

«Ещё три совпали, но не поместились» — факт, с которым агент может что-то сделать: поднять бюджет, сузить вопрос или отметить неполноту в собственном выводе. Молчаливое обрезание читается как полнота, а агент, уверенный, что видит всю картину, перестаёт искать.

5. Отказы должны быть действенными

Когда сервис отказывает — лимит запросов, права, кривой ввод, неподдерживаемая операция, — ответ должен сказать, что сделало бы его успешным. Не таксономию ошибок, которую человек посмотрит в документации, куда агент не пойдёт, — а корректирующее действие, прямо в полезной нагрузке.

Человек упирается в 403 и спрашивает коллегу. Агент упирается в 403 и либо ретраит тот же вызов, либо бросает достижимую задачу, либо изобретает обходной путь. Все три хуже, чем услышать «нужен скоуп write:memory, которого у этого токена нет».


Почему модель не называет технологий

Корпус, который я написал, намеренно не упоминает ни языка, ни движка хранения, ни фреймворка, ни продукта. Пятнадцать областей — контракт ответа, стоимость ответа, происхождение, наблюдаемость, вердикты, доступ, координация, федерация, дистрибуция и остальные — каждая сформулирована как то, что должно выполняться, и никогда как то, чем это строить.

Ограничение не стилистическое. Именно оно делает модель поднимаемой в проект, собранный из совершенно другой механики. Правило «возвращайте RetrievalEnvelope с полем coverage» — совет про одну кодовую базу. Правило «ответ заявляет, какую долю предполагаемой вселенной он просмотрел» — правило, которое вы унесёте в питоновый сервис, в Go-шлюз или в API, который писали не вы.

Такая переносимость распадается в тот момент, когда её перестают проверять, поэтому она держится механически: сборочный гард сканирует корпус на технологические существительные и падает, когда находит.

Как это внедрить, не написав пятнадцать документов

Начинайте с посылки, а не с корпуса. Один абзац, записанный, о том, что ваш потребитель может и чего не может сделать с вашими ответами. Дальше вперёд:

  1. Проведите ревизию пустых и ошибочных ответов. По каждому спросите, что заключит из него потребитель без присмотра и оправдан ли этот вывод. Одно это обычно вскрывает несколько настоящих багов.
  2. Добавьте покрытие и возраст в самый нагруженный эндпоинт. Один эндпоинт, два поля. Померьте, улучшилось ли поведение ниже по течению, прежде чем катить дальше.
  3. Разделите «нет» и «неизвестно» везде, где это различие существует в реальности.
  4. Запишите посылку и вытекающие из неё правила своими словами, не называя технологий. Держите короткими настолько, чтобы их читали.
  5. Обеспечьте механически то, что можно — проверкой схемы, правилом линтера, тестом на то, что конверт ответа несёт свои метаданные.

Смысл не в том, чтобы произвести документацию. Смысл в том, что как только посылка записана, споры об отдельных эндпоинтах перестают быть делом вкуса. Кто-то предлагает вернуть голый массив, кто-то показывает на посылку — и разговор занимает тридцать секунд вместо дизайн-ревью.


Выводы

  • Неспособность потребителя заглянуть за ответ — это проектное ограничение, а не деталь.
  • Контракты формы описывают полезную нагрузку; машинным сервисам нужно описывать ещё и надёжность ответа.
  • Типизируйте пустоту. [] порождают четыре разные ситуации, требующие разной реакции.
  • Каждый ответ несёт покрытие и возраст данных, а не только отметку времени.
  • «Нет» и «не знаю» — разные ответы с противоположными следствиями для вызывающей стороны.
  • Заявляйте, что пропущено и во что ответ обошёлся: молчаливое обрезание читается как полнота.
  • Отказ несёт корректирующее действие, а не только код.
  • Пишите правила, не называя технологий, и обеспечивайте это механически — иначе за квартал они перестанут быть переносимыми.

Конкретное воплощение — выборка, которая сама сообщает свой бюджет и возраст индекса, — это выборка под бюджет токенов. То же самое применительно к вашей архитектурной летописи — граф решений. Если нужен второй взгляд на то, как ваши сервисы поведут себя, когда потребителями станут агенты, — для этого есть консультация.

Связаться

Прямая связь с инженером — Telegram, Email, Calendly или структурированный бриф.

Бесплатный 30-минутный звонок — без обязательств, без агентской воронки.