# Задание B6 (Codex): рабочий Hub и граф над маршрутизацией

## Дата поступления
2026-08-21

## База
Проверочный HEAD: **`09d5e5d`**, `origin/main` = `09d5e5d`. `git fetch`, зафиксировать `BASE_SHA`, работать от свежего `main`.

## Ветка
`codex/routing-graph`

---

## Что принято по B5

Проверено исполнением и по скриншотам:

- **макет воспроизведён** — левая навигация, верхняя строка с поиском и `Ctrl + K`, ряд KPI, схема маршрутизации, правая панель, лента событий;
- **три темы работают**, переключение перекрашивает всё приложение;
- **дисциплина честности выдержана**: на скриншоте «Обзора» стоят `Н/Д` с пояснением причины там, где данных нет, и реальные значения там, где есть. Ни одного выдуманного числа;
- четыре действия вернулись на карточку аккаунта через `MANAGEMENT_ACTIONS`;
- 33 скриншота, тесты 193 headless / 249 с UI-зависимостями, ruff чисто.

Это лучшая работа по интерфейсу за все раунды.

---

## Про исходный документ «Agent Graph / n8n»

Документ на 104 пункта прочитан и разобран. **Реализуется только часть**, и это осознанное решение владельца.

Причина: n8n — это Node.js/TypeScript с **собственным движком исполнения workflow** и базой данных; канва там редактирует то, что движок исполняет. У Hermes Hub движок другой — **маршрутизации**: он выбирает провайдера, аккаунт и модель для одного вызова. Агентов, которые исполняются, делегируют задачи и возвращают артефакты, в Hub нет — они живут в Hermes Agent, внутри которого Hub работает плагином.

**Исключено из задания** (требует несуществующего слоя исполнения):

| Пункт документа | Причина |
|---|---|
| §3 Hermes Execution Engine, §66 Execution Planner | движка исполнения агентов в Hub нет |
| §12 Execution Tree, §56 snapshot execution | нечего вкладывать: `route_request` — один вызов, один ответ |
| §31–35 Agent as Tool, input/output schema | межагентных вызовов не существует |
| §37 отмена дерева, §38 таймауты на 4 уровнях | нечего отменять |
| §39 failure propagation, §40–41 REVIEW и цикл доработки | требует исполнения ролей |
| §1, §96 БД и миграции | БД нет, конфигурация в YAML/JSON |
| §69 React graph-библиотека | стек — Python + CustomTkinter |
| §61 permissions | системы прав нет |

**Берётся из документа**: §68 (разделение слоёв), §30 (topology не меняется при provider fallback), §28 (не делать второй редактор маршрутизации), §43–44 (персистентность и версия схемы), §46–47 (валидация с показом на графе), §48–53 (layout, zoom, minimap, поиск), §58 (без выдуманных метрик), §60 (без секретов в графе), §62 (миграция текущих ролей), §72–75 (клавиатура, undo/redo, несохранённые изменения), §80–81 (различать типы связей не только цветом).

---

## P0-1. Hub должен быть демонстрируемо рабочим

Главное требование задания. Приложение ни разу не проходило полный путь с живым аккаунтом: все скриншоты сняты на пустой конфигурации, где везде `Н/Д` и «Критическое состояние».

**Что проверить и починить:**

1. **Мастер потерял назначение роли.** `add_account_wizard._finish` (строка 1451) теперь только пишет событие в журнал и вызывает `on_complete`. Вызова `AutoAssigner.assign_profile_to_role` там больше нет. Разобраться: достаточно ли того, что слот уже входит в цепочку роли по умолчанию, или подключённый аккаунт остаётся вне маршрутизации. Если достаточно — описать это в отчёте; если нет — вернуть назначение.

2. **Пустое состояние должно вести пользователя.** Сейчас на «Обзоре» при отсутствии аккаунтов — «Критическое состояние» и пять `Н/Д`, без единой подсказки, что делать. Первый экран нового пользователя обязан объяснять следующий шаг и давать кнопку к нему.

3. **Ручная проверка с настоящим аккаунтом — обязательна.** Подключить один реальный аккаунт (любого провайдера), убедиться, что он появился в «Аккаунтах», получил роль, виден на «Маршрутизации», и что тест профиля проходит. Приложить скриншоты **этих** состояний, а не пустых.

Без пункта 3 задание не принимается. Скриншоты пустого приложения доказывают вёрстку, но не работоспособность.

## P0-2. «Команда» становится графом маршрутизации

Превратить экран «Команда» в визуальный редактор на `CTkCanvas`.

**Модель.** Узел = логическая роль из `config.roles` (`orchestrator`, `coder-primary`, `coder-secondary`, `reviewer`, `research`, `fast`) плюс узел оркестратора. Связь = позиция в цепочке отказоустойчивости: основной → резерв 1 → резерв 2. Типы связей минимально: `PRIMARY`, `FALLBACK`, `DELEGATE`.

**Ключевой принцип (§68 документа):** граф описывает роли и их отношения. Какой провайдер, аккаунт и модель исполнят роль — решает существующий роутер. **Смена провайдера при отказе не меняет граф.**

**Что должно работать:**
- перемещение узлов, создание и удаление связей, изменение типа связи;
- изменение графа **реально меняет цепочки** в `router_profiles.yaml` через `AutoAssigner.assign_profile_to_role` — это не картинка (§64 документа);
- сохранение позиций, масштаба и области просмотра рядом с конфигурацией, с полем версии схемы;
- миграция текущих шести ролей в граф по умолчанию при первом открытии;
- автовыравнивание (иерархическое), после него ручные координаты не перезатираются;
- zoom, вписать в экран, minimap для больших графов;
- undo/redo для перемещения, добавления и удаления узлов и связей;
- отметка «есть несохранённые изменения», без молчаливой потери.

**Валидация перед сохранением** с показом ошибок прямо на графе: отсутствующий оркестратор, роль без профилей, недостижимый узел, ссылка на несуществующий профиль, дубли.

## P0-3. Инспектор узла

При выборе роли справа: назначенные профили в порядке цепочки, текущий активный, провайдер, аккаунт, модель, состояние квоты, причина последнего переключения. Кнопка «Открыть маршрутизацию» ведёт в существующий раздел с выбранной ролью — **второй редактор маршрутизации не создавать** (§28).

## P1-4. Живая подсветка на реальных данных

Данные уже есть, выдумывать ничего не нужно:

| Что показать | Откуда |
|---|---|
| активный узел и связь | `PipelineNode.is_active`, `RolePipeline.active_profile_id` |
| причина переключения | `PipelineNode.failover_reason` |
| почему выбран провайдер | `selection_trace` в метаданных ответа |
| нагрузка по ролям | телеметрия `by_role`, `total_calls` |
| состояние квоты узла | `quota_status` |

Обновлять по событиям `EventBus` (`ROUTING_UPDATED`, `QUOTA_UPDATED`, `ACCOUNT_*`), а не полным пересбором канвы на каждое событие (§95).

Где данных нет — узел не подсвечивается, метка не рисуется. `Н/Д` вместо чисел, правило прежнее.

---

## Ограничения

- Граница: ваша зона — `src/antigravity_provider/router/ui/**`, `hermes_hub_app.py`, `tests/test_ui_*.py`. Модель графа и её сохранение — тоже в UI-слое, поскольку это конфигурация представления; изменение цепочек идёт через существующий `AutoAssigner`.
- Не создавать параллельные сущности: роли, профили, цепочки уже есть в `router_config`.
- Не трогать движок маршрутизации, адаптеры, телеметрию.
- Сеть и подпроцессы — не в UI-потоке.
- Три темы сохранить, `#FFFFFF` фоном не использовать.
- Логотип Hermes использовать существующий, псевдологотипы агентам не генерировать.
- Тег `v0.1.1` не создавать.

## Критерии приёмки

1. Ни один файл чужой зоны не изменён.
2. **Скриншоты с подключённым живым аккаунтом**: «Обзор», «Аккаунты», «Команда», «Маршрутизация» — с реальными данными, а не `Н/Д`.
3. Пустое состояние на «Обзоре» объясняет следующий шаг и ведёт к нему.
4. В отчёте сказано, попадает ли подключённый через мастер аккаунт в маршрутизацию, и на основании чего это установлено.
5. Изменение графа отражается в `router_profiles.yaml` и переживает перезапуск вместе с позициями и масштабом; проверено тестом.
6. Миграция шести существующих ролей в граф выполняется без потери цепочек.
7. Валидация ловит цикл, недостижимый узел и ссылку на несуществующий профиль; ошибки видны на графе.
8. Живая подсветка работает от событий, без полного пересбора канвы.
9. Приложение запускается, все разделы открываются; граф держит не менее 20 узлов без заметных задержек.
10. Прогон **в обоих окружениях** — без UI-зависимостей и с `customtkinter`/`pillow`/`psutil`; обе команды и оба результата в отчёте.
11. `ruff check .` чисто; release gate не ухудшен.
12. Отчёт: `BASE_SHA`, `FINAL_SHA`, изменённые файлы, что из документа реализовано, что исключено и почему, известные ограничения.

## Главное

Задача — не нарисовать красивый граф, а сделать так, чтобы владелец установил Hub, подключил аккаунт, увидел свою команду ролей, понял, кто чем исполняется и куда уйдёт запрос при исчерпании квоты. Граф — способ это показать и настроить, а не самоцель.

## Порядок сдачи
Передать точный `FINAL_COMMIT_SHA`.
