Модуль 1 · Codex без человека в цикле · урок 3 из 14
Ответ по схеме: JSON для программ
Текст в stdout хорош для человека, но плох для скрипта: формулировки плавают, разбирать их регулярками — источник ошибок. У codex exec есть два машинных режима вывода. Поток событий показывает, что агент делал по шагам. Ответ по схеме выдаёт итог в заданной вами форме. Разберём оба и соберём из них проверку, которая может остановить пайплайн.
Поток событий: --json
С флагом --json stdout превращается в поток JSON Lines: одна строка — одно событие.
codex exec --json "кратко опиши структуру репозитория" > run.jsonlТипы событий: thread.started, turn.started, turn.completed, turn.failed, item.* и error. Внутри item.* лежат элементы работы агента: сообщения, рассуждения, выполненные команды, изменения файлов, вызовы MCP-инструментов, веб-поиск, обновления плана. Примерно так выглядит поток:
{"type":"thread.started","thread_id":"0199a213-81c0-7800-8aa1-bbab2a035a53"}
{"type":"turn.started"}
{"type":"item.started","item":{"id":"item_1","type":"command_execution","command":"bash -lc ls","status":"in_progress"}}
{"type":"item.completed","item":{"id":"item_3","type":"agent_message","text":"В репозитории каталоги plugins, themes и tests."}}
{"type":"turn.completed","usage":{"input_tokens":24763,"cached_input_tokens":24448,"output_tokens":122,"reasoning_output_tokens":0}}С таким потоком удобно работать через jq:
# какие команды запускал агент и с каким кодом выхода
jq -r 'select(.type=="item.completed" and .item.type=="command_execution")
| "\(.item.exit_code)\t\(.item.command)"' run.jsonl
# итоговое сообщение агента (последнее agent_message)
jq -rs 'map(select(.type=="item.completed" and .item.type=="agent_message"))
| last | .item.text' run.jsonl
# расход токенов за ход — пригодится для учёта затрат
jq -c 'select(.type=="turn.completed") | .usage' run.jsonlЗачем это в агентстве. Поток событий — журнал аудита: видно, какие команды агент выполнял в пайплайне клиента и что менял. Сохраняйте его как артефакт job. Если агент «сделал что-то не то», без журнала вы будете гадать.
Финальный ответ по схеме: --output-schema
Если нужен не журнал, а данные для следующего шага, опишите форму ответа в JSON Schema и передайте её флагом --output-schema. Флаг -o (--output-last-message) запишет финальное сообщение в файл и при этом продублирует его в stdout.
Пример: оценка риска merge request перед ревью. Схема:
{
"type": "object",
"properties": {
"risk": { "type": "string", "enum": ["low", "medium", "high"] },
"summary": { "type": "string" },
"touched_areas": {
"type": "array",
"items": { "type": "string", "enum": ["payments", "auth", "migrations", "integrations", "frontend", "other"] }
},
"needs_manual_qa": { "type": "boolean" }
},
"required": ["risk", "summary", "touched_areas", "needs_manual_qa"],
"additionalProperties": false
}Запуск:
git diff origin/main...HEAD \
| codex exec \
--ephemeral \
--output-schema .codex/schemas/mr-risk.json \
-o mr-risk.json \
"Оцени риск этого изменения для продакшена. Платежи, авторизация и миграции БД — всегда не ниже medium."В mr-risk.json окажется объект ровно этой формы. Дальше с ним работает обычный скрипт:
risk=$(jq -r .risk mr-risk.json)
if [ "$risk" = "high" ]; then
echo "Высокий риск: нужен второй ревьюер"
exit 1
fiКак проектировать схему
Несколько правил, которые экономят время.
Строгая форма. Перечисляйте все поля в required и ставьте "additionalProperties": false — так сделано в примерах документации. Модель не будет добавлять «полезные» поля, которые сломают ваш парсер.
Корень — объект. Даже если по смыслу нужен массив, заверните его в объект: {"issues": [...]}. Потом jq '.issues' достанет массив в нужном формате. Этот приём понадобится в уроке про GitLab CI, где отчёт должен быть массивом в формате Code Quality.
Перечисления вместо свободного текста. Всё, по чему скрипт будет принимать решение, делайте enum: уровень риска, статус, категорию. Свободный текст оставьте для полей, которые читает человек.
Схема в репозитории. Храните схемы рядом с кодом, например в .codex/schemas/, и меняйте через MR. Схема — это контракт между Codex и вашим скриптом, и изменение контракта должно быть видно в ревью.
Промпт объясняет смысл полей. Схема задаёт форму, но не критерии. Что считать «high», когда ставить needs_manual_qa — это пишется в промпте. Лучше всего в файле промпта, который тоже лежит в репозитории.
Сбой — не пустой результат
Самая опасная ошибка в автоматизации — принять упавший запуск за чистый результат. В одном из примеров Cookbook OpenAI добавила к своему старому пайплайну именно такое предупреждение: неудачный запуск Codex или невалидный вывод нельзя считать отсутствием замечаний.
На практике это значит:
set -euo pipefail
codex exec --output-schema schema.json -o result.json "..." \
|| { echo "Codex завершился с ошибкой"; exit 1; }
# файл есть и в нём JSON нужной формы
jq -e 'has("risk") and has("summary")' result.json > /dev/null \
|| { echo "Ответ не соответствует ожиданиям"; exit 1; }Codex проверяет ответ по схеме сам, но вторая проверка на вашей стороне стоит одной строки и ловит обрыв сети, лимиты и пустые файлы. Если нужна полная валидация, используйте валидатор JSON Schema из своего стека.
Оба режима сразу
Флаги можно совмещать: --json пишет поток событий в stdout, а -o сохраняет финальный ответ по схеме в файл.
codex exec --json \
--output-schema .codex/schemas/mr-risk.json \
-o mr-risk.json \
"Оцени риск изменений в текущей ветке относительно origin/main" \
> codex-events.jsonlЖурнал идёт в артефакты для аудита, mr-risk.json — в следующий шаг пайплайна. Такую связку мы используем дальше во всех CI-сценариях курса.
Попробуйте сами
10–15 мин на рабочем месте- Запустите
codex exec --json "назови три самых больших файла в репозитории" > run.jsonlи вытащите черезjqсписок выполненных команд. - Напишите схему из трёх полей для своей задачи — например, классификации бага по описанию: компонент (
enum), серьёзность (enum), краткое описание. Прогоните её на двух-трёх реальных описаниях задач из трекера. - Добавьте в скрипт проверку
jq -eи убедитесь, что он падает, если удалить файл ответа.
Коротко
--jsonдаёт поток событий JSONL: команды, правки, сообщения, расход токенов. Сохраняйте его для аудита.--output-schemaвместе с-oдают финальный ответ заданной формы.- Схема строгая: все поля в
required,additionalProperties: false, корень — объект. - Решения скрипт принимает по полям
enum, а не по свободному тексту. - Упавший запуск или невалидный ответ — это ошибка, а не «замечаний нет».