Files
clickstream-ch-kafka-supers…/docs/research/2026-06-11-subagent-coordinator-experiment.md
ddadminandClaude Opus 4.8 899a3f07a1 docs(research): зафиксировано внешнее ревью петли субагентов
- Зачем:
  - закрыть отложенный заход эксперимента: внешнее ревью результата
    автономной петли моделью другой родословной (дизайн-линия, не Кодекс).
- Что:
  - добавлена секция об итогах ревью: прогноз по находке A не оправдался,
    петля её поймала (try→fresh + регрессионный тест в коммите 640050e).
  - зафиксировано, что внешний взгляд добавил сквозные находки (расхождение
    живого/восстановленного путей, форма распределения длины визита).
  - уточнена гипотеза о пользе reviewer-а другой родословной.
- Проверка:
  - git show --stat HEAD; чтение docs/research/2026-06-11-subagent-coordinator-experiment.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 13:17:06 +03:00

32 KiB
Raw Permalink Blame History

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

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;
  • слабый интеграционный тест, не доказывавший уход от старой плоской модели.

Для будущего скилла ядро должно быть не «запусти много агентов», а координационный цикл с классификацией находок и контролем границ.

Главная формула процесса:

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