Программный интерфейс начинается с двух решений: чем аутентифицироваться и какими ресурсами вы оперируете. Поддерживаются оба привычных способа передачи ключа, и выбор между ними чисто технический. Содержательный выбор другой - чей это ключ. Ключ пользователя создаётся в личной панели, ключ служебной учётной записи - в настройках команды. Для автоматизации нужен именно второй: у него отдельный жизненный цикл, он не привязан к человеку, который завтра уйдёт в отпуск или из компании, его не жалко отозвать при первом подозрении, и его действия видны в журналах как машинные, а не как ваши. Есть и прямое ограничение, о котором лучше знать заранее: ключи администратора команды не годятся для запуска агентов через официальные наборы для языков.
Модель ресурсов проще, чем кажется, если один раз развести долгоживущее и разовое. Агент - это долгоживущий контейнер: разговор, конфигурация рабочей области, модель и настройки. Запуск - это один запрос со своим потоком событий, статусом, результатом и возможностью отмены. У одного агента в каждый момент активен только один запуск, и это ограничение стоит заложить в архитектуру сразу, а не обнаруживать по ошибке о занятости. Практически оно означает, что очередь работы вы держите у себя: либо ждёте освобождения агента, либо заводите пул агентов и распределяете задачи между ними.
Полезно один раз свести ресурсы в таблицу: что хранит каждый и как устроен его жизненный цикл. Ниже такая карта - агент, запуск, артефакты, каталог моделей, каталог репозиториев и делегированные токены. Отдельно отмечено то, что чаще всего забывают: каталог моделей нужно узнавать запросом, а не зашивать в код, а каталог репозиториев ограничен по частоте особенно жёстко. Первое спасает от поломки в день, когда идентификатор модели меняется или исчезает, второе - от отказов под нагрузкой.
| Ресурс | Что хранит | Жизненный цикл |
|---|---|---|
| Агент | Разговор, конфигурация рабочей области, модель и настройки | Несколько последовательных запусков; архивация и удаление |
| Запуск | Один запрос, поток событий, статус, результат, отмена | Только один активный запуск на агента |
| Артефакты | Файлы, снимки экрана, записи, журналы | Список и загрузка в пределах агента |
| Каталог моделей |
| Идентификаторы и параметры для вашего аккаунта |
| Узнавать запросом перед закреплением в коде |
| Каталог репозиториев | Доступные подключённые репозитории | Жёсткие ограничения частоты, нужен кэш |
|---|
| Делегированный токен | Ограниченные полномочия | Узкая область и короткий срок жизни |
|---|
Про ограничения частоты стоит сказать отдельно, потому что они проектируют вашу интеграцию за вас. Список репозиториев разрешено запрашивать не чаще раза в минуту и тридцати раз в час на пользователя, и это означает обязательное кэширование на вашей стороне. Интеграция, которая ходит за списком на каждый запрос пользователя, упрётся в лимит в первый же нагруженный день. Правильная форма здесь простая: список обновляется по расписанию или по явной команде, а все остальные обращения читают локальную копию и переживают её устаревание на несколько минут. Такое ограничение честнее считать не помехой, а подсказкой о том, как устроен сервис: редко меняющиеся справочники не рассчитаны на опрос в реальном времени.
В таблице есть ресурс, о котором вспоминают последним, - делегированный токен. Он нужен, чтобы собственный исполнитель работал как активный участник команды: часовой токен от имени пользователя, выпускаемый ключом служебной учётной записи с областью агентов, который сам себя не обновляет и других токенов не выпускает. Смысл в том, чтобы не раздавать основной ключ туда, где достаточно узкой части прав и короткого срока. Короткий срок жизни превращает утечку из постоянного доступа в неприятность с известной датой окончания, а узкая область ограничивает то, что успеют сделать до этой даты. Из двух свойств важнее узкая область: утёкший токен открывает участок работы одного исполнителя, а не всю учётную запись.
Отдельный сюжет - идемпотентность. Сетевой таймаут после отправки запроса на создание не доказывает, что ресурс не создан: запрос мог дойти, а ответ потеряться на обратном пути. Наивный повтор в такой ситуации порождает второго агента, и дальше вы имеете два процесса на одно бизнес-событие: две ветки, два пул-реквеста и двойной счёт за работу. Правильная схема - хранить у себя соответствие между идентификатором события и созданными ресурсами, обрабатывать конфликт как нормальный ответ, а не как ошибку, и повторять с нарастающей задержкой. Ключевое слово здесь бизнес-событие: идемпотентность на уровне запроса не спасает, потому что после потери ответа вы не знаете, какой именно запрос повторяете.
Есть и техническая деталь, помогающая идемпотентности: при создании можно передать собственный идентификатор агента. Тогда повтор с тем же идентификатором не создаёт второй ресурс, а возвращает конфликт, который вы уже умеете обрабатывать. Удобство не бесплатное: этот способ не сочетается с передачей переменных окружения на сессию, то есть выбирать приходится между собственным идентификатором и настройкой окружения из запроса. Такие несовместимости лучше выяснять на этапе проектирования, чем в момент, когда половина интеграции уже написана вокруг одного из двух вариантов.
Стоит знать, как обе описанные беды выглядят в эксплуатации, потому что по симптому их регулярно путают со случайными сбоями. Упор в ограничение частоты проявляется не как ровный отказ, а как плавающая ошибка под нагрузкой: в спокойный день всё работает, в день релиза часть обращений возвращает отказ, и в журналах это читается как нестабильность внешнего сервиса. Нарушенная идемпотентность выглядит иначе: на одно событие появляются два почти одинаковых пул-реквеста с разницей в несколько минут, и разработчик, который их увидел, обычно решает, что кто-то запустил задачу дважды руками. Ни то, ни другое не воспроизводится на машине разработчика, поэтому кэш и дедупликацию проверяют нагрузкой и намеренным обрывом соединения, а не надеждой.
Инженерный вывод простой: программный интерфейс требует тех же решений, что и любая интеграция с внешним сервисом. Отдельные учётные данные с узкой областью, кэширование там, где стоят жёсткие лимиты, идемпотентность на уровне бизнес-события и обработка конфликтов как штатного пути. Всё это пишется один раз и потом экономит недели разбирательств с дублями и лимитами.
Типичные провалы предсказуемы. Использовать личный ключ в общей автоматизации. Зашить идентификаторы моделей вместо запроса каталога. Ходить за списком репозиториев на каждый запрос и упереться в лимит. Раздать основной ключ подсистемам вместо делегированных токенов. И повторять создание после таймаута без сопоставления с бизнес-событием.