Files
clickstream-ch-kafka-supers…/docs/research/2026-06-11-subagent-coordinator-experiment.md
T
Dmitry Dementiev ce3552eaf2 docs(research): зафиксирован эксперимент с координатором субагентов
- Зачем:
  - выводы агентного эксперимента нужны как durable-основа для будущего скилла, а не как одноразовый handoff.
- Что:
  - добавлена research note с гипотезой, протоколом, наблюдениями и ограничениями эксперимента.
  - зафиксированы роли координатора, worker-а и reviewer-а, классификация находок и инварианты будущего скилла.
- Проверка:
  - ручная перечитка docs/research/2026-06-11-subagent-coordinator-experiment.md.
2026-06-11 18:44:07 +03:00

361 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Эксперимент с координатором субагентов
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`