Files
wiki/tasks/multi-agent/proposal_v1/08_interaction_protocol.md
2026-05-15 00:50:01 +03:00

378 lines
22 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.
# 08. Протокол взаимодействия агентов
**Назначение:** строго описать, как агенты «общаются» — через какие артефакты, события, сообщения. Кто, кого и когда вызывает. Что происходит, если агент сломался / не уверен / превысил бюджет.
---
## Простым языком
Агенты не разговаривают напрямую. Они общаются как сотрудники почтового отделения: пишут письма (артефакты в Git), кладут их в нужный ящик (папку, статус, лейбл), а почтальон (Orchestrator) разносит. Агент никогда не «помнит» предыдущий разговор — каждый раз он читает контекст с нуля из Git.
Преимущество: процесс детерминирован. Любое состояние можно воспроизвести. Любое действие отслеживается. Никакой «таинственной памяти» агента, в которую нельзя заглянуть.
---
## Принципы протокола
1. **Артефакт — единственный язык.** Всё, что нужно следующему агенту, лежит в файле в Git. Никакого state-вне-Git.
2. **Orchestrator — единственный диспетчер.** Только он решает, какого агента запустить, когда и с каким контекстом.
3. **Каждое действие — событие.** Изменения статуса в Plane, push в Git, лейбл на PR — всё генерирует событие.
4. **События обрабатываются идемпотентно.** Повторный запуск того же события приводит к тому же результату (никаких «дубликатов комментариев»).
5. **Агент знает только свой scope.** Analyst не знает, как deployer разворачивает; reviewer не знает, как tester настраивает Playwright. Минимизация контекста = удешевление и устойчивость.
6. **Эскалация явная.** Если агент не уверен — он останавливается с конкретным вопросом, не «угадывает».
---
## Карта событий и реакций
### События Plane → Orchestrator
| Событие | Условие | Реакция |
|---------|---------|--------|
| `work_item.created` (type=Feature) | новый Feature | QG-0 → init: ветка + 7 подзадач + папка `docs/work-items/<id>/` + `00-business-request.md` → запустить `analyst` |
| `comment.created` с `:approved:` | автор имеет роль `Stakeholder` | определить, на каком этапе → проверить QG → запустить следующего агента |
| `comment.created` с `:rejected:` | автор `Stakeholder` | поставить статус подзадачи в `blocked`, лейбл `back-to:<previous-stage>` |
| `comment.created` с `:break-glass:` | автор `Owner`, на Work Item типа `qg-override` | разрешить override; залогировать |
| `comment.created` с `:final-approved:` | на Work Item после деплоя в prom | закрыть Work Item |
| `work_item.updated` (status: To Do → In Progress) | подзадача стартовала | если ещё не запущен — запустить соответствующего агента |
| `work_item.updated` (status: blocked) | задача заблокирована | проверить, есть ли причина (комментарий); пинг ответственному |
| `work_item.updated` (priority: urgent) | поднят приоритет | ускорить очередь агентов для этой задачи |
### События Forge → Orchestrator
| Событие | Условие | Реакция |
|---------|---------|--------|
| `pull_request.opened` | новый PR | связать с Plane (по `<plane-id>` в имени ветки), записать `repo_pr` field, поставить `stage:dev` |
| `pull_request.synchronize` | новый push в ветку PR | пере-запустить QG-4 (CI) |
| `check_suite.completed` | CI завершилось | если зелёное и `stage:dev` → QG-4 ✅ → запустить `reviewer` |
| `pull_request_review.submitted` (APPROVED) | Reviewer одобрил | проверить, что reviewer ≠ developer → QG-5 ✅ → лейбл `stage:test` → запустить `tester` |
| `pull_request_review.submitted` (REQUEST_CHANGES) | request-changes | лейбл `back-to:dev`, статус подзадачи «Разработка» → `in_progress`, запустить `developer` |
| `pull_request.merged` | PR замержен | запустить `deployer` (deploy-test) |
| `release.published` (tag `v*`) | Создан тег | запустить deploy-test workflow |
### События CI → Orchestrator
| Событие | Реакция |
|---------|--------|
| `qg-1: green` (spec-lint, req-coverage, approved) | подзадача «Анализ» → `done`, лейбл PR `stage:arch`, запустить `architect` |
| `qg-2: green` | подзадача «Архитектура» → `done`, лейбл `stage:design` или `stage:dev` (зависит от ui_affected) |
| `qg-3: green` | подзадача «Дизайн» → `done`, лейбл `stage:dev`, запустить `developer` |
| `qg-4: green` | подзадача «Разработка» → `done`, лейбл `stage:review`, запустить `reviewer` |
| `qg-5: green` | подзадача «Code Review» → `done`, лейбл `stage:test`, запустить `tester` |
| `qg-6: green` | подзадача «Тестирование» → `done`, лейбл `stage:ready-to-deploy`, запустить `deployer` |
| `qg-7: green` (deploy in test smoke) | статус подзадачи «Внедрение» → `awaiting-prom-approval`, прокомментировать «прошу :approved: для prom» |
| `qg-final: green` | подзадача «Внедрение» → `done`, Work Item → `Done` |
| `qg-N: red` | статус подзадачи → `blocked`, комментарий с конкретной причиной (текст QG-проверки), пинг владельцу подзадачи |
---
## Hand-off между агентами
Hand-off — момент передачи задачи от одного агента другому. Всегда происходит **через Orchestrator**, не напрямую.
### Шаги hand-off
1. Агент A заканчивает работу: создаёт/обновляет артефакты, делает commit и push, ставит статус подзадачи в `done` через MCP.
2. CI запускает QG для этого этапа.
3. QG: green → Orchestrator получает webhook → меняет лейбл, обновляет статус следующей подзадачи на `in_progress`, запускает следующего агента.
4. QG: red → Orchestrator ставит подзадачу A в `blocked`, оставляет комментарий с конкретной причиной (output линтера/теста), агент A может попробовать снова.
5. Агент B запускается с **чистого листа** — единственный контекст, который у него есть, это Git и Plane через MCP. Никакого state передачи в обход.
### Контекст, который получает агент при запуске
Orchestrator формирует startup-prompt для агента:
```
[System prompt из .openclaw/agents/<role>.md]
[User prompt]
Work Item: <plane-id>
Project: <project-name>
Repo: <repo-url>
Branch: feature/<plane-id>-<slug>
Plane URL: <url>
Прочитай артефакты предыдущих этапов в `docs/work-items/<plane-id>/`.
Прочитай `CLAUDE.md`.
Прочитай комментарии в Plane через Plane MCP (since: <last-handoff-timestamp>).
Произведи свой артефакт согласно своей роли.
Бюджет: $<X>, max iterations: <N>.
```
Никакого «вот что сказал предыдущий агент» — всё через Git.
---
## Передача замечаний (`back-to`)
Когда Reviewer/Tester возвращает задачу:
1. Reviewer оставляет комментарии в PR со ссылкой на конкретные строки и правила.
2. Reviewer пишет `12-review.md` со списком findings (severity + ссылка).
3. Reviewer ставит review-статус `REQUEST_CHANGES`.
4. Orchestrator получает событие → лейбл `back-to:dev`, статус подзадачи «Разработка» → `in_progress`, запускает `developer` снова.
5. Developer запускается с чистого листа, читает `12-review.md` и комментарии PR (через Forge MCP), правит, делает commit, push.
6. Цикл повторяется до approve.
**Лимит итераций:** ≤3 цикла back-to-dev на задачу. После 3-го — лейбл `escalation:human-needed`, статус `blocked`, ожидание человека.
---
## Эскалация
Эскалация — явный сигнал «дальше без человека нельзя». Поводы:
- Превышен бюджет токенов (`hard_kill_at_usd`).
- Агент 3-й раз возвращается на предыдущий этап.
- Стейкхолдер не отвечает ≥48 часов.
- Conflict между ТЗ и ADR/архитектурой, который нельзя решить в рамках задачи.
- Найдена security-уязвимость уровня critical.
- Падение деплоя в prom (всегда эскалация).
- Override QG.
### Процедура эскалации
1. Агент ставит лейбл `escalation:<reason>` на Work Item.
2. Статус подзадачи → `blocked`.
3. Комментарий с описанием проблемы (формат: «Что произошло / что я попробовал / что нужно от человека»).
4. Plane уведомляет всех с ролью `Owner` (через notifications или mention).
5. Человек разрешает: либо снимает блок (комментарий + reaction `:unblock:`), либо закрывает Work Item с `:wont-fix:` или `:duplicate:`.
---
## Идемпотентность
Любое событие может быть доставлено более одного раза (web-hook'и не гарантируют exactly-once). Все обработчики Orchestrator — **идемпотентны**:
- При создании ветки — проверка существования; если есть — пропустить.
- При создании подзадачи — проверка `external_id` (хэш от plane_id + subtask_type); если есть — пропустить.
- При запуске агента — проверка, не запущен ли уже (lock в Redis или Postgres advisory lock).
- При комментировании — проверка через `idempotency_key` (хэш комментария).
Это критично для надёжности: при повторе webhook'а не должно быть «двух Analyst'ов одновременно» или «семи копий BRD».
---
## Сериализация и параллельность
В рамках **одной Work Item** этапы строго последовательны. Никакого параллельного «Анализ + Архитектура».
Между разными Work Item — параллельность поощряется. Один проект может одновременно иметь:
- 3 фичи на этапе разработки,
- 1 на ревью,
- 2 на тесте,
- 1 на деплое.
Лимит параллельных задач на проект — настраивается в `.openclaw/budget.yaml` (`max_concurrent_subtasks: 5` по умолчанию). Чтобы не перегружать LLM-API и не плодить flaky preview-окружения.
---
## Контракт MCP-tools (нормативный)
Все агенты используют один и тот же набор MCP-серверов. Нормативный список:
### `plane` (тонкая обёртка над Plane REST API)
| Tool | Аргументы | Возвращает |
|------|-----------|-----------|
| `plane_get_work_item` | `id` | объект Work Item с подзадачами и комментариями |
| `plane_search_items` | `query`, `project?`, `labels?` | массив Work Item (минимальная информация) |
| `plane_update_status` | `id`, `status` (`backlog\|to_do\|in_progress\|blocked\|in_review\|done\|cancelled`) | `{ ok: true }` |
| `plane_set_label` | `id`, `label`, `op` (`add\|remove`) | `{ ok: true }` |
| `plane_add_comment` | `id`, `body` (markdown) | `{ comment_id }` |
| `plane_get_comments` | `id`, `since?` (ISO timestamp) | массив комментариев |
| `plane_check_reaction` | `comment_id`, `emoji`, `min_role?` | `{ found, by, at }` или `{ found: false }` |
| `plane_create_issue` | `parent_id?`, `type`, `title`, `body`, `labels[]` | `{ id, url }` |
| `plane_link_pr` | `id`, `pr_url` | `{ ok: true }` |
| `plane_set_custom_field` | `id`, `field`, `value` | `{ ok: true }` |
### `forge` (GitHub/Gitea, через стандартный MCP)
Стандартный набор: `create_pull_request`, `create_or_update_file`, `get_file_contents`, `list_pull_requests`, `pulls.create_review`, `pulls.list_review_comments`, `issues.create`, `tags.create`, etc.
### `playwright` (тестер)
`playwright_run_spec`, `playwright_screenshot`, `playwright_compare_visual`.
### `filesystem` (встроенный в Claude Code)
`Read`, `Write`, `Edit`, `Glob`, `Grep`.
### `bash` (встроенный)
Команды строго ограничены allowlist'ом (см. `.openclaw/permissions.yaml`):
- read-only: `git status`, `git log`, `git diff`, `ls`, `find`, `grep` (через CLI), `cat` запрещён (использовать Read tool)
- read-write для соотв. ролей: `make *`, `pytest`, `playwright test`, `npm install`, `pip install --user`, `docker compose up/down`, `git commit`, `git push`
- запрещено всем: `rm -rf`, `chmod 777`, `sudo`, `dd`, `mkfs`, `> /etc/*`
---
## Журнал и трассировка
Orchestrator пишет в Postgres-журнал каждое действие:
| Поле | Описание |
|------|----------|
| `event_id` | UUID события |
| `timestamp` | UTC |
| `source` | `plane` / `forge` / `ci` / `agent` |
| `event_type` | `work_item.created`, `qg.passed`, `agent.started`, ... |
| `work_item_id` | Plane ID |
| `subtask` | название подзадачи |
| `agent_role` | если применимо |
| `model` | LLM, использованная агентом |
| `tokens_in / tokens_out` | счётчики |
| `cost_usd` | стоимость вызова |
| `duration_ms` | длительность |
| `result` | `ok` / `error` / `escalated` |
| `payload` | JSON с дополнительной информацией |
Этот журнал — источник для дашбордов и для разбора инцидентов («почему задача застряла на этапе X?»).
---
## Сценарий «happy path» end-to-end
Иллюстративный пример: фича **PROJ-123 «Добавить визуальную зону частоты полётов на карту»** в проекте `fr24-noisemap`.
```
T+0:00 Stakeholder создаёт Work Item в Plane:
title="Add noise zones layer to map"
description="Хочу видеть на карте зоны с разной частотой полётов..."
T+0:01 Plane webhook → Orchestrator:
- QG-0 ✅
- git: создана ветка feature/PROJ-123-add-noise-zones-layer
- git: создан docs/work-items/PROJ-123/00-business-request.md
- plane: созданы 7 подзадач (Анализ, Архитектура, Дизайн, Разработка, Review, Тест, Внедрение)
- запущен agent:analyst
T+0:08 Analyst:
- прочёл BR, CLAUDE.md, текущую архитектуру
- сформулировал 3 уточняющих вопроса (по unit'ам частоты, по диапазону цветов, по mobile)
- создал docs/work-items/PROJ-123/01-questions.md
- подзадача "Анализ" → needs-clarification
T+8:00 Stakeholder ответил в Plane на вопросы.
T+8:05 Analyst подхватил ответы, написал BRD/ТЗ/AC/TestPlan.
Подзадача "Анализ" → in_review.
Комментарий: "BRD/ТЗ готовы, прошу :approved:".
T+9:00 Stakeholder поставил :approved:.
T+9:01 Orchestrator: QG-1 ✅. Подзадача "Анализ" → done.
Запущен agent:architect.
T+9:30 Architect:
- прочёл ТЗ, текущую архитектуру
- решение: использовать существующий Mapbox-tile-builder, добавить новый layer
- создал ADR-0034
- обновил c4-component.mmd
- создал 07/08/09/10-*.md (UI-требования есть)
Подзадача "Архитектура" → done.
T+9:31 Orchestrator: QG-2 ✅. ui_affected=true, запущен agent:designer.
T+11:00 Designer:
- сделал wireframes (mermaid), mockups (PNG в Figma → экспорт)
- описал states (loading/empty/error)
- заполнил a11y
Подзадача "Дизайн" → in_review.
Stakeholder проверил, поставил :approved:.
T+11:30 Orchestrator: QG-3 ✅. Запущен agent:developer.
T+14:00 Developer:
- реализовал layer (frontend) + endpoint /api/noise-zones (backend)
- написал unit-тесты, integration, e2e
- обновил OpenAPI, CHANGELOG, CLAUDE.md
- открыл PR #142 с лейблом stage:dev
- CI зелёный
Подзадача "Разработка" → done.
T+14:05 Orchestrator: QG-4 ✅. Запущен agent:reviewer.
T+14:25 Reviewer:
- сравнил с ТЗ → все REQ покрыты
- сравнил с ADR → используется правильный builder
- нашёл 1 P2-finding (мелкий комментарий о naming)
- approved
- 12-review.md записан
Подзадача "Code Review" → done.
T+14:26 Orchestrator: QG-5 ✅. Лейбл stage:test. Запущен agent:tester.
T+14:30 CI поднял preview-окружение.
T+14:55 Tester:
- все TC из 04-test-plan.yaml выполнены
- e2e зелёные
- visual regression: 0 diffs
- a11y: 0 violations
- perf: p95=180ms (порог 500ms)
- 13-test-report.md готов
Подзадача "Тестирование" → done.
Лейбл stage:ready-to-deploy.
T+15:00 Orchestrator: QG-6 ✅. Запущен agent:deployer.
T+15:01 Deployer: merge PR в main → tag v1.4.0 → deploy в test.
T+15:05 Smoke-test на test ✅.
Подзадача "Внедрение" → awaiting-prom-approval.
Комментарий: "test зелёный, прошу :approved: для prom".
T+15:30 Stakeholder: :approved:.
T+15:31 Deployer: deploy в prom.
T+15:33 Smoke на prom ✅. Метрики ok.
14-deploy-log.md записан, CHANGELOG обновлён.
Комментарий: "prom стабилен, прошу :final-approved:".
T+16:00 Stakeholder: :final-approved:.
T+16:01 Orchestrator: QG-final ✅. Work Item → Done.
Total: 16 часов. Стоимость LLM ≈ $8.
```
---
## Сценарий «обратной волны» (back-to)
Та же фича, но Reviewer нашёл P0:
```
T+14:25 Reviewer: REQUEST_CHANGES, P0 finding:
"REQ-F-2 не реализовано: фильтр по времени не работает".
T+14:26 Orchestrator: лейбл back-to:dev. Подзадача "Разработка" → in_progress.
Запущен agent:developer (вторая итерация).
T+14:50 Developer:
- прочёл 12-review.md и комментарии PR
- реализовал недостающее
- push
- CI зелёный
T+14:55 Orchestrator: QG-4 ✅. Запущен agent:reviewer (вторая итерация).
T+15:10 Reviewer: approved (проблема устранена). 12-review.md обновлён.
... дальше как обычно
```
При 3-й итерации back-to-dev — `escalation:human-needed`.
---
## Ограничения и предположения
- Plane self-hosted доступен для webhook'ов.
- Forge поддерживает webhooks и API (GitHub Actions / Gitea Actions).
- Anthropic API доступно для агентов; в случае использования локальных моделей (Qwen, GLM) — через Ollama / vLLM, формат запроса унифицирован.
- Orchestrator имеет stable URL и сертификат для приёма webhook'ов.
- Postgres для журнала Orchestrator (можно использовать тот же Postgres, что у Plane, в отдельной схеме).