В марте я писал на Хабре, как для Claude Code пришёл к связке Superpowers + Beads + Template Bridge и отказался от собственного Orchestrator Kit на 39 агентов — 3000+ часов в Claude Code. Там у меня была ровно одна мысль на финал: AI заменяет набивание символов, но не инженерию. Мультипликатор — он мультипликатор: умножает ноль на сотню — получает ноль; умножает грамотный контракт — получает результат.
Эту мысль я в марте написал. И полгода после этого аккуратно её игнорировал — уже в Codex.
В Codex у меня всё это время лежал один большой AGENTS.md. Реально один файл, килобайт на двадцать. Внутри были правила: использовать Beads для задач, не пропускать verification, спрашивать перед опасными операциями, при длинном этапе делегировать subagents. Я туда добавлял всё, на что натыкался. По вечерам казалось, что я строю надёжную систему.
Реальность была другая. Оркестратор уверенно прочитывал контракт, кивал, и дальше начинал тихо срезать углы. Расскажу как именно.
Что значит «тихо срезать углы»
Сценарий простой. Я даю задачу: «Разнеси модуль X на два слоя, добавь регрессионный тест, проверь, что старые тесты не падают». В AGENTS.md записано: для medium/complex задач делегируй параллельно subagents в отдельных видимых run-ах, в каждом — свой write zone, в каждом — verification.
Оркестратор отвечает что-то в духе «хорошо, приступаю». Через полчаса я смотрю diff. Один большой коммит, всё сделано из главной сессии. Никаких subagents. Никакого параллелизма. Тест приклеен в файл с реализацией. Verification — «я запустил pytest, всё зелёное», без вывода в логе.
Я возвращаюсь, спрашиваю: «А почему не делегировал?». Ответ: «Задача показалась мне достаточно простой, чтобы выполнить локально». Формально не наврал. По духу контракта — наврал, потому что подменил «делегировать medium/complex» на «делегировать только когда я уверен, что не справлюсь».
Это и есть тихая глупость. Не баг модели. Не злая воля. Просто расплывчатый контракт даёт агенту дофига свободы в интерпретации, и в среднем он выберет более лёгкий путь. У меня в AGENTS.md не было ни одной механики, которая физически заставила бы его сначала построить таблицу streams, а потом уже принимать решение.
Полгода я думал, что у меня просто недописан AGENTS.md. Добавлю строжайшее правило — и заработает. Не работало. К концу марта файл вырос до тридцати с лишним килобайт, оркестратор всё так же делал «как удобнее». И на одном из таких diff-ов меня наконец накрыло: проблема не в правилах. Проблема в том, что одно полотно правил — это не контракт. Это эссе на тему «как было бы хорошо работать». Эссе никто не исполняет.
Главный тезис: оркестратор — это диспетчер контрактов
Простая формулировка, которая мне самому стоила нескольких месяцев:
Оркестратор не должен быть самым умным промптом. Он должен быть диспетчером контрактов.
Под «контрактом» я имею в виду буквально вот это: жёсткая структура, в которой записано — что именно сейчас происходит, кто это делает, в какой write zone, как проверим, когда стоп. Без неё оркестратор всегда найдёт способ сделать работу проще, чем планировал я.
Из мартовской статьи я тогда вытащил для Claude Code совсем не то, что нужно было вытащить. Я взял идею «интегрировать готовое вместо своего» — поставил Superpowers, Beads, Template Bridge, удалил Orchestrator Kit, написал статью. Полгода был доволен. А по-настоящему важная мысль про мультипликатор пролежала рядом и не сработала, потому что в Codex я её не приземлил. Контракта на агента не было — он умножал свою сотню на расплывчатый ноль и выдавал ноль красиво.
В Claude Code я ушёл к интеграции готового. В Codex с этим хуже — экосистема скиллов и плагинов пока тоньше, готового collaborative-workflow нет. Пришлось собирать своё, но уже не как один большой промпт, а как маленькую систему из четырёх отдельных skill.
Четыре skill вместо одного полотна
Сейчас у меня в ~/.agents/skills/ лежат четыре связанных skill. Каждый — со своим SKILL.md, своими шаблонами, своими support-скриптами. Между собой они общаются через файлы в .codex/ и через Beads.
|
Skill |
Когда срабатывает |
Что делает |
|---|---|---|
|
|
Новый проект, или ауд драфта |
Создаёт repo-local baseline: AGENTS.md, |
|
|
Активная работа над medium/complex задачей |
Читает baseline, выбирает Beads task, строит Parallel Decomposition Matrix, запускает subagents, ревьюит. |
|
|
Substep внутри stage |
Подбирает актуальные docs, нужные skills, нужных custom agents. Не заменяет процесс — только маршрутизирует. |
|
|
Конец этапа |
Проверяет accepted artifacts, гоняет verification, обновляет Beads и handoff, чистит worktrees/branches. |
Логика разделения: lifecycle проекта (setup), lifecycle этапа (stage), routing внутри этапа (router), безопасное закрытие (closeout). Это разные режимы мышления — и смешивать их в одном AGENTS.md примерно так же плохо, как держать одну функцию длиной 2000 строк, потому что «всё связано».
Самый ценный — orchestration-closeout. Когда я отдельным skill сделал закрытие этапа, у меня закончились silent debt. До этого я ловил остатки от субагентов через неделю: открываешь репо, видишь странный worktree, не помнишь зачем он. Сейчас этап нельзя закрыть, не пройдя evidence-checks.
(Кстати, эта же логика — почему в Anthropic для Claude Code появились отдельные skills вроде verification-before-completion и using-git-worktrees вместо одного «Engineering CLAUDE.md». Лучшая структура — несколько мелких контрактов, активирующихся по контексту, а не один большой эссе.)
Repo-local baseline: .codex/orchestrator.toml и компактный AGENTS.md
AGENTS.md я не удалил. Я его сильно усохнул. Теперь он стал human-facing и короткий:
-
как устроен проект;
-
какие entrypoints;
-
какие verification commands;
-
где состояние;
-
когда использовать orchestration;
-
что Beads = task truth;
-
что Superpowers обязательны.
Всё остальное переехало в .codex/orchestrator.toml — это уже machine-readable контракт. Вот фрагмент из текущего baseline (balanced-v2.12):
[baseline]profile = "balanced-v2.12"source_skill = "orchestration-setup"[delegation]launcher = "codex_subagents"subagent_visibility = "separate_spawned_threads"inline_subagents_allowed = falserequires_explicit_user_spawn_request = trueparallel_decomposition_matrix = "required_for_medium_complex"parallel_execution_default = "spawn_all_independent_streams"sequential_requires_reason = true[subagent_model_policy]default_model = "inherit_orchestrator"default_reasoning_effort = "inherit_orchestrator"reasoning_policy = "complexity_based"model_override_requires_current_user_authorization = truerecord_model_reasoning_rationale = true
Два поля, которые поменяли мою жизнь больше всего: inline_subagents_allowed = false и parallel_decomposition_matrix = "required_for_medium_complex". Про оба ниже отдельно.
Структура .codex/ рядом:
AGENTS.md.codex/ orchestrator.toml # machine-readable контракт handoff.md # ТОЛЬКО текущее состояние, не история project-index.md # стабильная карта проекта stage-artifact-template.md subagent-task-contract.md stages/ # история этапов: summary, artifacts, evidencescripts/orchestration/ run_process_verification.sh validate_artifact.py check_stage_ready.py run_stage_closeout.py cleanup_stage_workspace.py report_child_completion.py review_completion_inbox.py
Главная дисциплина здесь — handoff.md не должен становиться историей. Это файл «где я сейчас и что дальше», два-три абзаца. История этапов уходит в .codex/stages/ — туда складываются summary, accepted/rejected artifacts, verification evidence, явные defers. Промежуточное состояние между «всё в чате» и «ничего не сохраняем». Через месяц можно открыть конкретный stage и увидеть, почему именно этот stream выделили в отдельный worktree.
Parallel Decomposition Matrix: обязательный этап перед делегированием
Это центральная штука. До неё параллелизм у меня был декоративным: оркестратор писал «запускаю s1 и s2 параллельно», а на деле подразумевал «сделаю s1, потом s2». После — параллелизм стал настоящим.
Правило в orchestrator-stage: до старта реализации medium/complex задачи оркестратор обязан построить таблицу. Не «обдумать декомпозицию», а конкретно — таблицу с полями.
|
Stream |
Goal |
Agent |
Write zone |
Dependencies |
Verification |
Model/reasoning |
Decision |
Reason |
|---|---|---|---|---|---|---|---|---|
|
|
Изменить backend contract |
|
|
none |
|
inherit/high |
parallel |
isolated write zone |
|
|
Обновить UI state |
|
|
waits for s1 schema |
|
inherit/medium |
sequential |
depends on s1 contract |
|
|
Проверить API docs |
|
read-only |
none |
source citation |
role_default |
parallel |
read-only |
Дальше — простое правило: если есть два или более independent streams и subagents разрешены, оркестратор должен запускать их параллельно. Sequential допустим только с конкретной причиной.
И вот эти «причины» — отдельная тема. Раньше оркестратор писал «решил сделать последовательно, потому что файлы связаны». Это плохая причина — почти всегда означает «мне лень». Сейчас в контракте есть список приемлемых причин:
-
dependency chain (s2 правда ждёт схему от s1);
-
write conflict (оба stream трогают один файл);
-
shared verification bottleneck (тестовый цикл неразделим);
-
shared external resource (один внешний API с rate limit);
-
uncertain scope (ещё непонятно, во что превратится stream);
-
repo limit (политика репо запрещает параллельные изменения в этой зоне).
«Файлы связаны» — отсутствует. Если не подпадает ни под одну причину — параллелим.
Реальный кейс из прошлого этапа на одном из моих проектов. Я отдавал на параллель три стрима: backend-контракт, UI-стейт, обновление docs. Backend и docs запустились параллельно. UI — сделали sequential после backend. Причина в матрице была записана так: «stream s1 (backend) меняет shape ответа API, stream s2 (UI) расходует этот shape для типизации — sequential, потому что без стабильного contract из s1 типы будут переписываться дважды». Это нормальная причина. Когда я её прочитал в матрице, я согласился.
А в начале этого года я бы получил то же самое — но без матрицы, и без причины. Просто всё было бы sequential, и я бы потом неделю не понимал, почему «параллельная разработка» не ускоряет.
(Кстати, это очень напоминает классическую историю с микросервисами. Параллельная разработка без disjoint write zones — это не ускорение, это генератор merge-конфликтов. Тот же эффект, что у людей с микросервисами без bounded contexts. Архитектурные ошибки повторяются на новом уровне абстракции, только теперь между не людьми, а агентами.)
Subagents — только видимые spawned, и тот самый промт авторизации
Тут самая важная сюжетная развилка. Расскажу с самого начала.
Когда я начинал ковырять delegation в Codex, я делал её руками. Codex давал мне готовый промт-пакет для subagent, я открывал отдельную сессию, копировал, запускал, потом возвращался с результатом, ревьюил. Утомительно, но я видел всё своими глазами: вот отдельный run, вот его лог, вот его diff. Наблюдаемость — стопроцентная.
Codex со временем сделал кликабельных видимых spawned subagents: когда оркестратор делегирует задачу, в интерфейсе появляется отдельный run с именем агента, в него можно зайти, посмотреть его контекст и его действия. Это закрыло почти весь мой manual fallback — наблюдаемости через UI хватает.
Но я наступил на одни грабли, и теперь не отступаю от формулы. Если в стартовом промте задачи нет прямого разрешения на spawned subagents — Codex имеет право делегировать inline, то есть продолжать работу в той же сессии под видом «я тут думаю», без отдельного run-а. Я узнаю об этом только по diff-у, постфактум.
После одного такого случая я сделал две вещи. Первая — записал в .codex/orchestrator.toml:
inline_subagents_allowed = falserequires_explicit_user_spawn_request = true
Вторая — в стартовом промте каждой большой задачи теперь живёт ровно одна и та же фраза:
I explicitly authorize you in this current task to spawn separatevisible Codex subagents when justified. Do not use inline-only delegation.
Эта фраза работает по двум причинам. Первая — Codex по документации требует явного user request для spawning subagents, потому что это «новый процесс», и они не хотят, чтобы агент запускал агентов на свой страх и риск. Без фразы — formally агент может legally не запускать subagents и делать всё локально. Вторая — фраза мгновенно решает спор «инлайн или нет». Не «когда сочтёшь нужным, можешь делегировать», а «делегируй, и делай это видимым».
Нет, подождите. Не совсем «всегда». У меня осталась одна песочница, где manual fallback живёт до сих пор. Там runtime сам по себе режет spawning (его развернули в режиме «без вложенных процессов»), и приходится возвращаться к ручным prompt pack-ам. Manual fallback есть в orchestration-setup как отдельный шаблон именно для таких случаев. Но дисциплина не меняется: даже manual prompt должен содержать Beads task, docs, asset routing, write zone, verification, stop rules. Manual не значит халтурно.
Я долго не верил, что простая фраза в стартовом промте может что-то менять. Считал, что если у меня в repo contract всё прописано — runtime должен слушаться. Не должен. Runtime слушается user в текущей сессии, не репозиторий. Урок выученный, дорогой.
Worker contract: расплывчатой задачи мало
Когда оркестратор spawned subagent — он не должен говорить ему «посмотри и сделай». Worker должен получить контракт с минимальным набором блоков. Вот реальная форма из шаблона subagent-task-contract.md:
Task ID: mc2-db696.3Stage ID: mc2-db696Agent Type: workerVisibility: separate spawned Codex agent/thread/run; inline-only delegation is not allowedModel: inherit_orchestratorReasoning Effort: highModel/Reasoning Rationale: cross-module graph/state change with regression risk## GoalImplement group 3 loader flow without touching judge/regenerator code.## Success Criteria- Loader handles empty state- Existing graph tests still pass- New regression test covers missing headings## Documentation- No dependency documentation lookup needed## Asset Routing- Selected docs: none — repo-local behavior- Selected skills: superpowers:test-driven-development- Selected agents/personas: worker- Catalog candidates: none — installed assets sufficient## Context And Ownership- Workspace root: /home/me/code/mc2- Branch/worktree: dedicated worktree required- Parallel group: stream s3; siblings s4 judge, s5 assembler- Write zone: src/graph/loader.ts, tests/loader.spec.ts## Verification- Run: pnpm test -- loader## Stop RulesReturn blocked if state contract changes require touching sibling streams.
Зачем такая бюрократия. Каждое поле закрывает один конкретный класс «тихой глупости» субагента.
Write zone — закрывает «случайно влезу в чужой файл и сломаю sibling-стрим». Stop rules — закрывает «увлекусь и переделаю архитектуру вместо того, чтобы вернуть blocked». Verification — закрывает «скажу, что прошло, не запустив». Asset Routing — закрывает «потрачу 20 минут на повторное catalog discovery». Parallel group — закрывает «не пойму, что я не одинок, и накачу свой подход на тот же файл».
Это всё не про недоверие к субагенту. Это про то, что расплывчатую задачу честный субагент тоже выполнит расплывчато. Нечего ему делать.
Completion event ≠ acceptance
Subagent вернулся с результатом. В систему упало событие completion: report_child_completion.py записал в inbox, что worker отчитался. Это не значит, что работа принята.
В прошлой статье я писал про скилл verification-before-completion от Superpowers: «доказательства до утверждений». В Codex я ту же идею протащил на уровень целого этапа — там разделены четыре сущности:
-
completion event — «worker вернулся»;
-
artifact — «что worker утверждает»;
-
orchestrator review — «принято или нет»;
-
local verification — «доказано или нет».
Я несколько раз обжигался ровно на разнице между первым и последним. Subagent возвращался, оркестратор пожимал плечами «ну тесты у него зелёные, наверное», и принимал stream. Потом на closeout выяснялось, что worker запустил один тестовый файл, а не весь pytest suite. Stream формально принят, но verification — фуфло.
Сейчас artifact обязан фиксировать verification — какую команду запустили, какой вывод получили. accepted_by_orchestrator: yes нельзя поставить без verification evidence. На closeout эту цепочку ещё раз проверяет run_stage_closeout.py. Это не паранойя — это просто закрытие очевидной дыры, через которую агент с удовольствием тихо проскальзывал.
Что я убрал и почему: Beads hooks
В прошлой статье я хвалил bd prime hook на старте сессии Claude Code: новая сессия — автоматически подтягиваются текущие задачи из Beads. Удобно.
В Codex я тот же подход поначалу пытался воспроизвести через hook-и. И вот тут поймал классическую боль: «вчера работало, сегодня не работает, никаких изменений». Hook не сработал из-за порядка инициализации. Hook не сработал в worktree. Hook не сработал в режиме --ask-for-approval. Каждый раз я тратил минут двадцать на «а почему».
Решение — взять и убрать hook вообще. Теперь оркестратор работает с Beads явными командами: bd ready, bd create, bd update --claim. Никакой магии на старте, никакого автоматического подтягивания. Кажется хуже — на деле:
-
меньше скрытого поведения;
-
проще перенести между средами;
-
легче объяснить новому агенту;
-
никаких «а почему сработало вчера».
Я честно колебался. Hook давал ощущение «всё само». Но иллюзия «всё само» обходилась мне дорого — отлаживать невидимую инициализацию хуже, чем явно написать одну строчку в начале промта. Прагматичная инженерия здесь оказалась честнее, чем элегантная автоматизация.
Где система проигрывает manual fallback
Не буду делать вид, что новая система — серебряная пуля. Несколько честных мест.
Стоимость на маленькие задачи. Если задача правда простая (исправить опечатку, переименовать переменную) — вся эта обвязка только тормозит. В orchestrator-stage есть классификация: для simple задач оркестратор делает всё локально, без церемонии. Но граница между simple и medium субъективная — пару раз оркестратор отнёс к simple то, что я считал medium. Тут оркестратор всё ещё умнее меня примерно в 90% случаев и тупее меня примерно в 10%.
Среды, где spawning не работает. Если runtime запущен в режиме без вложенных subagents — система деградирует до manual fallback, и часть ускорения теряется. Manual prompt pack у меня есть, он остаётся валидным контрактом, но скорость падает примерно вдвое.
Старые worktree-копии не обновляются автоматически. Я обновляю baseline в основном репо, но старые временные worktree остаются на предыдущей версии. Это нормально, но требует помнить: closeout должен идти из основного worktree.
Custom agents — пока пустовато. Built-in роли (worker, explorer, docs_researcher, skill_scout) покрывают примерно 80% задач. Но реально полезные специализации (reviewer, architect, security-reviewer, migration-specialist) я пока не оформил как полноценных custom agents. Это в roadmap, но не сейчас.
Куда буду двигаться дальше
Главное, что хочется добавить — поведенческие проверки оркестратора. Сейчас audit_repo.py проверяет, что нужные файлы и поля на месте. Это структурная валидация. А мне нужны pressure-сценарии: задаю оркестратору medium/complex задачу — проверяю, что он построил Parallel Decomposition Matrix. Даю две независимые подзадачи — проверяю, что он реально spawn-ил параллельно, а не делегировал inline. Делаю completion без verification evidence — проверяю, что closeout его отклонил.
По сути — TDD для процесса оркестрации. Тесты не для модели (она и так делает что может), а для самих контрактов: где у них дырки, где агент проскальзывает.
Второе — stage dashboard. Сейчас состояние этапа размазано между Beads, .codex/handoff.md, .codex/stages/, completion inbox, git worktrees. Один CLI, который показывает «вот текущий этап, вот его streams, вот их статусы, вот dirty worktrees, вот безопасно удалить» — это сделает систему сильно наблюдаемее. Пока обхожусь git worktree list и bd ready, но это руками.
Архив скиллов и контакты
Все четыре skill в одном zip-архиве приложу к посту в моём Telegram-канале — t.me/maslennikovigor. Положил туда же README.md с инструкцией: куда распаковать, что нужно поставить рядом (Beads, Superpowers), какие поля настроить под свой репо. Baseline — balanced-v2.12, срез на сегодня. Через полгода что-то наверняка поменяется в Codex — обновлю baseline и выложу новую версию в тот же канал.
Если будут вопросы или замечания по контракту — пишите в @maslennikovig. GitHub с Claude Code-версией орк-кита — maslennikov-ig/claude-code-orchestrator-kit (это та самая система, от которой я отказался в марте; оставил как MIT для тех, кому нужен полный контроль и стабильный стек). Codex-версия — отдельный архив в Telegram, открытого репо под неё пока нет.
И последнее — то, ради чего я вообще писал статью. Если есть один тезис, который стоит унести с собой: система работает не потому, что агент умнее. Она работает потому, что агенту стало тяжелее сделать тихую глупость. Каждое поле в orchestrator.toml, каждая ячейка в Parallel Decomposition Matrix, каждый блок в worker-контракте — это закрытая дырка, через которую агент раньше проскальзывал. AI как мультипликатор работает только когда у него по обе стороны умножения стоит что-то, отличное от нуля. Контракт — это и есть то самое «что-то».
ссылка на оригинал статьи https://habr.com/ru/articles/1037064/