diff --git a/docs/research/2026-06-11-subagent-coordinator-experiment.md b/docs/research/2026-06-11-subagent-coordinator-experiment.md new file mode 100644 index 0000000..5d8d209 --- /dev/null +++ b/docs/research/2026-06-11-subagent-coordinator-experiment.md @@ -0,0 +1,360 @@ +# Эксперимент с координатором субагентов + +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 -> координатор коммитит +``` + +## Связанные артефакты + +- `.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`