Как работает ACP: от задачи в Jira до изменений в коде
Представим привычный сценарий работы с кодинговым агентом, где разработчик открывает проект в IDE и пишет
Сделай мне таску https://jira.example.com/tasks/BILL-1842
После этого агент должен:
- получить описание задачи из Jira по ссылке;
- найти в открытой репе связанный с задачей код;
- понять, какие изменения нужно сделать;
- запросить разрешение на потенциально опасные действия;
- изменить файлы;
- показывать статус выполнения;
- вернуть описание с итоговым результатом.
С точки зрения пользователя есть только одна команда - промпт. С точки зрения системы - это продолжительное взаимодействие между IDE, агентом, файловой системой, терминалом и внешними инструментами (jira/gitlab/etc).
Agent Client Protocol (стандартное сокращение ACP) задаёт общий интерфейс для такого взаимодействия. Клиентом может быть IDE, Web UI, CLI или любое другое пользовательское приложение. Агент при этом может работать локально, запускаться отдельным процессом или находиться на удалённой инфраструктуре. В этой статье разберём на простом примере ACP-сценарий.
Архитектура
В сценарии участвуют несколько компонентов: IDE (далее будем называть Клиент), Агент и ACP-модуль между ними
ACP только описывает взаимодействие между Клиентом и Агентом, а именно:
- как Клиент и Агент шарят свои возможности;
- как стартует сессия;
- как передаётся пользовательский запрос;
- как Агент стримит сообщения, thinking и вызовы тулов;
- как происходит запрос разрешений;
- как завершается очередной turn (одна итерация взаимодействия с Агентом);
- как сессия может быть восстановлена позднее.
При этом ACP не определяет внутреннюю архитектуру Агента. Внутри могут использоваться разные модели, логика планирования решения задачи, своё управление контекстом и логика вызова тулов. Для Клиента всё это скрыто за единым протоколом. Также и от Агента скрыто то, как Клиент рендерит на UI информацию о ходе выполнения задачи и как он взаимодействует с локальным окружением пользователя.
Если более формально, то алгоритм взаимодействия Клиента и Агента состоит из трех фаз: фаза инициализации, фаза создания сессии и фаза выполнения задачи.
Взаимодействие происходит по протоколу JSON-RPC, который позволяет Клиенту и Агенту вызывать методы друг друга. Рассмотрим подробнее, как это происходит.
Инициализация соединения
Взаимодействие начинается с метода initialize. Клиент сообщает Агенту версию протокола и собственные возможности, отправляя специальный JSON
Из этого сообщения Агент узнаёт, что Клиент: умеет читать и изменять файлы; предоставляет терминал; имеет некоторые локальные тулы; работает на macOS в VS Code; открыл TypeScript-проект billing-service. Часть информации находится в стандартных capabilities, часть - в _meta. Поле _meta удобно использовать для расширения возможностей протокола ACP и добавлять кастомные поля (Клиент и Агент должны понимать, как с ними работать).
Агент отвечает собственными capabilities:
Здесь Агент сообщает, что умеет восстанавливать ранее созданные сессии и поддерживает операции над списком сессий. Данный обмен сообщениями решает важную задачу: Клиент и Агент заранее договариваются, что умеет каждый. Агенту не приходится предполагать, умеет ли конкретный Клиент показывать diff, открывать терминал или восстанавливать историю.
Каждый JSON далее будет содержать поля
"jsonrpc"и"id". Я не буду их добавлять для облегчения чтения.
В этом месте Агент и Клиент просто знакомятся и обмениваются возможностями, но еще не начали общаться.
Создание сессии
После инициализации клиент создаёт сессию, используя метод "session/new"
Сессия привязана к рабочей директории /Users/dev/work/billing-service. Это даёт Агенту понимание относительных путей для поиска файлов, терминальные команды будут выполняться относительно этого каталога.
Вместе с сессией Клиент передаёт конфигурацию MCP-сервера Jira. Через него Агент сможет получить содержимое задачи BILL-1842.
В данном случае Агент должен уметь получать конфигурации и создавать соединения с указанными MCP-серверами. На деле MCP сервер может передаваться как один из доступных инструментов Клиента (в момент "знакомства"). Так же этот MCP сервер может быть доступен Агенту и так без участия Клиента (зашит в возможности самого Агента), тогда Агенту не нужно передавать данную информацию.
Ответ агента:
Клиент получает sessionId, который будет использоваться во всех последующих запросах. Агент также объявляет доступные режимы
code- агент может читать и изменять проект;plan- агент анализирует задачу, но не вносит изменения.
Режимы особенно полезны для IDE-интерфейса. Пользователь может явно выбрать, хочет ли он получить только план или сразу разрешить агенту работать с кодом.
Запрос пользователя
Теперь все готово, чтобы Агент мог получить запрос от Клиента. В момент отправки этого запроса Клиент вызывает метод session/prompt
Prompt - это не просто строка, отправленная пользователем, а массив блоков с контентом. В него также входят: файлы, ссылки на ресурсы, выделенный фрагмент кода, изображения и т.д. Перед запуском Агента ACP-адаптер может сам дополнить prompt служебными данными, взятыми из окружения Клиента.
Тут Клиент и Агент обменялись текущим контекстом и уже обозначили задачу, которую Агенту предстоит решать.
Выполнение задачи
Важно понимать семантику session/prompt. Это не обычный запрос вида request → готовый ответ. Один turn Агента может продолжаться минуты и включать десятки действий
prompt
↓
анализ задачи
↓
вызов Jira
↓
поиск файлов
↓
чтение кода
↓
изменение файлов
↓
запуск тестов
↓
финальный ответПоэтому содержимое ответа передаётся через уведомления session/update. Сам запрос session/prompt остаётся активным до завершения turn. В конце Клиент получает только формальное подтверждение:
{
"result": {
"stopReason": "end_turn",
"userMessageId": "user-msg-1"
}
}Пользовательский текст, reasoning, tool calls и результаты работы приходят раньше - в виде отдельных update-событий. Рассмотрим пару примеров таких сообщений.
После получения prompt Агент сначала думает. Он решает первым делом загрузить Jira issue. Клиент получит первый update:
Тип agent_thought_chunk означает, что Агент передаёт свой reasoning или его представление для Клиента. В IDE такое сообщение можно показать как свернутый блок:
Анализирую задачу…
Нужно сначала прочитать Jira issue, а затем найти связанный код.Далее Агент вызывает инструмент jira|get_issue. Сначала Клиент получает сообщение о начале tool call:
Этот tool call содержит несколько уровней данных: id, название, тип, статус, аргументы и т.д. Это позволяет Клиенту отобразить состояние вызова инструмента в интерфейсе
◌ jira|get_issue
BILL-1842MCP-сервер возвращает агенту ответ с описанием задачи:
{
"id": "call-jira-1",
"result": "{\n \"key\": \"BILL-1842\",\n \"summary\": \"Add VAT validation for invoice checkout\",\n \"description\": \"When a business customer checks out, VAT ID must be validated before invoice creation.\",\n \"acceptanceCriteria\": [\n \"Reject invoice checkout when VAT ID is missing for business customers\",\n \"Show a clear validation error\",\n \"Cover service and API behavior with tests\"\n ],\n \"components\": [\"checkout\", \"invoicing\"]\n}"
}Клиент получает update о завершении вызова для этображения в UI
В базовом сценарии tool call проходит через состояния in_progress → completed. Возможны также следующие последовательности состояний: in_progress → failed, in_progress → cancelled и in_progress → rejected. Клиенту не нужно знать, как именно Агент использует инструмент. Он получает унифицированное представление жизненного цикла операции.
Видно, что суть задачи - учитывать НДС при создании заказа.
Эта фаза и есть тот момент, когда Агент решает для нас задачу, а мы наблюдаем его действия.
Редактирование файлов
Рассмотрим еще пример, когда Агент готов приступить к редактированию кода внутри проекта. Последовательно он выполнит несколько тулколов: поискать файлы в репозитории, поискать нужный код, изменить файл и запустить тесты. Всё это точно так же пойдёт через update-события:
Остальные тулколы передаются по аналогии. Рассмотрим еще одну концепцию - подтверждение выполнения действия. Агент отправляет клиенту отдельный JSON-RPC request:
Клиент показывает пользователю диалог:
Агент хочет отредактировать файл `example.file`.
[Разрешить один раз]
[Всегда разрешать редактирование файлов в этом проекте]
[Запретить]После выбора клиент отвечает:
{
"result": {
"outcome": {
"outcome": "selected",
"optionId": "10"
}
}
}Разрешения относятся к взаимодействию Клиента и Агента, а не только к интерфейсу. Агент обязан дождаться решения пользователя до выполнения защищённого действия.
Стриминг
Во время выполнения задачи Клиент может получать ответ не сразу, а частями, так как модель зачастую отдаёт ответ чанками. Для этого существуют специальные update-события:
{
"sessionUpdate": "agent_message_chunk"
}Клиент должен уметь объединять чанки с одинаковым messageId для того, чтобы правильно отображать стриминг ответа модели.
Похожее событие для стриминга ризонинга
"sessionUpdate": "agent_thought_chunk"
Сохранение истории сессии
После завершения turn Агент может сохранить историю. Упрощённый вид:
История нужна не только для отображения чата на UI. Она позволяет:
- восстановить сессию после перезапуска IDE;
- продолжить работу с другого устройства;
- понять, какие разрешения выдавал пользователь;
- повторно показать выполненные tool calls;
- построить аудит действий агента;
- восстановить контекст для следующего turn;
- проанализировать ошибки.
Заключение
На сквозном примере мы разобрали полный цикл взаимодействия IDE и кодингового агента через ACP: от инициализации соединения и создания сессии до чтения файлов, вызова инструментов, запроса разрешений и внесения изменений в код.
ACP задаёт единый контракт между Клиентом и Агентом. Клиент отвечает за пользовательский интерфейс и доступ к локальному окружению: показывает сообщения, отображает состояние выполнения, предоставляет файловые и терминальные инструменты и запрашивает у пользователя необходимые разрешения. Агент, в свою очередь, управляет выполнением задачи: анализирует запрос, планирует действия, вызывает инструменты и возвращает результат.
За счёт такого разделения Клиент и Агент можно развивать независимо друг от друга. Один Агент может работать в разных IDE, редакторах и других интерфейсах, а один Клиент - подключаться к разным агентным реализациям. Например, некоторые IDE уже предоставляют готовый чат с поддержкой ACP. Чтобы встроить в них собственного Агента, не требуется заново реализовывать интерфейс, стриминг сообщений, отображение tool calls и управление разрешениями - достаточно реализовать поддержку протокола.
При этом ACP не определяет внутреннее устройство Агента. Протокол ничего не говорит о том, какую модель использовать, как строить контекст, планировать действия, хранить память или выполнять код. Он стандартизирует только границу между Агентом и Клиентом.
Именно в этом заключается основная ценность ACP: протокол отделяет агентную логику от конкретного пользовательского интерфейса и локального окружения. Благодаря этому кодинговый агент перестаёт быть частью одного IDE-плагина и становится самостоятельным компонентом, который можно подключать к разным Клиентам без повторной реализации всей интеграции.