# Эксперимент с координатором субагентов Status: Research Дата: 2026-06-11 ## Зачем проводили эксперимент Проверяли рабочую схему, где верхнеуровневый агент не реализует задачу сам, а управляет цепочкой субагентов: - выдаёт одному worker-субагенту один локальный issue; - требует от worker-а работать через `/goal` и `/tdd`; - получает отчёт; - просит того же worker-а сделать саморевью; - классифицирует находки; - запускает исправления только по важным пунктам; - при необходимости подключает отдельного reviewer-субагента; - сам выполняет финальную проверку, разделяет коммиты и принимает решение о переходе к следующей задаче. Цель эксперимента была не только закрыть задачи генератора, но и понять, можно ли из такого процесса сделать повторяемый агентный навык. ## Контекст Эксперимент проводился в ветке `feature/data-generator` на последних трёх срезах переработки steady-stream генератора: 1. `.scratch/feature-data-generator/issues/05-intensity-and-flow-calibration.md` 2. `.scratch/feature-data-generator/issues/06-state-v2-and-restart.md` 3. `.scratch/feature-data-generator/issues/07-service-integration-and-docs.md` Задачи были зависимыми и меняли близкие места генератора, поэтому параллелить их было нельзя. Каждая следующая задача должна была стартовать только после коммита предыдущей. Итоговые рабочие коммиты: - `8f1e997 feat(generator): откалиброван поток steady-stream генератора` - `640050e feat(generator): добавлено состояние версии 2 для рестартов` - `6c4e0f4 feat(generator): подключена новая модель к steady-stream сервису` Отдельные docs-коммиты фиксировали ход и выводы эксперимента. ## Начальная гипотеза Ожидалось, что полезное разделение ролей такое: - **Координатор** держит порядок, границы задач, проверки, коммиты и решение, какие замечания действительно надо чинить. - **Worker-субагент** глубоко погружается в один issue и реализует его через короткие TDD-срезы. - **Саморевью worker-а** полезно, потому что у него уже есть подробный контекст реализации. - **Отдельный reviewer-субагент** нужен не всегда, а на рискованных переходах или после больших исправлений. Верхнеуровневому координатору решили не включать `/goal`: это могло бы сместить поведение в сторону «закрыть большую цель любой ценой», тогда как задача координатора — управлять этапами и останавливаться на контрольных точках. ## Словарь ролей - **Координатор** — верхнеуровневый агент в текущей сессии. Он не пишет основную реализацию, а управляет порядком работ, проверяет границы, классифицирует замечания, запускает дополнительные проверки и делает коммиты. - **Worker** — субагент-исполнитель. Он получает один issue, работает в общем рабочем дереве, меняет файлы, запускает тесты, но не коммитит. - **Саморевью worker-а** — отдельная фаза после реализации. Worker временно меняет роль с исполнителя на проверяющего свою работу, но сначала только выдаёт находки, не исправляя их. - **Reviewer** — отдельный субагент для независимой проверки. По умолчанию он работает только на чтение: анализирует diff, issue, спеки и тесты, но не редактирует файлы. - **Находка** — гипотеза о проблеме, а не автоматическое требование правки. Решение принимает координатор. Важная техническая деталь: все агенты работают с одним рабочим деревом. Поэтому координатор обязан регулярно проверять `git status --short` и явно отделять рабочие изменения задачи от процессных заметок, handoff-файлов и чужих правок. ## Протокол, который сложился в эксперименте Рабочий цикл на задачу: 1. Проверить чистоту дерева и, если нужно, отделить процессные правки отдельным docs-коммитом. 2. Запустить нового worker-субагента на один issue. 3. В задании worker-у указать: - `/goal`-образную цель; - `/tdd` как методику; - границы задачи; - запрет на коммит; - запрет на откат чужих изменений; - требование финального отчёта: файлы, тесты, проверки, критерии, риски, `git status --short`. 4. После реализации запросить у того же worker-а саморевью без правок. 5. Классифицировать находки. 6. Вернуть worker-у только одобренные правки. 7. Запустить локальные проверки координатора. 8. На сложных местах запустить отдельного reviewer-а. 9. Снова классифицировать находки reviewer-а. 10. При больших исправлениях выполнить второй reviewer-круг. 11. Коммит делает координатор. 12. Старый worker закрывается; следующая задача стартует в новом worker-е. Это описание фиксирует фактический процесс эксперимента. Будущий скилл не обязан механически повторять каждый шаг, но должен сохранить инварианты ниже. ## Классификация reviewer-находок Reviewer не является источником истины. Его выводы работают как гипотезы, которые координатор обязан проверить и классифицировать: - `чинить до коммита` — реальный дефект, нарушение acceptance criteria или слабое доказательство ключевого поведения; - `записать как риск` — важно помнить, но не блокирует текущий коммит; - `ложная тревога` — reviewer неверно понял код, тест или границы задачи; - `вне скоупа` — может быть полезно позже, но не относится к текущему issue. Это оказалось критически важным. Без фильтрации reviewer легко превращается в источник расползания задачи. ## Наблюдения по задаче 05 Задача 05 была самой дорогой по обратной связи: калибровка статистической модели требует длинных симуляций и не даёт быстрый «один инвариант — один результат». Worker долго работал без промежуточного отчёта. Координатор сначала поставил мягкий статус-чек, затем прервал агента ради статуса. Это не сломало работу, но показало: для задач со статистическими тестами стоит заранее закладывать контрольные точки. Саморевью worker-а нашло реальные проблемы: - `docker-compose.yml` оставлял старую интенсивность `GEN_LAMBDA_BASE_PER_MIN=200`; - тест межсессионной паузы был слишком широким; - в тестовой конфигурации оставались старые числа; - README мог быть двусмысленным про `GEN_MIN/MAX_EVENTS_PER_TICK`. Координаторская проверка добавила отдельную ценность: был найден конфликт между новым `GEN_LAMBDA_BASE_PER_MIN=30` и старым нижним пределом `GEN_MIN_EVENTS_PER_TICK=5`. При тике 5 секунд это давало минимум 60 событий в минуту и ломало критерий интенсивности. Вывод: саморевью хорошо ловит локальные несостыковки реализации, но координатор нужен для проверки связей между дефолтами, обычным запуском, документацией и acceptance criteria. ## Наблюдения по задаче 06 Задача 06 показала, зачем нужен отдельный reviewer-субагент между задачами. Саморевью worker-а и отдельный reviewer независимо нашли две существенные проблемы: - битое state v2 с валидным состоянием ГПСЧ могло пройти `from_dict_safe`, а затем уронить сервис уже в `restore_state`; - снимок активных визитов сохранялся полными batch-словарями и на верхних лимитах получался порядка мегабайт, хотя спека говорила о компактном состоянии. Координатор проверил обе гипотезы локально: - `restore_state` действительно падал на битой вложенной структуре; - оценка JSON-снимка при 200 активных визитах и популяции 300 дала около 4.7 MB. Обе находки были классифицированы как `чинить до коммита`. После исправления компактного состояния понадобился второй reviewer-круг. Он нашёл новый дефект уже в исправленной версии: формально похожий v2-state с `population=[]` или строковым `pending_visit_births` проходил первичную загрузку, но затем оставлял поток без пользователей или ронял следующий тик. Координатор подтвердил это локальной проверкой и вернул worker-у как обязательный пункт второго круга. Затем координатор дополнительно нашёл парный случай: пользователь с `active_click_id`, но без соответствующего `active_visit`. Вывод: если исправление по reviewer-находке меняет дизайн, нужен повторный reviewer-круг. Иначе можно закрыть старый дефект и внести новый рядом. ## Наблюдения по задаче 07 Задача 07 показала другую пользу reviewer-а: проверку силы доказательства, а не только поиск падений. Worker добавил сервисный тест с мок-публикацией. Он доказывал, что сервисный тик публикует связанные сообщения во все четыре топика, но внешний reviewer заметил: один опубликованный event ещё слабо доказывает уход от старой плоской модели. Для acceptance criteria было важнее доказать невырожденную модель: один визит должен дать несколько событий с одним `click_id`. Координатор классифицировал это как `чинить до коммита`. Тест был усилен: сервисный контур теперь делает несколько тиков и проверяет несколько событий одного визита с общим `click_id`, разными `event_id` и согласованными location/device/geo. Reviewer также поймал документационные неточности: - README слишком широко говорил, что переменные из таблицы проброшены через compose, хотя `KAFKA_BOOTSTRAP_SERVERS` и `GEN_DATA_DIR` должны оставаться безопасными внутренними значениями контейнера; - `docs/OPERATIONS.md` отставал от новых параметров и метрик. Вывод: финальная интеграционная задача требует reviewer-а не только по коду, но и по доказательности тестов и честности документации. ## Что сработало - Один worker на один issue хорошо удерживает контекст и границы. - `/goal`-образное задание worker-у работает как хороший контракт даже тогда, когда координатор сам не находится в goal-режиме. - Саморевью того же worker-а полезно, если явно запретить правки на первом шаге и попросить сначала выдать находки. - Отдельный reviewer полезен на границах задач и после больших исправлений. - Координаторская классификация находок обязательна. - Коммиты должен делать координатор: это последняя точка контроля состава diff. - Процессные наблюдения лучше фиксировать отдельно от рабочих коммитов. ## Что не стоит автоматизировать слепо - Не стоит всегда запускать reviewer-а после каждой мелкой правки. Это дорого и может раздувать задачу. - Не стоит принимать все reviewer-находки как правду. - Не стоит давать координатору верхнеуровневый `/goal` на всю цепочку, если от него требуется управленческая осторожность, а не слепое достижение цели. - Не стоит смешивать handoff-наблюдения с рабочими коммитами задач. - Не стоит требовать реальный Kafka-стек для каждого шага, если acceptance criteria допускают мок-публикацию. Но надо явно фиксировать, что именно не проверялось. ## Ограничения эксперимента Выводы нельзя считать универсально доказанными. Эксперимент прошёл в одном репозитории, на одной ветке и на трёх зависимых задачах одного домена. Это хороший сигнал для класса задач «последовательные инженерные срезы с тестами, документацией и явными acceptance criteria», но не доказательство, что процесс так же хорошо подойдёт для: - независимых задач, которые можно безопасно параллелить; - маленьких однофайловых правок; - задач без тестовой базы; - продуктовых или исследовательских задач, где итог заранее неясен; - задач, где субагентам нельзя писать в общее рабочее дерево. Стоимость процесса тоже заметна. Статистические и state-задачи требуют долгих прогонов, ожидания worker-а, reviewer-кругов и ручной классификации находок. Поэтому будущий скилл должен уметь выбирать облегчённый режим, а не всегда запускать полную схему. ## Инварианты будущего скилла Если на базе эксперимента делать скилл, в него стоит перенести не конкретные команды из этого репозитория, а следующие обязательные правила: - **Один worker — один bounded issue.** Цепочка задач ведётся координатором, а не одним долгоживущим исполнителем. - **Reviewer по умолчанию read-only.** Он выдаёт находки, но не правит файлы. - **Исправления идут только после классификации.** Даже хорошие reviewer-находки сначала проходят фильтр координатора. - **Коммит делает координатор.** Это защищает границы задачи и состав diff. - **Большое исправление требует повторной проверки.** Если правка меняет дизайн или формат данных, результат самой правки надо ревьюить заново. - **Рабочее дерево — общий ресурс.** Каждый цикл должен начинаться и заканчиваться явной проверкой статуса. - **Документация и тесты являются частью доказательства.** Reviewer должен проверять не только падения, но и силу тестов, честность README/операционных документов и соответствие acceptance criteria. ## Эвристики для будущего скилла Если делать скилл на базе эксперимента, в нём стоит закрепить такие правила: 1. **Начинать с чистого дерева.** Если есть процессные или чужие правки, отделить их до запуска worker-а. 2. **Давать worker-у один issue.** Не просить закрывать цепочку задач в одном агенте. 3. **Формулировать task prompt как контракт.** В нём должны быть цель, границы, методика, запрет на коммит, запрет на откат чужого и формат отчёта. 4. **Требовать саморевью без правок.** Исправления только после решения координатора. 5. **Классифицировать находки.** Минимальный набор: чинить до коммита, риск, ложная тревога, вне скоупа. 6. **Запускать отдельного reviewer-а на рискованных точках.** Особенно: state/serialization, сервисная интеграция, документация, изменения формата данных, статистические модели. 7. **Повторять reviewer-круг после больших исправлений.** Если исправление изменило дизайн, оно само нуждается в ревью. 8. **Координатор делает собственный sanity-check.** Не полный реимплемент, а проверка связей: дефолты, compose, документация, acceptance criteria, `git status`, тесты. 9. **Коммитить только координатору.** Worker не должен фиксировать изменения. 10. **Закрывать агентов.** Завершённые subagents надо закрывать, чтобы не держать лишний контекст и лимиты. ## Возможный скелет скилла 1. Прочитать issue и зависимости. 2. Проверить рабочее дерево. 3. Запустить worker. 4. Дождаться отчёта или запросить статус, если работа слишком долго молчит. 5. Запустить self-review того же worker-а. 6. Классифицировать находки. 7. Вернуть worker-у только выбранные правки. 8. Запустить проверки координатора. 9. Если задача рискованная, запустить reviewer-а. 10. Классифицировать reviewer-находки. 11. При больших исправлениях повторить reviewer-круг. 12. Сделать коммит. 13. Обновить исследовательскую заметку или итоговый лог, если эксперимент ещё идёт. Этот скелет должен быть параметризуемым. Например, для простой задачи можно ограничиться worker + саморевью + проверки координатора. Для state/serialization, интеграции сервиса, документации или статистической модели нужен reviewer. ## Оставшиеся вопросы - Как заранее понять, когда нужен отдельный reviewer, а когда достаточно саморевью? - Стоит ли задавать worker-у обязательные промежуточные отчёты для статистики и долгих тестов? - Нужно ли явно ограничивать бюджет worker-а или число reviewer-кругов? - Как лучше формализовать критерий «большое исправление требует второго reviewer-а»? - Должен ли будущий скилл сам создавать research log, или это должен быть отдельный режим? ## Предварительная рекомендация Эксперимент стоит считать успешным. Процесс дал не только рабочий результат, но и поймал дефекты, которые легко могли пройти обычный одиночный поток: - конфликт дефолтов интенсивности; - падение на битом state v2; - слишком большой state v2; - неполная валидация компактного state; - слабый интеграционный тест, не доказывавший уход от старой плоской модели. Для будущего скилла ядро должно быть не «запусти много агентов», а **координационный цикл с классификацией находок и контролем границ**. Главная формула процесса: ```text worker реализует -> worker сам себя ревьюит -> координатор фильтрует -> worker чинит выбранное -> reviewer проверяет риск -> координатор фильтрует -> при большом исправлении повторить reviewer -> координатор коммитит ``` ## Внешнее ревью другой моделью (отложенный заход, 2026-06-14) Отложенное ревью результата петли (его наметил handoff `2026-06-11-generator-time-adr-and-pending-review.md`) выполнено. Ревью провела **модель другой родословной** — та, что вела дизайн, ADR и спеки, а не линия Кодекс, писавшая код. Это и есть проверка центральной гипотезы эксперимента: какой класс дефектов автономная петля систематически не видит без внешнего взгляда. Основа проверки: перечитаны ADR-0004/0005 и обе спеки; код прочитан помодульно; марковская модель путей прогнана симуляцией на 300 тыс. визитов; прогнан весь набор тестов (113 passed); происхождение обёртки `try→fresh` прослежено по git. ### Прогноз по находке A не оправдался — петля её поймала Handoff предсказывал, что петля пропустит ссылочную целостность при `restore_state` («слепые зоны одной линии Кодекс скоррелированы»). На деле: - висячая ссылка визит→пользователь ловится валидатором формы (`state.py:88`); - висячая ссылка пользователь→словарь (неизвестный `seed_click_id`) валидатору формы недоступна, но петля **в коммите задачи 06** (`640050e`) добавила обёртку `try→fresh` вокруг `restore_state` в сервисе **и** написала регрессионный тест `test_invalid_restored_v2_state_starts_fresh` (профиль с `missing-click-id`). Это совпадает с разделом «Наблюдения по задаче 06»: и саморевью worker-а, и отдельный reviewer независимо нашли «битый v2 проходит `from_dict_safe`, но роняет `restore_state`». Вывод: многоролевой цикл (worker + саморевью + отдельный reviewer) **поймал** ровно тот класс дефекта, который ставился как главный индикатор эксперимента. Прогноз о скоррелированной слепоте по этому пункту ошибочен. ### Что внешний взгляд всё же добавил Дефекты, дожившие до внешнего ревью, оказались **не точечными, а сквозными** — их не пинит ни один тест, и каждый по отдельности «проходит»: - **Расхождение живого и восстановленного путей.** Живой визит берёт браузер/локацию из *случайной* сид-сессии и `event_id` = `_new_uuid` (`generation.py`), восстановленный — из сид-сессии *профиля* и `event_id` = `uuid5` (`runtime.py`). У визита, пережившего рестарт, атрибуты «доезжающих» событий меняются на середине. Инвариант «один визит — однородный контекст» нарушается на стыке двух путей, который ни один тест не сводит вместе. - **Форма распределения длины визита.** Среднее (10.7) и конверсия в `/confirmation` (27%) на цели, воронка монотонна — это тесты проверяют и подтверждают. Но *форма* дальше от сида: спайк 16% на длине 2 (форс минимума в 2 события убирает визиты-отказы из одного события, которые сид допускает), ~6.6% визитов срезаются о потолок 30, медиана 8 сидит на нижней границе тестовой полосы 8–12. Тесты пинят средние и пороги, но не форму распределения. ### Уточнённая гипотеза Главный индикатор A был *локальным* дефектом — падение в одной точке на битом входе; того же семейства, что и прочие находки задачи 06 (раздутый снимок, неполная валидация). Такие вещи дисциплинированный многоролевой цикл ловит хорошо. А выжили до внешнего ревью свойства **межмодульные** (инвариант поверх двух путей кода) и **распределительные** (форма, а не среднее) — ровно там, где слабы и ревью внутри одной линии, и покритериальные тесты. Отсюда уточнение к открытому вопросу «когда нужен отдельный reviewer»: ценнее всего reviewer **другой родословной**, и именно там, где (а) свойство тянется через несколько модулей/путей, либо (б) корректность распределительная (форма), а не одно утверждение. Для точечных дефектов на входе хватает саморевью и отдельного reviewer той же линии. ### Побочно подтвердилось Разделение двух линий сработало: ADR-0005 (модельные часы ×K) остался решением на бумаге, код петли честно держался реал-тайм-модели, нечаянного переплетения линий нет. Процессное решение «не мешать брейншторм времени с петлёй 06/07» выдержало. ## Связанные артефакты - `.scratch/handoffs/2026-06-11-subagent-coordinator-experiment.md` — исходный одноразовый handoff и рабочий журнал наблюдений. - `.scratch/feature-data-generator/issues/05-intensity-and-flow-calibration.md` - `.scratch/feature-data-generator/issues/06-state-v2-and-restart.md` - `.scratch/feature-data-generator/issues/07-service-integration-and-docs.md` - `docs/adr/0003-handoffs-in-scratch.md` - `docs/adr/0002-specs-as-durable-design-docs.md`