# Эксперимент с координатором субагентов

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`
