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

Спецификация 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 при инициализации.

Жизненный цикл соединения

Типичный сценарий работы клиента с сервером:

  1. initialize — согласовать версию протокола и получить возможности агента;
  2. authenticate — выбрать способ аутентификации (если нужно);
  3. session/new — создать сессию;
  4. session/prompt — отправлять запросы, получая потоковые обновления session/update;
  5. session/cancel (уведомление) — отменить выполняющийся запрос при необходимости;
  6. закрыть 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, объявленных агентом.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "authenticate",
  "params": {
    "methodId": "koda_auth"
  }
}

Ответ — 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 отменяет предыдущий незавершённый запрос той же сессии.

Ответ

{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "stopReason": "end_turn"
  }
}

stopReason может принимать значения:

Значение Что означает
end_turn ответ завершён
max_tokens достигнут лимит токенов
max_turn_requests слишком много обращений к модели за один ход
refusal модель отказалась отвечать
cancelled запрос отменён через session/cancel или новым session/prompt

Почему остановился ход

Два случая CLI обрывает сам:

  • Зацикливание. Детектор циклов увидел повторяющиеся вызовы инструментов или повторяющийся текст. Пользователю уходит объяснение обычным agent_message_chunk, а ответ приходит как end_turn с приметой в _meta: клиент, который её понимает, покажет это отдельной заметкой, а не очередным абзацем от модели.
{
  "stopReason": "end_turn",
  "_meta": { "com.kodadev.stopDetail": "loop_detected" }
}
  • Слишком много обращений к модели за один ход. Ответ приходит со стандартным stopReason: "max_turn_requests". Ответы уже выполненных инструментов остаются в истории, и следующее сообщение пользователя продолжает работу с этого места.

session/cancel (уведомление)

Отменяет выполняющийся запрос сессии:

{
  "jsonrpc": "2.0",
  "method": "session/cancel",
  "params": {
    "sessionId": "c8f8e0a1-..."
  }
}

Уведомление не имеет ответа. Текущий 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
  }
}

Ответ:

{
  "jsonrpc": "2.0",
  "id": 102,
  "result": {
    "terminalId": "terminal-1"
  }
}

Терминалы применяются для 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 с детализацией ошибок валидации.