docs(skill): добавлен handoff по экономии контекста coordinator-loop

- Зачем:
  - сохранить выводы сессии проектирования у самого навыка, чтобы будущая сессия по доработке нашла их рядом с handoff про наблюдаемость.
- Что:
  - добавлена расширенная версия handoff: диагностика двух «пожаров» по логам Codex, prior art (obra:superpowers), два принятых правила с адресами правок в SKILL.md, кандидаты-фиксы и открытые вопросы.
- Проверка:
  - открыть handoffs/2026-06-14-coordinator-loop-context-economy.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ddadminandClaude Opus 4.8 committed 2026-06-14 21:39:40 +03:00
1 parent cf4056fe66
commit 27370b92ba
1 file changed
+266
@@ -0,0 +1,266 @@
# Handoff: экономия контекста в coordinator-loop
Дата: 2026-06-14 MSK
Статус: рабочая договорённость по итогам сессии проектирования. В `SKILL.md` ещё не внесена.
## Зачем этот handoff
Сохранить выводы сессии, пока свежие. Разбирались, почему навык `coordinator-loop`
(координатор + субагенты) при длинной цепочке задач переполняет контекст координатора и дорого
стоит по токенам. Это снимок для продолжения в новой сессии, а не готовая спецификация: часть
решений принята, часть осталась открытой — так и записано. Здесь сознательно «чуть больше
информации, чем нужно»: для handoff недобор хуже перебора.
## Где что лежит (чтобы следующая сессия всё нашла)
- **Навык, источник-правды (git):** `~/dotfiles/agents/.agents/skills/coordinator-loop/`
(`SKILL.md`, `README.md`, `references/rationale.md`, `handoffs/`). Этот handoff — здесь.
- **Развёрнутая копия навыка** (её читает Claude Code): `~/.claude/skills/coordinator-loop/`.
Правки вносить в dotfiles, потом разворачивать обычным способом.
- **Побратим:** `handoffs/2026-06-14-observability-design.md` (рядом) — проектирование файлового
канала `coordinator-runs/`. **Свести с этим handoff, см. раздел «Связь».**
- **Прогон, на котором всё замеряли:** репозиторий
`~/sources/clickstream-ch-kafka-superset-demo`, фичи `feature-data-generator` (8 задач) и
`generator-model-time-startup-history` (6 задач). Вёлся как `/coordinator-loop` под Codex.
- **Сырьё диагностики:** `~/.codex/logs_2.sqlite`, `~/.codex/sessions/2026/`,
`~/.codex/goals_1.sqlite`. Внимание: файлы Codex **нумерованные и ротируются** — к будущей
сессии этих именно логов может уже не быть. Поэтому ключевые числа вынесены в этот handoff
ниже, чтобы не зависеть от сырья.
- **Prior art:** obra:superpowers, навык `subagent-driven-development` —
<https://github.com/obra/superpowers/tree/main/skills/subagent-driven-development>
(`SKILL.md`, `implementer-prompt.md`, `spec-reviewer-prompt.md`,
`code-quality-reviewer-prompt.md`).
## Что болит
У координатора дважды заканчивался контекст (по разу на каждый из двух прогонов), и работа съела
почти весь 5-часовой лимит подписки (по логам — около 3 ч 45 мин на втором прогоне).
## Диагностика
Главный метод и главный урок: **логи — истина, самоотчёт модели — нет.** Codex на прямой вопрос
искренне назвал виновником многословные отчёты субагентов, пересекающие границу. Логи это
опровергли. Модель не видит свой токен-счёт и сочиняет правдоподобную, но неверную картину.
Спасло то, что полезли в логи, а не поверили рассказу. Разбор логов делал **отдельный субагент
на дешёвой модели** (Sonnet), чтобы не забивать контекст основного агента сырьём. Этот приём
стоит повторять.
### Два разных пожара (мы их сначала склеивали)
**Пожар A — контекст координатора.** Горел НЕ от отчётов на границе субагент→координатор: они
были компактны (в среднем 2,6 КБ, максимум 7,3 КБ, всего 112 КБ за 44 возврата; полных diff и
логов тестов в них не было). Горел от того, что происходило **внутри сессии самого
координатора**:
- координатор **сам читал полные diff'ы**, чтобы проверить находки, — 19 diff'ов крупнее 10 КБ,
суммарно 413 КБ, независимо от сводок воркеров;
- сессия ревьюера-апрувера раздулась до 947 КБ за 21 ход, и **42 из 44 ожиданий `wait_agent`
завершились таймаутом** (опрос по кругу при том, что push-уведомления приходили).
Пик контекста координатора: 199 307 токенов из окна 258 400 (77 %), одна компактизация.
**Пожар B — 5-часовой лимит** (отдельная от контекст-окна вещь — это суммарный расход токенов):
- **103 прогона полного тест-сета** (каждый ~40 КБ вывода, до 16 877 токенов за один) — главный
потребитель;
- `/goal`-петли воркеров по ~400K токенов на задачу (задачи 05 и 06; 30 и 23 минуты);
- 13 Docker-сборок и накладные расходы опроса.
### Процессные счётчики (прогон issues 02–06)
| Метрика | Значение |
|---|---|
| Обработано issues | 5 (02–06) |
| Воркеров запущено | 5 + 2 `/goal`-петли |
| Вызовов независимого ревьюера | 7 (4 explorer + 3 default) |
| Кругов ревью на задачу (среднее) | 1,4 |
| Кругов правок после ревью | ~2 на задачу |
| Прогонов полного тест-сета | 103 |
| Docker-сборок | 13 |
| Всего exec_command | 996 |
| Общая длительность прогона | ~3 ч 45 мин |
### Главные потребители (по убыванию)
1. Полный вывод тестов внутри воркеров — 103 вывода по ~40 КБ.
2. Повторное чтение diff'ов координатором — 19 outputs >10 КБ, 413 КБ.
3. Опросный цикл ревьюера-апрувера — 21 ход, сессия 947 КБ.
4. `/goal`-петли (issues 05 и 06) — ~400K токенов каждая.
5. Отчёты субагентов на границе — низкое влияние (среднее 2,6 КБ).
### Гипотезы и вердикты
| # | Гипотеза | Вердикт | Доказательство |
|---|---|---|---|
| H1 | контекст рос из-за отчётов субагентов | **опровергнута** | средний отчёт 2,6 КБ, max 7,3 КБ; diff и логи в них не входили |
| H2 | координатор перечитывал то, что воркер уже сообщил | **подтверждена** | 19 полных diff в сессии координатора, 413 КБ |
| H3 | компактизация теряла важное и его восстанавливали | частично | после компактизации координатор перечитывал статусы issue и git-историю |
| H4 | полный режим ревью применялся избыточно | частично, переосмыслена | дело не в самом ревью, а в polling-overhead: 42/44 таймаута, сессия 947 КБ |
| H5 | лимит съели `/goal` и тест-прогоны | частично | 103 полных ретеста — главное; `/goal` был только на 05–06 |
### Что Codex дал верно, несмотря на промах с локацией
Его интроспекция угадала **форму** балласта (материал, который должен был сжиматься после своего
момента) и продиктовала точные компактные форматы. Он перечислил, что таскал зря: полные выводы
тестов; полные diff'ы крупных файлов; полные отчёты ревьюеров (вместо классифицированных
находок); полные отчёты воркеров после приёмки issue; тексты уже закрытых issue; собственные
пользовательские апдейты («запускаю», «жду воркера»); полные build/compose/ClickHouse-логи;
длинные фрагменты спек после первичного чтения.
Ключевой ответ про классификацию находок: отчёта субагента **хватало для маршрутизации**
(blocker / риск / docs-мелочь / ложная тревога), но для финального решения «чинить или нет» по
**межмодульному инварианту** он **всё равно лез в код** — отчёт был индексом и аргументацией, а
не заменой чтения. Примеры мест, куда он шёл: `service.py:_is_startup_history_state`, порядок
записи manifest/state, путь восстановления state→manifest→state→live load→startup-marker→
fallback, SQL-воронка для доказательства. Вывод: лучший отчёт — это **индекс** (находка,
`файл:строка`, минимальный путь воспроизведения, какой критерий нарушен, что проверить в коде),
а не нарратив.
Про `wait_agent`: таймауты стояли 600000 мс (10 мин) для воркера/ревьюера, 900000 мс (15 мин)
для долгих стендовых задач, 180000 мс (3 мин) после запроса промежуточного статуса.
Push-уведомления (`<subagent_notification>`) приходили, но Codex им не доверял и ждал заново —
отсюда лишний опрос. Сам прописал лечение: на уведомление **сразу обрабатывать результат**, а не
запускать `wait_agent` повторно; после `spawn_agent` не делать длинный `wait_agent`, если есть
локальная работа; один разумный статус-таймер вместо polling-цикла.
## Prior art: obra:superpowers `subagent-driven-development`
Чужой навык глубоко проработан (в том числе по экономии токенов и выбору минимально достаточной
модели) и стоит на той же идее: оркестратор не делает работу сам, держит контекст чистым,
субагенты — «свежий контекст». По части ядра мы переизобретали его.
Как у них устроено: на задачу один эфемерный **implementer** → два последовательных ревьюера
(spec-review → quality-review) → фиксы → следующая задача. Задачи строго последовательны.
Implementer возвращает компактный статус `DONE / DONE_WITH_CONCERNS / BLOCKED / NEEDS_CONTEXT` +
изменённые файлы + итог тестов (без полного вывода). Spec-reviewer читает реальный код и не
доверяет докладу implementer'а. Модель выбирается по роли (дешёвая → стандартная → сильная).
**Берём оттуда:**
- **Ревьюер читает код сам и не верит докладу** — жёсткое правило, убивает Пожар A по построению
(координатор перестаёт быть верификатором).
- **Модель по роли.** Поправка под нас: наш implementer «толстый» (`/goal`), ему нужна сильная
модель; дешёвая выигрывает на субагенте-верификаторе простых находок, docs-проверках и
диагностике (разбор логов мы и сделали на Sonnet).
- **Статус-контракт воркера** `DONE / DONE_WITH_CONCERNS / BLOCKED / NEEDS_CONTEXT` — протокол
эскалации, которого у нас нет: воркер сам стопорится на архитектурной неясности.
- **Качественная стадия ревью** (хорош ли код, межмодульные инварианты).
**Не берём (сознательно другой выбор):**
- У superpowers **толстый план** (~2000 строк, почти реализация) + тонкий механический
implementer. У нас **тонкий issue** (acceptance criteria) + толстый `/goal`, который сам
додумывает реализацию и самопроверяется. Для учебного репозитория наш путь лучше: issue
читаемы менти, реальное решение живёт в петле, нет огромного плана.
- Их **спек-стадия ревью** у нас по большей части избыточна: «выполнили ли спеку» уже проверяет
петля `/goal`. Берём только качественную линзу.
Вывод: `coordinator-loop` остаётся отдельным навыком (своя идентичность — цепочка issue из
`/to-issues`, гейт-классификация находок, владение коммитом). Заимствуем механику делегирования,
а не план-центричную философию.
## Решения сессии — с адресами правок в `SKILL.md`
**Принято (дёшево, метрически обосновано):**
1. **Координатор не читает diff ради проверки находки.** Чтение кода уходит ревьюеру-субагенту:
он читает код сам и возвращает только вердикт. Чинит Пожар A (413 КБ). Куда править:
- «Цикл на одну задачу», **шаг 8 «Свой sanity-check»** — сейчас список включает чтение
(дефолты, compose/конфиги, документы, acceptance criteria, git status, тесты). Сузить:
sanity-check проверяет связи, но **не читает diff/код ради верификации находки** — это
работа ревьюера.
- «Роли», пункт **Координатор** («делает sanity-check») — уточнить так же.
- «Роли», пункт **Независимый ревьюер** — добавить жёсткое правило superpowers: ревьюер
читает код сам и **не верит докладу**; именно он несёт верификацию.
- «Когда звать независимого ревьюера» — это и есть порог, на котором верификация (с чтением
кода) уходит в субагента; для межмодульных инвариантов — обязательно.
2. **Доверять push, не крутить `wait_agent` по кругу.** Пришло `<subagent_notification>` — сразу
обрабатывать; один разумный статус-таймер вместо polling-цикла; после `spawn_agent` не делать
длинный `wait_agent`, если есть локальная работа. Возможно, самый дешёвый одиночный выигрыш
(сток 947 КБ почти случайный, не дизайн). Куда править — **открытый вопрос** (см. ниже):
`wait_agent` — рантайм Codex, а `SKILL.md` host-agnostic. Связано со «Цикл», **шаг 4
«Дождись отчёта»**.
**Намеренная цена — НЕ трогать:**
- `/goal`-петля (~400K токенов/задача) — сознательный обмен на качество решения;
- независимое межмодульное ревью на пороге риска;
- декомпозиция «тонкий issue + толстый `/goal`».
**Кандидаты-фиксы (не приняты, проверить на прогоне) — с адресами:**
- **Точечные тесты в петле, полный тест-сет — только на гейте.** Главный потребитель токенов
(103 ретеста). Риск пропустить межмодульный регресс в середине петли ловит гейт + межмодульный
ревьюер. Куда: «Контракт задания worker-у» (методика тестов) и шаг 12 «Коммит» (полный сет
перед коммитом).
- **Отчёты в формате индекса, а не нарратива.** Куда: «Контракт задания worker-у» →
«формат финального отчёта»: находка/изменение, `файл:строка`, минимальный repro, какой
критерий, что проверить; запрет на вставку diff и полных логов.
- **Статус-контракт воркера** `DONE / DONE_WITH_CONCERNS / BLOCKED / NEEDS_CONTEXT`. Куда:
«Контракт задания worker-у».
- **Демоушн закрытых issue** в компактную durable-форму (`summary`), чтобы компактизация её не
уничтожала. Куда: новый короткий раздел + связь с observability-handoff (`summary.md`).
- **Файловый канал в общем дереве** для обмена находками (оркестрация по ссылке, а не payload
через контекст). Куда: см. observability-handoff (`coordinator-runs/`).
- **Модель по роли.** Куда: «Облегчённый и полный режим» / «Контракт задания worker-у».
- **Только качественная стадия ревью** (спек несёт `/goal`). Куда: «Роли» (ревьюер) + «Когда
звать независимого ревьюера».
## Связь с handoff про наблюдаемость
Побратим `2026-06-14-observability-design.md` (рядом) уже спроектировал файловый канал
`coordinator-runs/` (live-логи + `summary.md`, чтение по требованию, координатор не ведёт лог
сам). Это **тот же** файловый канал, что нужен кандидатам-фиксам «демоушн» и «файловый канал».
**Свести в один дизайн, не строить две файловые системы.** Оба не реализованы — слить легко.
## Открытые вопросы (с моей рекомендацией, но не закрыты)
1. **Объём первого захода.** A — полная переделка сразу; B — внести только два дёшевых правила,
остальное пометить «проверить на следующем прогоне». Рекомендация: **B** (держит навык от
разрастания).
2. **Кто верифицирует находку** — автор-воркер со своим знанием кода и проверяемым обоснованием,
или свежий ревьюер? Склонялись к автору на знании + независимый на пороге риска. Не закрыто.
3. **Где живёт правило 2 (push/`wait_agent`).** Это рантайм Codex, а `SKILL.md` host-agnostic.
Куда: короткий раздел «Синхронизация с субагентами» в `SKILL.md` с пометкой «Codex-specific»,
либо `references/rationale.md`, либо привычка оператора. Не решено.
4. **Слить ли два handoff** (этот и про наблюдаемость) в один дизайн до правки `SKILL.md`?
Рекомендация: да.
5. **Кросс-хост.** Файловый канал отчасти оправдан тем, что host-agnostic (Codex и Claude Code).
Если на деле только Codex — обоснование слабее. Уточнить требование.
6. **Носитель для проверки.** Фича почти закрыта (осталась issue 06), новой цепочки под рукой
нет. На чём гонять проверочный прогон — пока неясно; числа-ориентиры ниже остаются baseline
на будущую фичу.
## Числа-ориентиры для следующего прогона
Чтобы измерить, помогли ли фиксы, а не поверить на слово:
- пик контекста координатора: было 77 % окна (199K/258K) — должно заметно упасть;
- собственные diff-чтения координатора: было 413 КБ — должно стремиться к нулю;
- сессия ожиданий/опроса: было 947 КБ при 42/44 таймаутах — push обрабатывается, цикла нет;
- прогоны полного тест-сета: было 103 — точечные в петле, полный только на гейте.
## Что ещё не сделано
- Решения не внесены в `SKILL.md`.
- Кандидаты-фиксы не приняты и не проверены на реальном прогоне.
- Два handoff не сведены.
- Не решён объём первого захода (вопрос 1).
- Не решено, где живёт правило 2 (вопрос 3).
## Рекомендуемые следующие шаги
1. Решить объём первого захода (A vs B).
2. Свести этот handoff с handoff про наблюдаемость в один дизайн.
3. Внести минимум в `SKILL.md` (два принятых правила) по адресам выше, сверяясь с superpowers как
prior art.
4. На ближайшей новой фиче замерить числа-ориентиры; по факту резать или добавлять.
5. Опционально — лёгкая дивергенция/конвергенция через `brainstorm-with-docs`, если решение
захочется поискать шире. Возможно, и не понадобится: два дешёвых фикса метрически очевидны.
## Suggested skills
- `write-a-skill` — при переносе правил в `SKILL.md`, чтобы не раздувать основной файл.
- `brainstorm-with-docs` — если делать полноценный заход с дивергенцией/конвергенцией.
- `conventional-commits` — для коммитов изменений в навыке.