# 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//` + `00-business-request.md` → запустить `analyst` | | `comment.created` с `:approved:` | автор имеет роль `Stakeholder` | определить, на каком этапе → проверить QG → запустить следующего агента | | `comment.created` с `:rejected:` | автор `Stakeholder` | поставить статус подзадачи в `blocked`, лейбл `back-to:` | | `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 (по `` в имени ветки), записать `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/.md] [User prompt] Work Item: Project: Repo: Branch: feature/- Plane URL: Прочитай артефакты предыдущих этапов в `docs/work-items//`. Прочитай `CLAUDE.md`. Прочитай комментарии в Plane через Plane MCP (since: ). Произведи свой артефакт согласно своей роли. Бюджет: $, max iterations: . ``` Никакого «вот что сказал предыдущий агент» — всё через 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:` на 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, в отдельной схеме).