Актуальная версия интерфейса облачных агентов находится в открытой бете и построена на разделении долгоживущего агента и отдельных запусков. Создание агента ставит первый запуск в очередь сразу; последующие обращения добавляют новые запуски к тому же агенту. Попытка запустить второй параллельный запуск у того же агента возвращает конфликт с понятной причиной - агент занят. Открытая бета здесь не формальность: подробности могут меняться, поэтому интеграцию стоит строить так, чтобы форма ответов была изолирована в одном слое, а не размазана по всему коду.
Это разделение стоит принять как архитектурное решение, а не как деталь. Оно означает, что состояние разговора живёт дольше отдельного запроса, и что очередь работы вы строите на своей стороне. Интеграция, спроектированная в предположении запусти и забудь, рано или поздно упрётся в конфликт занятости и должна будет решать, что делать: ждать, ставить в очередь или создавать нового агента. Решение это не техническое, а смысловое - продолжение разговора и новая задача с чистого листа дают разный результат, и выбирать между ними надо осознанно.
Полезно один раз увидеть создание агента целиком. Ниже - запрос с текстом задачи, списком репозиториев с начальной ссылкой, режимом работы и явным отказом от автоматического создания пул-реквеста. Обратите внимание на последний параметр: решение о том, создавать ли пул-реквест, лучше принимать осознанно, а не получать по умолчанию. Начальная ссылка так же важна: она определяет, от какого состояния кода агент отталкивается, и запуск от устаревшей ветки даёт правдоподобное изменение, конфликтующее со всем, что произошло с тех пор.
У создания есть документированные пределы, и знать их полезно до проектирования: ограничено число репозиториев, число и размер изображений, число собственных ролей и число внешних серверов, описанных прямо в запросе. Эти цифры не мелочь - они задают, что вообще можно уместить в один запуск, и подсказывают, когда задачу пора делить. Упереться в них обычно означает не нехватку возможностей платформы, а слишком широкую постановку: если задаче нужны два десятка репозиториев одновременно, проблема не в лимите.
Отдельный параметр решает, где окажутся изменения. По умолчанию агент создаёт новую ветку с понятным префиксом. Включённый режим работы в текущей ветке отправляет изменения прямо в исходную ссылку или в голову пул-реквеста - и это как раз тот случай, когда защита веток на стороне провайдера должна быть особенно строгой. Автономный процесс, пишущий прямо в рабочую ветку, требует ровно тех же гарантий, что и человек, только он не остановится сам и не задумается о том, что кто-то другой прямо сейчас работает с этой же веткой.
Поток событий запуска устроен как обычный поток серверных событий: статусы, сообщения, размышления, вызовы инструментов, обновления взаимодействия, сердцебиение, результат, ошибки и признак завершения. Потребителю нужно уметь три вещи: переподключаться, обрабатывать события идемпотентно и завершать работу только по терминальному состоянию. Отдельно поддерживают отмену - без неё длинный запуск невозможно остановить программно.
Из этих трёх требований чаще всего нарушают последнее, и это стоит разобрать. Сердцебиение существует именно затем, чтобы отличить долгую работу от оборванного соединения: пауза между содержательными событиями нормальна, отсутствие сердцебиения - нет. Интеграция, которая считает работу законченной по последнему полученному событию, ведёт себя предсказуемо плохо: при обрыве соединения она рапортует об успехе, а через несколько минут в репозитории появляется ветка от запуска, который она считала завершённым. Признак этой ошибки узнаваем: результаты приходят позже собственных отчётов о готовности, а часть запусков навсегда остаётся в промежуточном состоянии в вашей панели. Лечится это одним правилом - завершать только по терминальному статусу, а при обрыве переподключаться и дочитывать поток.
Отмена заслуживает отдельного слова, потому что она не про удобство. Автономный запуск тратит время и деньги, пока не остановлен, и без программной отмены единственный доступный рычаг - ждать. Отмену стоит завести на собственные таймауты сразу, а не потом: длительность здесь определяется задачей, и разумный предел проще поставить, чем объяснить неожиданный счёт. Есть и граница: отмена останавливает работу, но не отменяет уже сделанного. Если запуск успел отправить ветку, она останется - и именно поэтому защита на стороне провайдера остаётся последней линией, а не дублированием.
Инженерный вывод простой: относитесь к запуску как к длительной операции с состоянием, а не как к вызову функции. Тогда естественно появляются очередь, повторные подключения, дедупликация событий, отмена и обработка конфликтов. Всё это скучная инфраструктурная работа, но именно она отличает интеграцию, которая переживёт первый сбой сети, от демонстрации.
Типичные провалы предсказуемы. Проектировать интеграцию без учёта того, что активен только один запуск. Ждать завершения по последнему полученному событию вместо терминального состояния. Включить запись в текущую ветку, не усилив защиту на стороне провайдера. Запускать работу от устаревшей начальной ссылки. И не реализовать отмену, оставив себе единственный способ остановки - ждать.
curl --request POST \
--url https://api.cursor.com/v1/agents \
-u "$CURSOR_API_KEY:" \
--header 'Content-Type: application/json' \
--data '{
"prompt": {"text": "Add setup troubleshooting to README"},
"repos": [{
"url": "https://github.com/acme/app",
"startingRef": "main"
}],
"mode": "plan",
"autoCreatePR": false
}'
# по умолчанию создаётся новая ветка; запись в текущую включают осознанно