378 lines
22 KiB
Markdown
378 lines
22 KiB
Markdown
# 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, в отдельной схеме).
|