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

Headless-режим

v0.1.6

Headless-режим Koda CLI выполняет запрос без интерактивного интерфейса и возвращает результат в stdout. Он предназначен для скриптов, пайплайнов CI/CD и программных клиентов, которым нужен машинночитаемый ответ.

В этом режиме TUI не рендерится: CLI принимает запрос, работает с моделью и инструментами, а затем завершает процесс с кодом, по которому можно судить об исходе.

Все флаги запуска перечислены в разделе неинтерактивного режима справочника аргументов.

Когда включается headless-режим

Headless-режим активируется автоматически, если соблюдено одно из условий:

  • передан флаг -p (или --prompt) с текстом запроса;
  • ввод подаётся через stdin, когда процесс запущен не в TTY-окружении (например, через конвейер);
  • задан флаг --input-format stream-json, даже если stdin подключён к терминалу.

В следующем примере команда передаётся через echo, а результат выводится в терминал:

echo "Что такое тонкая настройка?" | koda

Тот же результат достигается флагом --prompt:

koda -p "Что такое тонкая настройка?"

Совместимость способов запуска

Содержимое stdin добавляется к тексту --prompt с разделителем в виде пустой строки. Так можно комбинировать директиву из скрипта с динамическим вводом из конвейера.

Ввод

Источники ввода для headless-режима:

  • флаг --prompt;
  • stdin, если процесс запущен не в TTY и ввод не пуст;
  • комбинация: stdin добавляется к запросу из --prompt.

Лимит объёма stdin — 8 МБ. При превышении ввод усекается до лимита, а в stderr выводится предупреждение.

Проброс ввода через конвейер

Флаг --prompt-interactive (-i) нельзя использовать вместе с вводом из stdin. Если -i передан, а процесс не запущен в TTY, CLI завершается с ошибкой и кодом 1.

Флаг --input-format stream-json заменяет одиночный ввод потоком сообщений: stdin читается построчно, а --prompt и --prompt-interactive с ним несовместимы.

Для работы без интерактивного интерфейса настройте способ аутентификации заранее через переменные окружения: KODA_AUTH_ACCESS_TOKEN, KODA_AUTH_REFRESH_TOKEN или KODA_API_KEY. Подробнее — в разделе аутентификации.

Вывод

В headless-режиме stdout и stderr разделены:

Поток Что попадает
stdout ответ модели или машиночитаемый вывод в выбранном формате
stderr reasoning модели, служебные сообщения и ошибки

Формат задаёт флаг --output-format:

Значение Что печатает
text (умолчание) только ответ модели в stdout
json один JSON-объект с результатом и метриками в конце прогона
stream-json поток событий NDJSON: одно событие — одна строка stdout

Reasoning выводится при флаге --show-reasoning. В режимах text и json он идёт в stderr, а в stream-json — отдельными событиями thought.

Последовательные переводы строк в ответе схлопываются до двух подряд, чтобы вывод оставался компактным. Правило одинаково действует и в текстовом ответе, и в поле result структурированного вывода.

Если вывод направлен в команду, которая завершилась раньше, обрыв канала считается штатным завершением: процесс выходит с кодом 0, не порождая ошибку EPIPE.

Формат json

v1.3.0

koda -p "Опиши проект" --output-format json | jq -r '.result'
{
  "type": "result",
  "stop_reason": "final",
  "is_error": false,
  "result": "Финальный ответ модели",
  "session_id": "0f1c…",
  "prompt_id": "a7b3…",
  "num_turns": 3,
  "duration_ms": 8421,
  "usage": {
    "tokens": {
      "prompt": 10240,
      "candidates": 512,
      "cached": 8192,
      "thoughts": 96,
      "tool": 48,
      "total": 10752
    },
    "models": {
      "koda-1": {
        "requests": 3,
        "errors": 0,
        "latency_ms": 7310,
        "tokens": {
          "prompt": 10240,
          "candidates": 512,
          "cached": 8192,
          "thoughts": 96,
          "tool": 48,
          "total": 10752
        }
      }
    },
    "tools": {
      "calls": 2,
      "success": 2,
      "fail": 0,
      "duration_ms": 640
    }
  },
  "error": null
}

Объект печатается с отступами, поэтому читайте stdout целиком: построчный разбор рассчитан на режим stream-json.

Поля:

  • stop_reason — причина завершения: final (модель ответила), limit (упор в лимит витков), loop (обнаружен цикл), cancel (прогон отменён), error;
  • is_error — true только при stop_reason: "error";
  • result — финальный текст ответа, тот же, что печатает текстовый режим;
  • session_id и prompt_id — идентификаторы сессии и запроса;
  • num_turns — сколько витков агента отработало;
  • duration_ms — длительность прогона;
  • usage — токены по моделям и суммарно, число запросов и вызовов инструментов;
  • error — текст ошибки или сообщения о лимите, иначе null.

Денежная стоимость не считается: у CLI нет таблицы цен.

Формат stream-json

v1.3.0

Каждая строка — отдельный JSON-объект. Порядок событий: init, затем события прогона, последним — result с той же структурой, что и в режиме json.

type Поля Когда
init session_id, prompt_id, model в начале прогона
thought text «мысль» модели (только с --show-reasoning)
content text фрагмент ответа модели
tool_request call_id, name, args модель запросила инструмент
tool_result call_id, name, error, display инструмент отработал
notice text служебное сообщение: ошибка инструмента, лимит
result как в режиме json в конце прогона
koda -p "Напиши стих" --output-format stream-json |
  jq -rj 'select(.type == "content") | .text'

Коды завершения

v1.3.0

Код выхода различает успех и причину остановки:

Код stop_reason Значение
0 final модель дала финальный ответ
1 error ошибка API или прогона, а также некорректная строка ввода stream-json
2 limit исчерпан лимит витков
3 loop обнаружен цикл, прогон остановлен досрочно
4 cancel прогон отменён

Упор в лимит и обнаруженный цикл раньше завершались с кодом 0, поэтому программно их нельзя было отличить от успеха. Теперь вызывающий скрипт видит причину по коду выхода.

Ошибки разбора аргументов и несовместимые комбинации флагов завершают процесс до старта прогона с кодом 1. Если не передан --prompt и stdin пуст, процесс также завершается с кодом 1.

Ограничение числа шагов

Максимальное число ходов модели регулируется настройкой maxSessionTurns или флагом --max-turns. Остальные настройки — на странице настройки.

Отчёт об использовании

v1.3.0

В режимах json и stream-json отчёт всегда входит в поле usage. В текстовом режиме его выводит флаг --show-usage: отчёт печатается в stderr, чтобы не засорять stdout.

koda -p "Опиши проект" --show-usage

Отчёт содержит число витков, длительность, токены и количество вызовов инструментов.

Лимит витков

v1.3.0

koda -p "Прогони тесты и исправь падения" --max-turns 20

Флаг перекрывает настройку maxSessionTurns и принимает целое число не меньше 1. Упор в лимит даёт код выхода 2 и stop_reason: "limit".

Контроль инструментов

v1.3.0

koda -p "Почини auth.ts" --approval-mode yolo \
  --allowed-tools read_file,write_file,search_file_content
koda -p "Разберись в коде" --disallowed-tools run_shell_command
  • --allowed-tools оставляет модели только перечисленные встроенные инструменты — как настройка coreTools, но на один запуск;
  • --disallowed-tools исключает перечисленные инструменты и применяется поверх --allowed-tools;
  • оба флага принимают список через запятую или повторяются несколько раз.

Флаги не заменяют режим подтверждений: тот решает, спрашивать ли согласие, а эти — какие инструменты модель вообще видит. В неинтерактивном режиме с --approval-mode default инструменты, требующие подтверждения, исключаются автоматически.

Добавка к системному промпту

v1.3.0

gh pr diff 123 | koda -p \
  --append-system-prompt "Ты инженер по безопасности. Ищи уязвимости." \
  --output-format json

Текст дописывается в конец системного промпта после памяти проекта из KODA.md. Файлы проекта при этом не меняются.

Слеш-команды в -p

Если запрос начинается с /, он исполняется как слеш-команда:

koda -p "/compress"
koda -p "/memory add проект собирается через npm run build"
  • команда, выводящая текст, печатает его и завершает прогон без обращения к модели;
  • команда, разворачивающаяся в промпт, отправляет этот промпт модели;
  • команда, запрашивающая инструмент, исполняет его напрямую;
  • команды, которым нужен интерактивный диалог или подтверждение, отклоняются с понятным сообщением.

В режимах json и stream-json служебные сообщения команд уходят в stderr, чтобы stdout оставался машиночитаемым.

Только для одиночного запроса

В режиме --input-format stream-json слеш-команды не исполняются: строка со / уходит модели как обычное сообщение.

Многовитковый диалог

v1.3.0

Флаг --input-format stream-json превращает stdin в поток сообщений: каждое сообщение обрабатывается в той же сессии, и на каждое печатается свой result. Так программа ведёт диалог с одним процессом Koda.

printf '%s\n' \
  '{"type":"user","text":"перечисли файлы в src"}' \
  '{"type":"user","text":"а теперь опиши самый большой"}' \
  '{"type":"control","action":"end"}' |
  koda --input-format stream-json --output-format stream-json

Поддерживаемые строки:

Строка Значение
{"type":"user","text":"…"} сообщение пользователя, короткая форма
{"type":"user","message":{"content":"…"}} то же, совместимо с другими агентными CLI
{"type":"user","message":{"content":[{"type":"text","text":"…"}]}} текст по частям
{"type":"control","action":"end"} завершить диалог, как конец потока

Пустые строки игнорируются. Строка, которая не разбирается, завершает прогон с кодом 1 и stop_reason: "error". Прогон также обрывается на первом сообщении с ненулевым кодом: лимит витков, цикл или ошибка. Пустой поток завершается с кодом 0.

Событие init печатается один раз в начале диалога, а каждый result содержит свой prompt_id. Идентификатор сессии session_id общий для всего диалога.

Несовместимость с одиночным вводом

--input-format stream-json нельзя использовать вместе с --prompt и --prompt-interactive: сообщения приходят только из stdin.

Подтверждение действий

По умолчанию в неинтерактивном режиме инструменты, требующие подтверждения, отключаются, чтобы агент не выполнял действия без вашего согласия.

Выбор режима подтверждения задаётся флагом --approval-mode:

Режим Что происходит
default инструменты с подтверждением отключены
auto_edit автоматически подтверждаются инструменты редактирования файлов
yolo автоматически подтверждаются все инструменты

Допустимо использовать -y (--yolo) вместо --approval-mode yolo, но не одновременно.

Управление компьютером

Инструмент computer_use в неинтерактивном режиме доступен только при --yolo: этот запуск не умеет спрашивать согласие на ввод. В остальных режимах инструмент не предлагается модели.

Примеры автоматизации

Простой запрос с флагом --prompt:

koda -p "Опиши структуру каталога src/"

Отправка вывода конвейером, reasoning не показывается:

cat service.log | koda -p "Найди ошибки в этом логе"

Запрос с заданной моделью и подтверждением правок:

koda -p "Почини линтер" --approval-mode=auto_edit -m my-model

Использование в CI: выполнить запрос и считать результат из stdout:

RESULT=$(koda -p "Сгенерируй git-сообщение для этих изменений")
echo "$RESULT"

Машиночитаемый результат для скрипта:

koda -p "Найди неиспользуемые функции" --output-format json |
  jq -r 'select(.stop_reason == "final") | .result'

Проверка причины остановки по коду выхода:

koda -p "Прогони тесты" --max-turns 10
case $? in
  0) echo "готово" ;;
  2) echo "упёрлись в лимит витков" ;;
  3) echo "обнаружен цикл" ;;
  *) echo "ошибка" ;;
esac

Ограничения

  • Интерактивные возможности TUI в headless-режиме недоступны.
  • Подтверждение инструментов по умолчанию выключено, что защищает от нежелательных действий.
  • Слеш-команды исполняются только в режиме одиночного запроса.
  • Продолжения прошлой сессии (--continue и --resume) и вывода по JSON-схеме нет.
  • Стоимость запроса в деньгах не рассчитывается: у CLI нет таблицы цен.