Перейти к содержанию

Глава 8. Модель выполнения и каталог инструментов

1. Начнем с того же кейса поддержки, но уже на пути записи

Продолжим тот же сценарий из первых глав.

Пользователь пишет:

Я уже третий день жду активации доступа. Проверьте статус и создайте срочный тикет, если заявка застряла.

На первый взгляд задача кажется простой:

  • агент читает сообщение;
  • вызывает инструмент проверки статуса;
  • если заявка действительно застряла, вызывает инструмент создания тикета;
  • возвращает ответ.

На демо этого почти достаточно. В эксплуатации именно здесь и начинаются самые дорогие ошибки.

Потому что теперь вопрос уже не только в том, что модель захотела сделать. Вопрос в другом:

  • какой инструмент ей вообще разрешено вызвать;
  • в какой области арендатора это допустимо;
  • какие аргументы считаются валидными;
  • где отделяются операции чтения и записи;
  • что делать, если внешний сервис завис после побочного эффекта;
  • как потом доказать, был ли тикет создан один раз или дважды.

Именно поэтому вызов инструментов нужно проектировать не как функцию при модели, а как слой выполнения платформы.

2. Агент не должен ходить в инструменты напрямую

Одна из самых полезных инженерных привычек здесь очень скучная: агент никогда не должен получать прямой доступ к реальным интеграциям.

Вместо этого нужен слой выполнения, который:

  • знает каталог доступных инструментов;
  • валидирует входные аргументы;
  • навешивает проверки политик;
  • отделяет операции чтения и записи;
  • управляет повторами, тайм-аутами и идемпотентностью;
  • пишет события аудита.

Для того же кейса поддержки это означает, что модель не должна сама ходить в API helpdesk или IAM-сервис. Она должна разговаривать только со слоем выполнения.

Сквозной кейс: контроль дубля тикета

Именно в кейсе сортировки обращений поддержки слой выполнения становится конкретным. check_access_request_status — ограниченное чтение, а create_support_ticket — управляемая операция записи с подтверждением, идемпотентностью, обработкой тайм-аутов и телеметрией исхода. Если helpdesk API отвечает тайм-аутом после создания тикета, рантайм не должен позволить модели просто попробовать еще раз; ему нужен путь сверки, который докажет, произошел ли побочный эффект уже.

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

3. Как один запрос проходит через слой выполнения

Посмотрим на тот же сценарий уже как на путь выполнения.

3.1. Сначала модель предлагает инструмент чтения

Чтобы понять, застряла ли заявка, агенту нужен инструмент проверки статуса. Это путь чтения:

  • он не должен менять внешний мир;
  • ему нужна корректная область арендатора;
  • он должен вернуть понятный структурированный результат.

3.2. Потом система решает, разрешен ли инструмент записи

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

  • здесь появляется побочный эффект;
  • может потребоваться подтверждение;
  • нужен ключ идемпотентности;
  • нужен более строгий журнал аудита.

3.3. Потом слой выполнения берет на себя неприятную реальность

Именно здесь появляются сценарии, о которых редко думают на демо:

  • helpdesk ответил тайм-аутом после создания тикета;
  • инструмент вернул частичный успех;
  • модель повторила вызов после повтора;
  • внешний сервис вернул неожиданную полезную нагрузку;
  • у рантайма нет уверенности, произошел ли побочный эффект.

Это уже не "вызов инструментов". Это полноценная дисциплина выполнения.

Модель должна разговаривать не с внешним миром напрямую, а со слоем выполнения

flowchart LR
    A["Подсказка + контекст политики"] --> B["Модель"]
    B --> C["Запрос инструмента"]
    C --> D["Слой выполнения"]
    D --> E["Поиск в каталоге"]
    D --> F["Политика / валидация"]
    D --> G["Повтор / тайм-аут / идемпотентность"]
    G --> H["Внешняя система"]
    H --> D
    D --> I["Структурированный результат инструмента"]
    I --> B

4. Каталог инструментов это интерфейс платформы, а не список случайных функций

Если смотреть на каталог как на "папку с вызовами", он быстро превращается в свалку интеграций. Гораздо полезнее считать его публичным интерфейсом слоя выполнения.

Для нашего сценария поддержки в каталоге полезно явно видеть, что именно умеет агент:

  • check_access_request_status
  • get_user_profile
  • create_support_ticket
  • request_human_approval

У хорошего каталога обычно есть:

  • стабильное имя инструмента;
  • описание назначения;
  • схема входных аргументов;
  • класс риска;
  • уровень побочного эффекта;
  • разрешенные вызывающие стороны или возможности;
  • тайм-аут, политика повторов и ожидания идемпотентности.

Это делает слой выполнения обозримым: команда видит не "что-то там может вызвать модель", а конкретный контракт платформы.

4.1. Слишком большой каталог инструментов ухудшает выбор, а не расширяет свободу

Еще одна очень практичная проблема появляется в тот момент, когда каталог становится слишком большим.

Чем больше инструментов одновременно видит модель:

  • тем больше токенов уходит на их описания;
  • тем длиннее становятся похожие друг на друга контракты;
  • тем труднее различать почти одинаковые возможности;
  • тем сильнее размывается внимание на этапе выбора.

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

Хороший практический паттерн здесь обычно называется семантическая фильтрация инструментов:

  • полный реестр продолжает жить в слое платформы;
  • но конкретному запуску показывают только узкий релевантный поднабор;
  • часто это 3–5 инструментов, а не несколько десятков.

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

И еще одно полезное практическое правило: повторы не лечат плохой выбор инструмента, если сама модель изначально видела слишком шумный каталог.

5. Важно различать инструменты чтения и инструменты записи

Это кажется очевидным, но на практике многие системы описывают их почти одинаково. А зря.

Для того же агента поддержки check_access_request_status и create_support_ticket не просто два инструмента. Это два разных класса риска.

read tools обычно:

  • менее опасны;
  • чаще могут вызываться автоматически;
  • полезны для обоснования и извлечения;
  • требуют контроля доступа, но не всегда требуют подтверждения.

write tools обычно:

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

Если операции чтения и записи смешиваются в одну неявную категорию "вызов инструмента", слой выполнения быстро теряет управляемость.

5.1. Еще одна полезная таксономия: data, action, orchestration

В практическом гайде OpenAI есть еще одно полезное упрощение: инструменты удобно делить не только на read и write, но и по их роли в системе.2

  • data tools читают и возвращают контекст: проверка статуса, извлечение, чтение CRM;
  • action tools меняют внешний мир: создать тикет, отправить письмо, обновить запись;
  • orchestration tools помогают самому рантайму: запросить подтверждение, сделать передачу, вызвать планировщик.

Эти две оси хорошо работают вместе:

  • data tools почти всегда ближе к read;
  • action tools почти всегда ближе к write;
  • orchestration tools могут быть и тем, и другим, но у них отдельный эксплуатационный смысл.

Таксономия паттернов рабочих процессов у Anthropic добавляет сюда еще одну полезную дисциплину.1 Каталог должен не только сообщать модели, какие инструменты вообще существуют. Он еще должен делать явным, в каких паттернах оркестрации эти инструменты безопасно участвовать.

Например:

  • data tool может быть безопасен внутри routing, prompt chaining или parallelization;
  • write action tool может быть допустим только после прерывания на подтверждение или внутри жестко ограниченного рабочего процесса;
  • orchestration tool вроде request_human_approval или handoff_to_specialist меняет сам граф выполнения и потому требует более строгих правил трассировки и владения;
  • паттерн orchestrator-workers может требовать явного безопасного для воркера поднабора каталога, а не всей родительской поверхности инструментов.

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

6. Контракт инструмента должен быть скучным и строгим

Одна из худших привычек в агентных системах: позволять модели импровизировать формат вызова.

В хорошем дизайне у инструмента есть контракт:

  • четкие обязательные поля;
  • понятные enum и ограничения;
  • нормальные сообщения об ошибках;
  • явный формат ответа;
  • предсказуемое поведение при тайм-ауте или дубликате запроса.

Для нашего кейса поддержки это может выглядеть так:

tools:
  check_access_request_status:
    description: "Read the current status of an access request"
    kind: "read"
    risk: "low"
    timeout_seconds: 10
    input_schema:
      required: ["request_id", "tenant_id"]
      properties:
        request_id: {type: string}
        tenant_id: {type: string}

  create_support_ticket:
    description: "Create a support ticket in the internal helpdesk"
    kind: "write"
    risk: "high"
    idempotent: true
    timeout_seconds: 15
    input_schema:
      required: ["title", "queue", "requester_id", "tenant_id", "idempotency_key"]
      properties:
        title: {type: string, maxLength: 200}
        queue: {type: string, enum: ["support", "security", "ops"]}
        requester_id: {type: string}
        tenant_id: {type: string}
        idempotency_key: {type: string}
        description: {type: string}

Это выглядит прозаично. И это хорошо. Чем меньше магии в контрактном слое, тем устойчивее инструментальная часть системы.

7. Слой выполнения должен нормализовать ошибки

Еще один частый провал: каждый внешний сервис возвращает свои ошибки в своем стиле, а агенту пробрасывают это почти без обработки.

Для того же кейса поддержки это легко превращается в хаос:

  • IAM-сервис вернул HTTP 500;
  • helpdesk ответил "created": true, но не прислал ticket_id;
  • старый интеграционный адаптер вернул HTML;
  • тайм-аут случился уже после побочного эффекта;
  • нижестоящий API ответил пустым телом.

Слой выполнения должен превращать это в нормальные типы исходов:

  • success
  • retryable_failure
  • validation_failure
  • permission_denied
  • side_effect_unknown

Это резко повышает объяснимость и позволяет агенту принимать более взрослые решения: повторить, запросить подтверждение, эскалировать человеку или безопасно остановиться.

8. Идемпотентность и повторы нельзя додумывать потом

Почти каждая реальная интеграция рано или поздно дает хотя бы один неприятный сценарий:

  • тайм-аут после того, как побочный эффект уже случился;
  • дубль вызова после повтора;
  • частичный успех;
  • состояние гонки между двумя запусками;
  • внешний сервис ответил позже ожидаемого окна.

Если идемпотентность не заложена в модель выполнения, агент очень быстро начинает делать то, что в обычных системах и так больно чинить: повторно создавать тикеты, дублировать письма, несколько раз менять один и тот же объект.

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

9. Практические правила для слоя выполнения

Если нужен короткий набор правил, который реально помогает в эксплуатации, он обычно такой:

  1. У каждого инструмента должны быть владелец, схема, класс риска и жизненный цикл контракта.
  2. Пути чтения и записи нужно различать до первого запуска, а не после первого инцидента.
  3. Все инструменты записи должны иметь стратегию идемпотентности до того, как их увидит реальный трафик.
  4. Все исходы инструментов нужно нормализовать до передачи обратно модели.
  5. Если побочный эффект уже мог произойти, безопаснее остановиться, чем делать слепой повтор.

10. Что команды чаще всего делают неправильно

Слой выполнения почти всегда ломают одинаково:

  • дают модели прямой доступ к внешнему API;
  • смешивают чтение и запись в одну безликую категорию;
  • позволяют инструментам утекать между паттернами оркестрации без явного ответа, где вообще разрешены маршрутизация, параллелизация, прерывания на подтверждение и делегирование воркерам;
  • прячут повторы глубоко в адаптере без журнала аудита и идемпотентности;
  • возвращают модели сырую полезную нагрузку вместо нормализованных результатов;
  • не назначают владельца и политику вывода из эксплуатации для слоя каталога.

11. Простой кодовый шаблон для слоя выполнения

Ниже не эксплуатационный рантайм, а каркас, который показывает правильное разделение ответственности: поиск, валидацию, выполнение и нормализацию результата.

from dataclasses import dataclass


@dataclass
class ToolSpec:
    name: str
    kind: str
    timeout_seconds: int
    idempotent: bool


@dataclass
class ToolResult:
    status: str
    payload: dict


def execute_tool(spec: ToolSpec, args: dict) -> ToolResult:
    if spec.kind not in {"read", "write"}:
        return ToolResult(status="validation_failure", payload={"reason": "unknown tool kind"})

    if spec.kind == "write" and "idempotency_key" not in args:
        return ToolResult(
            status="validation_failure",
            payload={"reason": "missing idempotency key"},
        )

    # In production this call would go through policy checks, a gateway, and typed adapters.
    return ToolResult(status="success", payload={"tool": spec.name})

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

12. Результаты инструментов тоже нужно проектировать, а не просто возвращать как есть

Если результат инструмента слишком сырой, у модели снова появляется пространство для опасной импровизации.

Хороший результат:

  • краткий;
  • структурированный;
  • не тащит лишний технический шум;
  • содержит машиночитаемый статус;
  • не прячет неопределенность.

Плохой результат:

  • возвращает всю внешнюю полезную нагрузку простыней;
  • смешивает пользовательский текст и системные детали;
  • не различает "ничего не найдено" и "система упала";
  • не говорит, произошел ли побочный эффект.

13. Каталог инструментов должен эволюционировать медленно

Если инструменты меняются каждый день без совместимости и версионирования, агентная система начинает вести себя как клиент на нестабильном частном API.

Поэтому у слоя каталога полезны такие привычки:

  • версионированные контракты;
  • политика вывода из эксплуатации;
  • владелец у каждого инструмента;
  • тесты на схему и форму результата;
  • разбор возможностей перед добавлением новых инструментов записи.

Это скучная платформа, а не романтическая импровизация. Именно поэтому она работает.

14. Быстрый тест зрелости для инструментального слоя

Команде не стоит считать слой выполнения зрелым только потому, что инструменты уже можно вызывать.

Более сильный стандарт такой:

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

Если одного-двух пунктов не хватает, система еще может работать. Если не хватает большинства, инструментальный слой пока остается только прототипной обвязкой.

15. Практикум: ревью capability contract перед выдачей инструмента модели

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

Capability contract review отвечает на простой вопрос: что именно мы готовы показать модели как доступную возможность в данном запуске? Не вообще в платформе, не в полном внутреннем реестре, а именно в этом сценарии, с этим пользователем, tenant, policy snapshot и уровнем риска.

Если такого review нет, команда обычно узнает о слабом контракте слишком поздно: модель увидела слишком широкий каталог, выбрала похожий, но неверный инструмент, передала неполные аргументы, повторила write-вызов после тайм-аута или получила доступ к действию, которое должно было требовать подтверждения.

Практическая цель этого раздела - дать форму, по которой capability можно проверить до того, как она попадет в prompt или tool selection layer.

Шаг 1. Начать с карточки capability, а не с функции

Функция в коде отвечает на вопрос: как вызвать интеграцию. Capability отвечает на другой вопрос: какое действие платформа разрешает агенту выполнить и при каких условиях.

Минимальная карточка capability может выглядеть так:

capability:
  name: create_ticket
  owner: support_platform
  mode: write
  transport: gateway
  tool_principal: svc-ticket-writer
  risk_tier: high
  network_access: brokered
  allowed_egress:
    - tickets.internal
  approval: manager
  idempotency_key_required: true
  timeout_seconds: 15
  lifecycle_status: approved

Такая карточка сразу делает видимыми несколько вещей. У возможности есть владелец. Она является write-действием, а не чтением. Она ходит через gateway, а не напрямую из модели. У нее есть отдельный tool principal. Она требует manager approval и idempotency key. Ее сетевой контур ограничен tickets.internal.

Это уже не просто функция create_ticket. Это управляемое полномочие.

Шаг 2. Проверить, кто является владельцем и кто несет операционную ответственность

Если у capability нет owner, ее нельзя безопасно выпускать. Без владельца непонятно:

  • кто утверждает изменение схемы;
  • кто отвечает за инцидент;
  • кто решает, можно ли повысить risk tier;
  • кто выключает capability при деградации;
  • кто принимает решение о replacement или retirement.

Для support triage owner может быть support_platform. Для knowledge assistant owner может быть knowledge_platform. Для incident coordination owner часто должен быть ближе к on-call или incident platform, потому что уведомления и эскалации напрямую влияют на работу реагирования.

Практическое правило: если команда не может назвать владельца capability за 30 секунд, модель не должна видеть эту capability в production-каталоге.

Шаг 3. Развести read, write и orchestration до выдачи модели

Глава уже показала различие между read tools и write tools. В review это различие нужно сделать механическим.

read_capability:
  name: search_docs
  mode: read
  risk_tier: low
  approval: none
  network_access: restricted
  allowed_egress:
    - docs.internal

write_capability:
  name: create_ticket
  mode: write
  risk_tier: high
  approval: manager
  idempotency_key_required: true
  network_access: brokered
  allowed_egress:
    - tickets.internal

orchestration_capability:
  name: request_human_approval
  mode: orchestration
  risk_tier: medium
  approval: none
  creates_control_state: true

Это не косметика. Если capability меняет внешний мир, она не должна проходить тот же путь, что и чтение. Если capability меняет граф выполнения, например создает approval request или handoff, она тоже не является обычным tool call. У нее свой след, свой владелец и свои failure modes.

Шаг 4. Проверить schema surface и запретить импровизацию аргументов

Контракт capability должен описывать не только название, но и форму входа. Минимальный review проверяет:

  • все обязательные поля названы явно;
  • tenant_id и principal_id не выводятся моделью из текста, а приходят из runtime context;
  • enum ограничивает допустимые очереди, режимы и типы действий;
  • строки имеют maxLength там, где они уходят во внешнюю систему;
  • idempotency_key обязателен для write-действий;
  • sensitive fields не показываются модели, если они не нужны для выбора действия.

Пример для create_ticket:

input_schema:
  required:
    - title
    - queue
    - requester_id
    - tenant_id
    - idempotency_key
  properties:
    title:
      type: string
      maxLength: 200
    queue:
      type: string
      enum:
        - support
        - security
        - ops
    requester_id:
      type: string
    tenant_id:
      type: string
      source: runtime_context
    idempotency_key:
      type: string
      source: runtime_generated
    description:
      type: string
      maxLength: 4000

Важное правило: модель может предложить смысл действия, но не должна сама придумывать tenant_id, tool_principal или idempotency_key. Эти значения должны приходить из контролируемого слоя выполнения.

Шаг 5. Привязать capability к policy decision

Каталог инструментов без policy layer быстро становится справочником опасных возможностей. Для каждой capability нужно заранее знать решение политики.

policy:
  run_precheck:
    require_tenant: true
    deny_if_principal_missing: true
  capabilities:
    search_docs:
      decision: allow
    create_ticket:
      decision: approval_required
      approver: manager
    run_shell:
      decision: deny

Такой policy snapshot помогает не спорить о разрешении в момент вызова. Runtime сначала проверяет tenant и principal, потом смотрит capability decision, и только затем показывает модели допустимый поднабор инструментов или переводит действие в approval flow.

Зрелая система различает как минимум четыре решения:

  • allow: можно вызвать автоматически при валидных аргументах;
  • approval_required: нужен человеческий шлюз;
  • deny: capability недоступна в этом сценарии;
  • hidden: capability не показывается модели, потому что она не относится к текущему workflow.

Последний статус особенно важен. Опасный инструмент лучше не просто запретить на позднем этапе, а не включать в tool selection set, если он не нужен текущей задаче.

Шаг 6. Проверить approval gate до write-действия

Если policy говорит approval_required, capability review должен проверить не только факт подтверждения, но и форму запроса.

approval_request:
  approval_id: apr-2026-04-07-001
  trace_id: trace-support-001
  session_id: session-support-001
  tenant_id: tenant-acme
  principal_id: user-42
  capability: create_ticket
  risk_tier: high
  required_role: manager
  requested_fields:
    title: "Access request blocked for three days"
    queue: support
    requester_id: user-42
    idempotency_key: ticket-req-2026-04-07-001
  status: pending

Человек должен видеть ровно те поля, которые потом уйдут в действие. Если после подтверждения runtime меняет queue, requester_id или description без новой проверки, approval уже не соответствует исполнению.

Для user-delegated сценариев также важны principal binding и scope visibility. Если делегированная область отозвана, действие должно отменяться или требовать повторного подтверждения, а не продолжать выполнение по старому approval.

Шаг 7. Проверить idempotency и side_effect_unknown

Для write capability недостаточно сказать idempotent: true. Review должен проверить, где именно живет ключ и что происходит при неопределенности.

Минимальный контракт:

idempotency:
  required: true
  key_source: runtime_generated
  key_scope: tenant_plus_capability_plus_request
  replay_policy: reconcile_before_retry
  on_timeout_after_possible_side_effect: side_effect_unknown
  on_side_effect_unknown: stop_or_reconcile

Это место отделяет production-систему от демо. Если helpdesk API ответил тайм-аутом, но тикет мог быть создан, модель не должна просто повторить create_ticket. Runtime должен сначала сверить состояние по idempotency key или correlation id. Если сверка невозможна, безопасный исход - остановка или ручная проверка.

Шаг 8. Нормализовать результаты capability

Capability contract должен описывать не только вход, но и выход. Иначе модель получит сырой ответ внешнего API и начнет додумывать состояние.

Хороший output contract:

output_schema:
  status:
    enum:
      - success
      - validation_failure
      - permission_denied
      - approval_required
      - retryable_failure
      - side_effect_unknown
  fields:
    ticket_id: optional_string
    external_status: optional_string
    failure_reason: optional_string
    retry_after_seconds: optional_integer
    audit_ref: required_string

Runtime должен возвращать модели не весь ответ helpdesk, а нормализованный результат. Если status равен side_effect_unknown, модель не должна формулировать пользователю уверенное "тикет создан". Она должна перейти в безопасную ветку: сверка, эскалация или ожидание человека.

Шаг 9. Собрать tool selection set для конкретного запуска

Полный каталог может содержать десятки или сотни capabilities. Модель не должна видеть их все. Для каждого запуска runtime должен собрать узкий tool selection set.

Пример:

tool_selection_set:
  trace_id: trace-support-001
  workflow: support_triage
  tenant_id: tenant-acme
  principal_id: user-42
  visible_capabilities:
    - check_access_request_status
    - get_user_profile
    - request_human_approval
    - create_ticket
  hidden_capabilities:
    - run_shell
    - execute_remediation
    - notify_customer_directly
  reasons:
    create_ticket: visible_but_approval_required
    run_shell: policy_denied
    execute_remediation: outside_workflow
    notify_customer_directly: requires_incident_workflow

Это снижает риск неверного выбора и делает catalog exposure наблюдаемым. Если во время incident review выясняется, что модель видела опасный инструмент вне workflow, это уже дефект runtime assembly, а не загадочная ошибка модели.

Шаг 10. Привязать capability к trace и audit trail

Минимальная трасса должна показывать цепочку от выбора инструмента до результата:

events:
  - kind: tool_selection_set_built
    trace_id: trace-support-001
    visible_capabilities:
      - check_access_request_status
      - create_ticket
    hidden_capabilities_count: 3
  - kind: capability_policy_decision
    capability: create_ticket
    decision: approval_required
    approver: manager
  - kind: approval_requested
    approval_id: apr-2026-04-07-001
    capability: create_ticket
    idempotency_key: ticket-req-2026-04-07-001
  - kind: tool_called
    capability: create_ticket
    tool_principal: svc-ticket-writer
    idempotency_key: ticket-req-2026-04-07-001
  - kind: tool_result_normalized
    status: success
    audit_ref: audit-ticket-2026-04-07-001

Для отказов цепочка так же важна. validation_failure, permission_denied и side_effect_unknown должны быть не строками в логах, а нормальными outcome-событиями.

Шаг 11. Прогнать capability через три канонических сценария

Для support triage:

  • create_ticket должен быть write capability с approval и idempotency key;
  • check_access_request_status должен быть read capability с tenant scope;
  • side_effect_unknown должен останавливать слепой retry;
  • approval должен видеть те же поля, которые уйдут в tool call.

Для internal knowledge assistant:

  • search_docs должен быть read capability с role-scoped и tenant-scoped retrieval;
  • скрытая запись в память не должна появляться через search capability;
  • результат поиска должен возвращать source labels, а не только текст;
  • write capabilities обычно скрыты из tool selection set.

Для incident coordination:

  • notify_team и create_incident_thread являются write capabilities;
  • execute_remediation должен быть disabled_by_default или approval_required;
  • handoff capability должна фиксировать текущего владельца;
  • notification side effects должны иметь idempotency и audit_ref.

Минимальный checklist для capability contract review

Перед тем как capability попадет в tool selection set, нужно ответить на вопросы:

  • Есть ли owner и lifecycle_status?
  • Ясно ли, это read, write или orchestration capability?
  • Есть ли risk_tier и policy decision?
  • Откуда берутся tenant_id, principal_id, tool_principal и idempotency_key?
  • Может ли модель увидеть только нужный поднабор capabilities?
  • Требуется ли approval и кто имеет право подтверждать?
  • Видит ли подтверждающий ровно те поля, которые будут исполнены?
  • Что происходит при timeout после возможного side effect?
  • Нормализован ли output до success, validation_failure, permission_denied, approval_required, retryable_failure или side_effect_unknown?
  • Есть ли trace events для selection, policy decision, approval, tool call и normalized result?

Что должно измениться в реализации после такого ревью

После такого review команда обычно получает конкретный backlog:

  • перевести tool catalog из списка функций в capability registry;
  • добавить owner, mode, risk_tier, transport, tool_principal, network_access и allowed_egress;
  • собирать tool selection set на каждый запуск, а не показывать полный каталог;
  • генерировать idempotency_key в runtime, а не в модели;
  • вынести policy decision до tool call;
  • связать approval request с конкретными requested_fields;
  • нормализовать результаты инструментов;
  • добавить trace events для capability path;
  • включить side_effect_unknown в eval-набор и rollout gate.

Главная мысль проста: модель может предложить действие, но capability contract решает, существует ли такое действие как управляемое полномочие в данном запуске. Если контракт не выдерживает review, инструмент не должен попадать в доступный набор модели.

16. Что делать сразу после этой главы

Если хочешь быстро проверить свой слой выполнения, пройди по короткому списку:

  1. Есть ли у тебя отдельный каталог инструментов, а не просто набор функций?
  2. Разделены ли инструменты чтения и записи?
  3. Есть ли валидация схемы для аргументов?
  4. Нормализуются ли ошибки внешних систем?
  5. Учтены ли тайм-ауты, повторы и идемпотентность?
  6. Видно ли, произошел побочный эффект или нет?
  7. Есть ли владелец и жизненный цикл контракта у каждого инструмента?

Если на несколько пунктов подряд ответ "нет", агент у тебя уже умеет вызывать инструменты, но модель выполнения пока еще незрелая.

Шаблон завершения главы

Что запомнить: выполнение агента должно быть описано как каталог возможностей, а не как набор произвольных вызовов инструментов.

Типичные ошибки: давать модели свободный список инструментов; не отделять чтение от записи; не фиксировать владельца, риск и контракт каждой возможности.

Что проверить в своей системе: есть ли у каждой возможности владелец, риск, входной контракт, политика подтверждения и событие трассировки.

Сопутствующие материалы: свяжи каталог возможностей со схемой подтверждений, схемой трассировки и операционными карточками канонических сценариев.

Что читать дальше: переходи к Главе 9, чтобы понять, как изолировать выполнение и сделать MCP границей доверия.

17. Что читать дальше

Следующие естественные темы в этой части: выполнение в песочнице, MCP как контракт интеграции и правила для повторов и границ отката. Именно там становится видно, как тот же агент поддержки не просто вызывает инструменты, а делает это через зрелый слой выполнения.