Спецификация ACP-сервера¶
ACP (Agent Client Protocol) — открытый протокол взаимодействия AI-агентов с редакторами и IDE.
Koda CLI реализует серверную сторону протокола и запускается в ACP-режиме флагом --acp.
Концепция, сценарии использования и настройка описаны на странице ACP-режим.
Эта страница — технический справочник протокола: транспорт, методы, типы данных, возможности и ошибки.
Транспорт¶
Обмен сообщениями идёт по протоколу JSON-RPC 2.0 через стандартные потоки процесса:
- stdin — запросы и уведомления от клиента к агенту;
- stdout — ответы и уведомления от агента к клиенту.
Каждое сообщение — одна JSON-строка, завершённая переводом строки. stdout зарезервирован под фреймы протокола: любая посторонняя запись в stdout повредит обмен, поэтому служебные сообщения агент пишет в stderr.
Поддерживается мультиплексирование запросов: клиент может отправлять несколько сообщений одновременно, не дожидаясь ответа на предыдущее.
Идентификатор запроса (id) — число или строка, ответ повторяет id запроса.
Версия протокола — 1, она передаётся в поле protocolVersion при инициализации.
Жизненный цикл соединения¶
Типичный сценарий работы клиента с сервером:
initialize— согласовать версию протокола и получить возможности агента;authenticate— выбрать способ аутентификации (если нужно);session/new— создать сессию;session/prompt— отправлять запросы, получая потоковые обновленияsession/update;session/cancel(уведомление) — отменить выполняющийся запрос при необходимости;- закрыть stdin — завершить соединение.
Методы session/fork, session/load, session/compact, session/set_config_option, session/set_model, session/set_reasoning_effort, session/set_mode, session/set_plan и koda/custom_models/* доступны опционально: клиент определяет их наличие по возможностям агента из ответа на initialize.
Методы агента¶
Агент обрабатывает следующие методы:
| Метод | Тип | Назначение |
|---|---|---|
initialize |
запрос | установить соединение и получить возможности агента |
authenticate |
запрос | аутентифицировать пользователя |
session/new |
запрос | создать новую сессию |
session/fork |
запрос | создать новую сессию из точки ветвления |
session/load |
запрос | восстановить сохранённую сессию |
session/prompt |
запрос | отправить запрос агенту |
session/cancel |
уведомление | отменить выполняющийся запрос |
session/compact |
запрос | сжать контекст сессии |
session/set_config_option |
запрос | изменить опцию конфигурации сессии |
session/set_model |
запрос | сменить модель в текущей сессии |
session/set_reasoning_effort |
запрос | изменить уровень рассуждений модели |
session/set_mode |
запрос | переключить режим сессии (agent / plan) |
session/set_plan |
запрос | заменить опубликованный план сессии |
koda/custom_models/list |
запрос | получить список пользовательских моделей |
koda/custom_models/upsert |
запрос | добавить или обновить пользовательскую модель |
koda/custom_models/delete |
запрос | удалить пользовательскую модель |
initialize¶
Запрос согласует версию протокола и возможности клиента:
{
"jsonrpc": "2.0",
"id": 0,
"method": "initialize",
"params": {
"protocolVersion": 1,
"clientCapabilities": {
"fs": {
"readTextFile": true,
"writeTextFile": false
},
"terminal": true
}
}
}
Ответ содержит возможности агента, способы аутентификации и версию протокола:
{
"jsonrpc": "2.0",
"id": 0,
"result": {
"protocolVersion": 1,
"authMethods": [
{
"id": "koda_auth",
"name": "Koda Auth",
"description": "Authenticate via Jarvis API"
},
{
"id": "without_auth",
"name": "Continue without authentication",
"description": null
}
],
"agentCapabilities": {
"loadSession": false,
"mcpCapabilities": {
"http": true,
"sse": true
},
"promptCapabilities": {
"audio": true,
"embeddedContext": true,
"image": true
},
"sessionCapabilities": {
"fork": {}
}
}
}
}
Возможности агента¶
| Поле | Тип | Значение |
|---|---|---|
loadSession |
boolean | поддержка session/load; включается переменной окружения KODA_ACP_SESSION_STORE_DIR |
mcpCapabilities |
объект | http и sse — поддержка MCP-серверов этих типов |
promptCapabilities |
объект | audio, embeddedContext, image — типы контента, которые агент принимает в session/prompt |
sessionCapabilities |
объект | fork — поддержка ветвления сессий |
loadSession равен true только если агент запущен с переменной окружения KODA_ACP_SESSION_STORE_DIR.
Без нее контрольные точки сессий не сохраняются, а session/load и session/fork возвращают ошибку.
authenticate¶
Выбирает способ аутентификации из authMethods, объявленных агентом.
Ответ — null.
session/new¶
Создаёт новую сессию с указанной рабочей директорией:
{
"jsonrpc": "2.0",
"id": 2,
"method": "session/new",
"params": {
"cwd": "/path/to/project",
"systemPrompt": "You are a project-specific coding agent.",
"allowedSkills": ["code-review"],
"coreTools": ["read_file", "search_file_content", "replace"],
"excludeTools": ["run_shell_command"],
"mcpServers": []
}
}
Параметры¶
| Параметр | Тип | Что делает |
|---|---|---|
cwd |
строка | рабочая директория сессии (обязательный) |
systemPrompt |
строка | системная инструкция вместо стандартного промпта CLI |
allowedSkills |
список | белый список навыков для этой сессии; пустой массив отключает все навыки |
enabledSkills |
список | алиас для allowedSkills |
coreTools |
список | белый список встроенных инструментов для этой сессии |
excludeTools |
список | инструменты, которые нужно скрыть от модели в этой сессии |
mcpServers |
список | MCP-серверы для сессии |
excludeTools дополняет пользовательские настройки, а coreTools только сужает уже заданный белый список.
Имена инструментов в coreTools принимаются в виде алиасов: read_file, search_file_content, replace, glob, list_directory и другие.
Ответ¶
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"sessionId": "c8f8e0a1-...",
"models": {
"currentModelId": "koda-pro",
"availableModels": [
{
"modelId": "koda-pro",
"name": "koda-pro",
"description": null,
"imageSupport": true,
"reasoning": {
"supportedEfforts": ["low", "medium", "high"],
"defaultEffort": "medium",
"currentEffort": "medium"
}
}
]
},
"configOptions": null,
"mcpState": {
"discoveryState": "completed",
"servers": []
}
}
}
Ответ содержит:
| Поле | Назначение |
|---|---|
sessionId |
идентификатор сессии, используется во всех последующих вызовах |
models |
текущая модель и список доступных моделей с метаданными (imageSupport, reasoning) |
configOptions |
опции конфигурации сессии (модель типа select); null, если нет |
mcpState |
состояние обнаружения MCP-серверов и список серверов |
session/fork¶
Создаёт новую сессию из точки ветвления существующей.
В params принимает те же поля, что и session/new, плюс обязательные sessionId исходной сессии и _meta с меткой клиента:
{
"jsonrpc": "2.0",
"id": 3,
"method": "session/fork",
"params": {
"sessionId": "c8f8e0a1-...",
"cwd": "/path/to/project",
"mcpServers": [],
"_meta": {
"com.kodadev.desktopEntryId": "message-42"
}
}
}
Метка _meta.com.kodadev.desktopEntryId указывает на сообщение, перед которым строится ветка.
Если метка отсутствует, граница не найдена или рабочая директория исходной сессии не совпадает с cwd, агент возвращает типизированную ошибку -32002 (session fork unavailable).
Ответ имеет ту же структуру, что и session/new.
session/load¶
Восстанавливает сохранённую сессию по её идентификатору.
Доступен только при включённом хранилище контрольных точек (KODA_ACP_SESSION_STORE_DIR).
{
"jsonrpc": "2.0",
"id": 4,
"method": "session/load",
"params": {
"sessionId": "c8f8e0a1-...",
"cwd": "/path/to/project",
"mcpServers": []
}
}
Параметры — как в session/new, плюс обязательный sessionId.
Рабочая директория cwd должна совпадать с директорией сохранённой сессии, иначе агент вернёт ошибку -32001 (session restore unavailable).
Ответ — null: восстановленная сессия продолжает слать обновления session/update, как и новая.
session/prompt¶
Отправляет запрос агенту:
{
"jsonrpc": "2.0",
"id": 5,
"method": "session/prompt",
"params": {
"sessionId": "c8f8e0a1-...",
"prompt": [
{
"type": "text",
"text": "Объясни, как устроена авторизация в этом проекте"
}
]
}
}
prompt — массив content block.
В _meta клиент может передать com.kodadev.desktopEntryId — метку, которая позже используется как точка ветвления в session/fork.
Пока агент обрабатывает запрос, он шлёт потоковые уведомления session/update.
Каждый новый session/prompt отменяет предыдущий незавершённый запрос той же сессии.
Ответ¶
stopReason может принимать значения:
| Значение | Что означает |
|---|---|
end_turn |
ответ завершён |
max_tokens |
достигнут лимит токенов |
max_turn_requests |
слишком много обращений к модели за один ход |
refusal |
модель отказалась отвечать |
cancelled |
запрос отменён через session/cancel или новым session/prompt |
Почему остановился ход¶
Два случая CLI обрывает сам:
- Зацикливание. Детектор циклов увидел повторяющиеся вызовы инструментов или повторяющийся текст.
Пользователю уходит объяснение обычным
agent_message_chunk, а ответ приходит какend_turnс приметой в_meta: клиент, который её понимает, покажет это отдельной заметкой, а не очередным абзацем от модели.
- Слишком много обращений к модели за один ход. Ответ приходит со стандартным
stopReason: "max_turn_requests". Ответы уже выполненных инструментов остаются в истории, и следующее сообщение пользователя продолжает работу с этого места.
session/cancel (уведомление)¶
Отменяет выполняющийся запрос сессии:
Уведомление не имеет ответа.
Текущий session/prompt завершится со stopReason: "cancelled".
session/compact¶
Сжимает контекст сессии:
{
"jsonrpc": "2.0",
"id": 6,
"method": "session/compact",
"params": {
"sessionId": "c8f8e0a1-..."
}
}
Ответ:
{
"jsonrpc": "2.0",
"id": 6,
"result": {
"compacted": true,
"originalTokenCount": 42000,
"newTokenCount": 3100
}
}
Сжатие нельзя запустить, пока выполняется session/prompt.
Ход сжатия агент дополнительно сообщает уведомлением context_compaction_update.
session/set_config_option¶
Изменяет опцию конфигурации сессии:
{
"jsonrpc": "2.0",
"id": 7,
"method": "session/set_config_option",
"params": {
"sessionId": "c8f8e0a1-...",
"configId": "models",
"value": "koda-pro"
}
}
Сервер поддерживает единственную опцию models (тип select).
Ответ возвращает обновлённый список configOptions.
Неизвестный configId или значение приводит к ошибке -32602 (invalid params).
session/set_model¶
Сменяет модель сессии:
{
"jsonrpc": "2.0",
"id": 8,
"method": "session/set_model",
"params": {
"sessionId": "c8f8e0a1-...",
"modelId": "koda-pro"
}
}
Ответ — пустой объект {}.
Модель должна присутствовать в списке availableModels из ответа session/new, иначе — ошибка -32602.
session/set_reasoning_effort¶
Изменяет уровень рассуждений текущей модели:
{
"jsonrpc": "2.0",
"id": 9,
"method": "session/set_reasoning_effort",
"params": {
"sessionId": "c8f8e0a1-...",
"effort": "high"
}
}
Ответ возвращает обновлённое состояние моделей models.
Уровень должен поддерживаться моделью: доступные значения перечислены в reasoning.supportedEfforts.
Выбор сохраняется в пользовательских настройках и распространяется на интерактивный интерфейс CLI.
session/set_plan¶
Заменяет опубликованный план сессии: клиент присылает список шагов целиком, поэтому шага, которого в запросе нет, у агента больше не остаётся.
{
"jsonrpc": "2.0",
"id": 4,
"method": "session/set_plan",
"params": {
"sessionId": "c8f8e0a1-...",
"entries": [
{ "content": "Изучить роутер" },
{ "content": "Внести правки" }
],
"explanation": "Документацию отложили"
}
}
| Параметр | Тип | Что делает |
|---|---|---|
sessionId |
строка | идентификатор сессии (обязательный) |
entries |
список | новый план целиком; у шага поле content, status и priority |
explanation |
строка | необязательное пояснение к правке |
status у шага необязателен и по умолчанию pending: клиент, который только переформулировал ещё не начатый план, ничего больше не сообщает.
Пустой список entries отменяет план — в том числе наполовину выполненный; режим сессии при этом не меняется.
Запрос не ждёт конца текущего хода, поэтому отменить план можно и во время работы агента.
Правка, из которой не собирается план (шаги без текста, больше одного шага in_progress), отклоняется с ошибкой -32602, и опубликованный план остаётся прежним.
Результат приходит обычным уведомлением с sessionUpdate: "plan".
koda/custom_models/*¶
Управление пользовательскими моделями (OpenAI-совместимыми).
Метод koda/custom_models/list возвращает список моделей:
{
"jsonrpc": "2.0",
"id": 10,
"result": {
"models": [
{
"modelId": "my-custom-model",
"baseUrl": "https://api.example.com/v1"
}
]
}
}
Метод koda/custom_models/upsert добавляет или обновляет модель:
{
"jsonrpc": "2.0",
"id": 11,
"method": "koda/custom_models/upsert",
"params": {
"modelId": "my-custom-model",
"baseUrl": "https://api.example.com/v1",
"apiKey": "sk-...",
"previousModelId": "old-model-id"
}
}
apiKey обязателен для новой модели.
previousModelId позволяет переименовать модель: старая запись удаляется.
Метод koda/custom_models/delete удаляет модель:
{
"jsonrpc": "2.0",
"id": 12,
"method": "koda/custom_models/delete",
"params": {
"modelId": "my-custom-model"
}
}
Все три метода возвращают актуальный список models.
Методы клиента¶
Клиент (IDE или инструмент) обрабатывает следующие запросы агента:
| Метод | Назначение |
|---|---|
fs/read_text_file |
прочитать текстовый файл |
fs/write_text_file |
записать текстовый файл |
terminal/create |
создать терминал |
terminal/kill |
завершить процесс терминала |
terminal/output |
получить вывод терминала |
terminal/release |
освободить терминал |
terminal/wait_for_exit |
дождаться завершения процесса терминала |
session/request_permission |
запросить у пользователя разрешение на вызов инструмента |
session/update |
уведомление: потоковое обновление сессии |
Методы fs/* и terminal/* доступны только если клиент объявил соответствующие возможности в initialize.
Файловая система¶
fs/read_text_file:
{
"jsonrpc": "2.0",
"id": 100,
"method": "fs/read_text_file",
"params": {
"sessionId": "c8f8e0a1-...",
"path": "/path/to/project/src/main.ts",
"line": null,
"limit": null
}
}
Ответ — объект с полем content (строка).
Параметры line и limit позволяют прочитать фрагмент файла.
fs/write_text_file:
{
"jsonrpc": "2.0",
"id": 101,
"method": "fs/write_text_file",
"params": {
"sessionId": "c8f8e0a1-...",
"path": "/path/to/project/README.md",
"content": "Новое содержимое файла"
}
}
Ответ — null.
Терминалы¶
terminal/create — создаёт терминал и возвращает его идентификатор:
{
"jsonrpc": "2.0",
"id": 102,
"method": "terminal/create",
"params": {
"sessionId": "c8f8e0a1-...",
"command": "/bin/bash",
"args": ["-lc", "npm test"],
"cwd": "/path/to/project",
"env": [{ "name": "CI", "value": "1" }],
"outputByteLimit": 524288
}
}
Ответ:
Терминалы применяются для shell-команд, когда клиент объявил поддержку в clientCapabilities.terminal.
Агент ограничивает размер буфера вывода (outputByteLimit, по умолчанию 512 КБ) и сообщает об усечении в terminal/output.
terminal/kill, terminal/release — ответ пустой объект {}.
terminal/wait_for_exit — ожидает завершения и возвращает { "exitCode": 0, "signal": null }.
terminal/output — возвращает вывод, статус завершения и флаг усечения:
{
"jsonrpc": "2.0",
"id": 103,
"result": {
"output": "...",
"truncated": false,
"exitStatus": {
"exitCode": 0,
"signal": null
}
}
}
Запрос разрешения¶
session/request_permission вызывается агентом перед выполнением инструмента, требующего подтверждения:
{
"jsonrpc": "2.0",
"id": 200,
"method": "session/request_permission",
"params": {
"sessionId": "c8f8e0a1-...",
"toolCall": {
"toolCallId": "call-1",
"status": "pending",
"title": "Edit src/main.ts",
"kind": "edit",
"locations": [{ "path": "src/main.ts", "line": 10 }],
"content": [
{
"type": "diff",
"path": "src/main.ts",
"oldText": "const a = 1;",
"newText": "const a = 2;"
}
]
},
"options": [
{
"optionId": "proceed_always",
"name": "Allow All Edits",
"kind": "allow_always"
},
{
"optionId": "proceed_once",
"name": "Allow",
"kind": "allow_once"
},
{
"optionId": "cancel",
"name": "Reject",
"kind": "reject_once"
}
]
}
}
Клиент возвращает выбранный вариант:
{
"jsonrpc": "2.0",
"id": 200,
"result": {
"outcome": {
"outcome": "selected",
"optionId": "proceed_once"
}
}
}
Если пользователь отклонил запрос или диалог закрыт, клиент возвращает { "outcome": "cancelled" }.
Варианты разрешений (kind):
| Значение | Назначение |
|---|---|
allow_once |
разрешить один раз |
allow_always |
разрешить всегда |
reject_once |
отклонить один раз |
reject_always |
отклонять всегда |
Уведомление session/update¶
Агент шлёт клиенту уведомления session/update с потоковыми изменениями сессии:
{
"jsonrpc": "2.0",
"method": "session/update",
"params": {
"sessionId": "c8f8e0a1-...",
"update": {
"sessionUpdate": "agent_message_chunk",
"content": {
"type": "text",
"text": "Авторизация реализована через JWT..."
}
}
}
}
Поле update.sessionUpdate определяет тип обновления:
| Тип | Назначение |
|---|---|
user_message_chunk |
фрагмент сообщения пользователя |
agent_message_chunk |
фрагмент ответа агента |
agent_thought_chunk |
фрагмент рассуждений агента |
tool_call |
начало вызова инструмента |
tool_call_update |
изменение статуса или контента вызова инструмента |
plan |
план работ: список записей с приоритетом и статусом |
available_commands_update |
доступные слеш-команды сессии |
session_info_update |
метаданные сессии: заголовок и время обновления |
config_option_update |
актуальные опции конфигурации |
mcp_state_update |
состояние обнаружения MCP-серверов и список серверов |
context_usage_update |
использование контекста: usedTokens, maxTokens, modelId, источник |
context_compaction_update |
ход сжатия контекста: started / finished, триггер automatic / manual |
После создания сессии агент отправляет начальные обновления: available_commands_update, mcp_state_update, session_info_update и context_usage_update.
Вызовы инструментов¶
Событие tool_call описывает вызов инструмента:
| Поле | Назначение |
|---|---|
toolCallId |
идентификатор вызова |
status |
pending, in_progress, completed, failed |
title |
описание вызова для отображения пользователю |
kind |
категория: read, edit, delete, move, search, execute, think, fetch, other |
locations |
пути и строки файлов, с которыми работает инструмент |
content |
массив контента вызова: content, diff, terminal |
rawInput |
исходные аргументы вызова (необязательно) |
rawOutput |
сырой результат вызова (необязательно) |
_meta |
служебные поля; _meta.toolName и _meta.toolDisplayName содержат имя инструмента |
Каждому событию tool_call со статусом pending или in_progress соответствует tool_call_update со статусом completed или failed.
Политика доступа без перезапуска¶
Режим подтверждений и «только чтение» задаются при запуске (--approval-mode, наборы coreTools/excludeTools в session/new), но у живой сессии их можно менять на месте.
Поддержку показывает agentCapabilities.sessionCapabilities.accessPolicy в ответе initialize; сама политика выставлена как config option access_policy со значениями read_only, default, auto_edit и yolo.
Ответы session/new и session/fork несут её в configOptions с текущим значением, переключает клиент стандартным запросом:
{
"jsonrpc": "2.0",
"id": 5,
"method": "session/set_config_option",
"params": {
"sessionId": "session-id",
"configId": "access_policy",
"value": "read_only"
}
}
Ответ содержит обновлённый список configOptions.
read_only работает только набором инструментов: пишущие инструменты пропадают из объявлений модели, каждый вызов проверяется заново (shell принимает только простые читающие команды, как в режиме plan), MCP-инструменты недоступны, а промпт не меняется.
Режим подтверждений при этом сохраняется и возвращается вместе с любым другим значением.
Политика независима от режима сессии: /plan implement в read-only сессии не откроет пишущие инструменты.
Значение живёт в конкретной сессии и в чекпоинт не пишется: сессия из session/load и ветка из session/fork стартуют с политикой из аргументов запуска, так что клиент выставляет её заново.
Фоновые субагенты¶
Инструмент run_subagent доступен и в ACP-сессиях: агент доставляет результаты субагентов модели сам, а клиент может отслеживать задачи и управлять ими.
Клиент должен объявить, что понимает уведомления субагентов, иначе агент их не шлёт:
{
"jsonrpc": "2.0",
"id": 0,
"method": "initialize",
"params": {
"protocolVersion": 1,
"clientCapabilities": {
"fs": { "readTextFile": false, "writeTextFile": false },
"subagents": true,
"terminal": false
}
}
}
Поддержку со стороны агента показывает поле agentCapabilities.sessionCapabilities.subagents в ответе initialize; там же приходят maxConcurrentRange — границы, в которых клиент может предлагать значение maxConcurrent, maxSteps — сколько шагов может сделать один запуск субагента, и три флага: releaseWait: true означает, что есть метод session/subagent_release_wait, liveEnable: true — что переключатель enabled применяется без перезапуска, а roles: true — что поддерживаются роли субагентов.
Возможность объявляется всегда, даже когда субагенты выключены настройкой: иначе клиент спрятал бы настройки ровно тогда, когда их нужно включить.
Ответы session/new и session/fork дополнительно несут блок subagents с текущими задачами и настройками; session/load отвечает null — восстановленная сессия узнаёт состояние из стартовых уведомлений или запроса session/subagent_list.
Клиент, который не объявил возможность, ничего не ломает: субагенты всё равно работают и доставляют результат модели, просто без карточек в интерфейсе.
Уведомления субагентов¶
В потоке session/update приходят четыре типа обновлений:
Тип sessionUpdate |
Назначение |
|---|---|
subagent_update |
полный снимок задачи в поле task, а не дельта |
subagent_turn |
обрамляет ход, в котором агент отдаёт модели результат субагента: пара started/finished |
subagent_settings_update |
рассылка новых настроек субагентов во все живые сессии |
subagent_definitions_update |
изменение каталога ролей субагентов этой сессии |
Правило слияния снимков одно: игнорировать снимок, у которого updatedAt старее сохранённого, — прогресс, активность и финал приходят вперемешку и не по порядку.
Поле activity присутствует, когда обновление вызвано движением активности, а deliveryId — только на терминальном обновлении, результат которого отдан модели.
Поле acpToolCallId связывает задачу с тем вызовом tool_call, из которого она запущена.
У записи активности три вида: model (ответ модели субагента), tool (вызов инструмента) и compression (субагент сжимает собственную историю).
Последний не стоит показывать как речь модели: это служебная работа, а не находка.
Запись compression со статусом completed несёт счётчики originalTokenCount и newTokenCount, чтобы клиент сам сформулировал строку, а не разбирал text; у failed причина в text.
Между маркерами subagent_turn идут обычные agent_message_chunk, agent_thought_chunk и tool_call.
Пара waiting/resumed с тем же promptId, что у хода, обрамляет паузу внутри хода: при выключенном allowParentToContinue следующий запрос к модели после группы инструментов ждёт, пока завершатся все работающие субагенты из taskIds (см. Ожидание субагентов).
Пауза заканчивается, когда они завершились, когда пользователь включил продолжение, когда клиент снял ожидание методом session/subagent_release_wait, или когда ход отменили.
Уведомление subagent_settings_update вместе с settings несёт availableTools — инструменты, которые эта установка готова дать субагенту прямо сейчас; список пересчитывается на каждую рассылку.
Агент следит и за самим settings.json: секция subagents, изменённая другим процессом — терминалом, Desktop без живого агента или руками, — подхватывается за ~250 мс и рассылается тем же уведомлением.
Переключатель enabled применяется на месте: инструмент run_subagent регистрируется или снимается у каждой живой сессии, и следующий ход видит новый список инструментов и подсказку; перезапуск не нужен (флаг liveEnable в capabilities).
Уведомление subagent_definitions_update приходит, когда изменились файлы ролей этого workspace или глобальной папки: agents и errors те же, что в ответе session/subagent_definitions (см. Роли субагентов).
Ожидание субагентов¶
По умолчанию агент ждёт своих субагентов.
Модель отправляет все независимые run_subagent в одном ответе; когда группа инструментов этого ответа выполнена, агент не отправляет следующий запрос к модели, пока не завершится последний работающий субагент, а готовые результаты уходят в этот же запрос скрытой парой «вызов → ответ инструмента».
Пока агент ждёт, статусы, прогресс и session/subagent_cancel работают как обычно; отмена последнего субагента тоже снимает ожидание, и его результат «отменено» уходит модели тем же запросом.
allowParentToContinue: true возвращает прежнее поведение: агент продолжает независимую работу, а результат завершившегося субагента подмешивается в ближайшую границу инструментов того же хода или, если ход уже закончился, приходит отдельным ходом между маркерами started/finished.
Настройка проверяется на каждой границе: включение во время паузы заканчивает её сразу, выключение не прерывает уже начатый запрос.
Снять ожидание разово, не меняя настройку, можно методом session/subagent_release_wait с одним полем sessionId: удерживаемый ход продолжается без субагентов, а их результаты придут на следующей границе инструментов или отдельным ходом, как при включённом продолжении.
Ответ released: false значит, что ничего не ждало.
Снятие действует до конца текущего хода: его дальнейшие границы не задерживаются, а следующий ход снова ждёт по настройке.
Сам ход при этом не отменяется — это и отличает метод от session/cancel.
reasoningEffort задаёт уровень рассуждений для запросов субагента.
null означает стандартный уровень модели, на которой субагент работает, независимо от того, что пользователь выбрал для этой модели в основном чате.
Уровень сверяется с reasoning.supportedEfforts фактической модели на каждом запросе: неподдерживаемый заменяется стандартным, у модели без метаданных уровень не отправляется вовсе.
Допустимые значения те же, что у session/set_reasoning_effort; неизвестное нормализуется в null.
Роли субагентов¶
Роль — это Markdown-файл <workspace>/.koda/agents/<имя>.md или ~/.kodacli/agents/<имя>.md (глобальная папка; KODA_CLI_HOME может её переопределить).
Шапка знает пять полей: name (совпадает с именем файла, строчные буквы, цифры и дефисы, до 64 символов), description (до 500 символов) и необязательные model (идентификатор модели или inherit), reasoningEffort (уровень рассуждений роли; отсутствие ключа оставляет уровень из настроек) и tools (список только из исследовательских инструментов — роль может лишь сузить набор).
Тело файла — инструкции роли: они попадают в системный промпт субагента после раздела режима.
Неизвестное поле, пустое тело, файл больше 64 КиБ, символическая ссылка или вложенная папка не читаются: такие файлы возвращаются в errors.
Файл в workspace перекрывает глобальный с тем же именем, и невалидный workspace-файл резервирует имя.
Модель получает список включённых ролей в системном промпте и передаёт имя в параметре agent инструмента run_subagent; неизвестная, выключенная или посаженная на недоступную модель роль — это ошибка вызова, а не обычный запуск.
Роль копируется в сессию субагента целиком: restart незавершённой задачи идёт с этим снимком даже после правки файла, а повтор завершённой берёт роль по имени заново.
Инструменты роли пересекаются с settings.tools при каждом запуске.
| Метод | Назначение |
|---|---|
session/subagent_definitions |
каталог ролей с errors; refresh: true перечитывает файлы |
session/subagent_definition_create |
создать файл роли из шаблона: name, isGlobal, uiLanguage |
session/subagent_definition_delete |
удалить ровно тот файл, который был показан: name и sourceUri |
session/subagent_definition_read |
роль целиком для правки: шапка, prompt и supportedTools |
session/subagent_definition_write |
переписать файл роли черновиком: те же поля, что в ответе _read |
Создание нормализует имя в kebab-case, никогда не перезаписывает существующий файл и подбирает язык шаблона по uiLanguage (ru/ru-* даёт русский текст, остальное — английский); ключи YAML, имена инструментов и model: inherit не переводятся.
Удаление заново читает каталог и требует совпадения пары name + sourceUri.
Чтение и запись роли проверяют пару так же: _read отдаёт description, model (null — это inherit), reasoningEffort (null — уровень из настроек), tools (null — все исследовательские инструменты), prompt (тело файла), supportedTools — список имён, которые роль вообще может объявить, — и supportedReasoningEfforts.
supportedTools шире availableTools сессии, потому что файл не зависит от режима подтверждений.
Сломанный файл _read не отдаёт, а называет ошибку каталога.
_write принимает те же поля без supportedTools, рендерит файл в форме шаблона, прогоняет его через парсер каталога и при ошибке отказывает с её текстом, не трогая файл; сломанный файл из errors перезаписать можно — так его чинят.
Имя не правится: это имя файла.
В настройках поле disabledAgentNames выключает роли по имени, не трогая файлы; пустой список — явное значение, которым включают последнюю роль обратно.
В снимке задачи agentName называет роль, под которой работает субагент.
Клиент может запустить роли сам, без решения модели: в session/prompt передаётся _meta["com.kodadev.subagentRoles"] со списком имён (дубликаты и неверные имена отбрасываются, не больше четырёх).
Тогда перед первым запросом к модели в историю кладутся сообщение пользователя и по одному вызову run_subagent на роль с текстом сообщения как задачей; вызовы идут обычным путём инструментов, с карточками tool_call и настройкой ожидания, а модель получает их результаты первым же запросом.
Терминал и Telegram делают то же самое по метке @agent:<имя> в начале абзаца, так что клиенту достаточно вырезать метки из текста и передать имена в _meta.
Каталог живёт в файлах, и агент следит за ними: пока у сессии есть клиент, понимающий субагентов, папки ролей (и их родители, чтобы заметить появление .koda/agents) стоят под наблюдением файловой системы.
Любое изменение каталога — сохранённый в редакторе файл, удаление руками, session/subagent_definition_create/_delete/_write из этой или другой сессии — приходит уведомлением subagent_definitions_update.
Клиент заменяет свой список, не переспрашивая; события склеиваются по 200 мс, а перечитывание без изменений уведомления не даёт.
Своё создание, запись и удаление агент объявляет сразу, ещё до ответа на запрос.
Инструменты субагента¶
Ключ настроек subagents.tools — это подмножество availableTools, и оно умеет только сужать: имя, которого нет в списке доступных, молча отбрасывается, а не выдаёт субагенту новый инструмент.
null означает «все доступные», и так же читается ненастроенное значение, поэтому появившийся позже инструмент подхватывается сам.
Пустой список — осознанный выбор, а не умолчание: субагент останется вообще без инструментов.
Методы управления субагентами¶
| Метод | Назначение |
|---|---|
session/subagent_list |
задачи сессии, текущие настройки и availableTools |
session/subagent_details |
запрос, модель, полный результат, лента активности и история одной задачи |
session/subagent_cancel |
остановить задачу; без taskId останавливает все работающие |
session/subagent_extend |
дать подвисшей задаче ещё времени |
session/subagent_restart |
продолжить незавершённую или прогнать завершённую заново |
session/subagent_prompt |
ход пользователя в разговоре с субагентом: prompt и policy |
session/subagent_compress |
сжать историю завершённого субагента в одно резюме |
session/subagent_release_wait |
снять ожидание субагентов у удерживаемого хода, не отменяя его и не меняя настройку |
session/subagent_set_settings |
изменить настройки субагентов, включая allowParentToContinue и reasoningEffort |
Метод session/subagent_extend возвращает extended: false, когда задача уже не подвисшая: это повод обновить состояние, а не ошибка.
Метод session/subagent_details не отдаёт сырую историю модели: она не ограничена по размеру и утащила бы в клиент содержимое файлов, которых родитель не видел.
Вместо неё поле history несёт разговор субагента строками для показа (user, model, tool с ответом инструмента, summary для снимка сжатия); каждый текст обрезан, а в списке остаются только последние 400 записей.
У строки user, которой пользователь продолжил субагента с его экрана, есть policy — политика инструментов того хода; у исходного поручения и обычного возобновления поля нет.
В отличие от ленты активности история переживает перезапуск.
Рядом идёт contextUsage: сколько токенов занял последний запрос субагента по счёту провайдера (usedTokens) против лимита его модели (maxTokens).
То же поле есть в снимке задачи в subagent_update и session/subagent_list.
Пока субагент не отправил ни одного запроса, а также у сессий, записанных до появления поля, там null: клиент показывает «неизвестно», а не оценку, которой не соответствовал ни один запрос.
Разговор с субагентом¶
Метод session/subagent_prompt продолжает завершённого субагента репликой пользователя.
Субагент сохраняет свою историю и описание, но отвечает человеку, а не родительской модели: результат такого хода в доставку не попадает, и агент о нём не узнаёт.
Ход не блокируется выключенным subagents.enabled: переключатель ограничивает то, что запускает модель, а не человека.
Поле policy задаёт инструменты на этот ход: read_only (по умолчанию, тот же набор, что у исследования), auto_edit добавляет правку и запись файлов, yolo ещё и выполнение команд.
Уровня «спросить» нет: субагенту некому задать вопрос, поэтому всё, что политика добавляет, работает без подтверждений.
Терминальный снимок такого хода приходит обычным subagent_update, только без deliveryId.
Метод session/subagent_compress заменяет всю историю завершённого субагента одним снимком и возвращает счётчики токенов до и после.
Работающего субагента сначала нужно остановить; пока идёт сжатие, session/subagent_prompt и session/subagent_restart по этой задаче отвечают ошибкой.
Метод session/cancel во время хода доставки останавливает только сам ход, но не завершает субагентов: доставка вернётся в очередь и повторится.
Завершает субагентов только session/subagent_cancel.
Это отличие от терминала, где Esc останавливает ход вместе с субагентами: у ACP-клиента есть свой способ остановить их отдельно, и решение остаётся за ним.
Подтверждение доставки уходит после того, как модель ответила на результат: при восстановлении после сбоя пара «результат — ответ модели» либо признаётся уже потреблённой, либо откатывается из истории, чтобы результат не был показан дважды.
Типы контента¶
Массив prompt в session/prompt и блоки контента в обновлениях используют одни и те же типы:
| Тип | Поля | Назначение |
|---|---|---|
text |
text, annotations |
текстовый фрагмент |
image |
data, mimeType, uri, annotations |
изображение (base64) |
audio |
data, mimeType, annotations |
аудио (base64) |
resource_link |
uri, mimeType, name, title, size, description |
ссылка на ресурс (например, файл или веб-источник) |
resource |
resource: text/blob + mimeType, uri |
встроенный ресурс |
resource_link с file:// URI обрабатывается как @-ссылка на файл проекта: агент читает файл локально либо через fs/read_text_file, если клиент объявил поддержку.
Результаты веб-поиска и поиска по документации приходят как resource_link, чтобы клиент мог сделать их кликабельными.
Ошибки¶
Ошибки возвращаются в стандартном формате JSON-RPC 2.0:
{
"jsonrpc": "2.0",
"id": 5,
"error": {
"code": -32000,
"message": "Authentication required",
"data": {
"details": "Details of the error",
"reason": "not_found"
}
}
}
Стандартные коды JSON-RPC:
| Код | Значение |
|---|---|
-32700 |
parse error |
-32600 |
invalid request |
-32601 |
method not found |
-32602 |
invalid params |
-32603 |
internal error |
Коды Koda CLI:
| Код | Значение | data.reason |
|---|---|---|
-32000 |
authentication required | — |
-32001 |
session restore unavailable | not_found, incompatible |
-32002 |
session fork unavailable | not_found, incompatible, message_not_found |
Поле data.details содержит пояснение причины, а data.reason — машиночитаемый код для типизированной обработки клиентом.
При невалидных параметрах агент возвращает -32602 с детализацией ошибок валидации.