Полный аудит Hermes Hub и бэклог доработок
Дата аудита: 2026-08-20
Проверенная ревизия: origin/main @ 5ccfd48 (github.com/ochenstarik-ui/hermes-hub, private)
Метод: статический разбор всех исходников + прогон тестов на чистом checkout origin/main в отдельном worktree
Версия: 0.1.0 (pyproject.toml, compatibility.json) против 1.3.0 (about_view.py, gui_server.py)
Объём: ~19 500 строк (Python ~15k, C# 0.8k, HTML 0.85k, PS1/BAT 0.25k, docs/config ~2.5k)
Первая редакция этого документа делалась по локальному снимку fdf9ecc (на 3 коммита старше). Все пункты ниже перепроверены по origin/main; раздел 2 фиксирует, что именно уже исправлено в 7609ad8.
1. Общая оценка
Ядро маршрутизации (router_engine, health_tracker, session_affinity, адаптеры) спроектировано грамотно: чистые слои, dataclass-модели, потокобезопасность, детерминированные тесты с моками. Коммит 7609ad8 («product stabilization») закрыл заметную часть проблем презентационного слоя — появился кэш сканирования, строгий resolver статусов, идентичность приложения в Windows, предпрогрев экранов.
Тем не менее продукт остаётся неработоспособным как приложение:
- нативное приложение не запускается на чистой машине — ни один установщик не ставит зависимости UI;
- failover для основного провайдера (Antigravity) не срабатывает никогда из-за формата ошибок
agy_generate;
- три пользовательских сценария падают с
AttributeError/NameError: удаление credentials, подключение аккаунта по API-ключу, перераспределение ролей;
- настройки не сохраняются и ни на что не влияют;
- «roadmap»-модули (+819 строк) не подключены ни к одному кодовому пути.
Фактический прогон тестов на чистом checkout origin/main:
1 failed, 28 passed, 2 skipped + 1 ошибка сбора (test_ui_refinement.py не импортируется без customtkinter).
Отчёт agents/done/…stabilization…md заявляет «35/35 PASSED» — это верно только на машине, где dist/ существует локально и зависимости доустановлены руками.
Зрелость: ядро — Beta, оболочка — Alpha, упаковка — не готова к дистрибуции.
2. Что уже исправлено в 7609ad8 (не требует работы)
| Было в аудите |
Статус в origin/main |
TeamView.update_data(app_state) требует аргумент → главный экран не обновляется |
Исправлено — сигнатура унифицирована (app_state=None) во всех view |
scan_all() вызывается 9–10 раз за один refresh |
Исправлено — TTL-кэш 30 с (unified_health.py:248) |
Ложные HEALTHY/QUOTA_EXHAUSTED у неподключённых слотов |
Исправлено — введён STATUS_NOT_CONFIGURED, устаревшие записи квот чистятся |
| Иконка Python в панели задач |
Исправлено — SetCurrentProcessExplicitAppUserModelID("HermesHub.Desktop") + multi-res .ico |
| Лаг переключения вкладок |
Исправлено — предпрогрев всех 8 экранов в __init__, переключение через pack_forget() |
Установщик не копирует assets/branding |
Исправлено в новом installer/HermesHubSetup.py (в .cs и install.ps1 — нет) |
| Нет документации по разработке/безопасности/производительности |
Исправлено — добавлены docs/AUTH.md, DEVELOPMENT.md, PERFORMANCE.md, ROUTER.md, SECURITY.md |
| Нет адаптивности окна |
Исправлено — debounce-обработчик resize |
3. P0 — Блокеры (перепроверено на origin/main, всё воспроизводится)
| # |
Дефект |
Файл |
Последствие |
| 1 |
customtkinter и pillow не устанавливаются ни HermesHubSetup.py, ни HermesHubSetup.cs, ни install.ps1 |
все три установщика |
Запуск через pythonw.exe завершается молча: ни окна, ни ошибки. Проверено: в %LOCALAPPDATA%\hermes\hermes-agent\venv пакета нет |
| 2 |
ProfileAuthManager.get_profile_dir не существует — это модульная функция, а не метод класса |
profile_manager.py:79 vs gui_server.py:216, hermes_hub_app.py:120, add_account_wizard.py:200 |
AttributeError при удалении credentials и при сохранении API-ключа |
| 3 |
json не импортирован, но используется |
ui/add_account_wizard.py:200 |
NameError → Codex и OpenCode Go нельзя подключить через UI вообще |
| 4 |
AutoAssigner.auto_assign_all не существует |
hermes_hub_app.py:411, team_view.py:188,243 |
Кнопки «Перераспределить роли» и «⋮» падают |
| 5 |
agy_generate возвращает ошибки как успешный chat.completion: текст ошибки в content, ключа error нет |
agy_subprocess.py:584 + router_engine.py |
RouterEngine проверяет if "error" in response → не срабатывает → mark_success, failover для Antigravity не работает никогда, пользователь получает текст ошибки вместо ответа модели |
| 6 |
Мастер ждёт session.status == "error", сессия ставит "failed" |
add_account_wizard.py:243 vs profile_oauth.py |
При ошибке OAuth мастер молча висит 120 с |
| 7 |
Кнопка «Назначить» шлёт action assign_role, обработчика нет |
accounts_view.py:244 vs hermes_hub_app._handle_action |
Кнопка не делает ничего, без обратной связи |
| 8 |
Шаг 4 мастера не применяет выбранную роль — только пишет в журнал |
add_account_wizard.py:_finish |
Выбор пользователя игнорируется |
| 9 |
Шаг 3 показывает «✓ Аккаунт успешно проверен» без всякой проверки для API-ключей; список моделей захардкожен (gpt-4.1, qwen-coder, deepseek-v2) |
add_account_wizard.py |
Ложноположительная валидация, вымышленные модели |
4. P1 — Производительность и отзывчивость UI
| # |
Проблема |
| 10 |
_restore_status() вызывает get_system_readiness() в UI-потоке через after(6000, …). При холодном/истёкшем кэше — блокировка окна на время сетевого скана 16 профилей |
| 11 |
При промахе кэша scan_all() опрашивает 16 профилей последовательно (таймаут 10 с на профиль) — нет параллелизма и общего бюджета времени |
| 12 |
5 экранов (accounts, health, providers, routing, logs) полностью уничтожают и пересоздают виджеты через winfo_children() + destroy() — мерцание, потеря позиции скролла |
| 13 |
Нет периодического автообновления: данные устаревают до ручного нажатия «Обновить» (таймер только для resize) |
| 14 |
Тост затирает строку состояния и восстанавливается блокирующим вызовом |
5. P1 — Логика роутера и корректность состояний
Файлы router_engine.py, health_tracker.py, agy_subprocess.py, profile_manager.py, router_config.py, session_affinity.py, hermes_plugin.py в 7609ad8 не менялись — все находки в силе.
| # |
Проблема |
Файл |
| 15 |
_CM_LOCK удерживается всё время subprocess agy (до 180+ с) → все Antigravity-профили сериализуются, параллельная работа команды невозможна |
antigravity_adapter.py |
| 16 |
Профильные токены пишутся в глобальную запись gemini:antigravity Credential Manager и не восстанавливаются → «основной аккаунт Hermes» меняется как побочный эффект маршрутизации; гонки с самим agy и Hermes CLI |
antigravity_adapter.py |
| 17 |
overall_state не сбрасывается при истечении cooldown (только в mark_success) → профиль остаётся «Квота исчерпана» бессрочно |
health_tracker.py:is_healthy |
| 18 |
Роутер решает по состоянию семейства моделей, UI — по overall_state: роутер шлёт запросы в профиль, который UI показывает мёртвым |
health_tracker.py / unified_health.py |
| 19 |
Session affinity без TTL и вытеснения — неограниченный рост памяти; session_affinity_ttl_seconds из шаблона конфига не реализован |
session_affinity.py |
| 20 |
router_state.json пишется без межпроцессной блокировки — GUI и процесс Hermes затирают состояние друг друга |
health_tracker.py |
| 21 |
ProfileHealthRecord.active_leases всегда 0, но выводится в CLI и API |
health_tracker.py, cli_commands.py |
| 22 |
Категория TRANSIENT классифицируется, но обрабатывается как обычный отказ — без повтора и задержки |
router_engine.py |
| 23 |
Классификация ошибок по подстрокам ("429" in err_lower) — ложные срабатывания на любом тексте с «429» |
все адаптеры |
| 24 |
_safe_env() не используется: custom_env = dict(os.environ) передаёт в subprocess agy все ключи (OPENAI_API_KEY и пр.) |
agy_subprocess.py, antigravity_adapter.py |
| 25 |
Нет стриминга ответов — Hermes получает только полный ответ |
router_engine.py |
| 26 |
Нет учёта токенов, стоимости и latency между вызовами |
— |
| 27 |
В шаблоне router_profiles.example.yaml блок router: полностью игнорируется загрузчиком (ожидаются ключи верхнего уровня); max_failover_attempts, cooldown_base_seconds, cooldown_max_seconds, session_affinity_ttl_seconds отсутствуют в схеме |
router_config.py:load_router_config |
| 28 |
save_router_config пишет другую структуру, чем шаблон → первое сохранение («назначить оркестратором») уничтожает комментарии и структуру пользовательского YAML |
router_config.py |
| 29 |
max_concurrency=1 для Codex и OpenCode во встроенном дефолте, хотя это stateless REST (в шаблоне 2 и 3) — искусственное ограничение пропускной способности |
router_config.py |
| 30 |
antigravity_llm_execution перехватывает все вызовы llm_execution независимо от провайдера и никогда не вызывает next_call; ошибка роутера логируется на уровне debug |
hermes_plugin.py |
| 31 |
Каталог моделей захардкожен (gemini-3.7-flash, qwen3.8-max, kimi-k2.7-code…) и не сверяется с реальным |
router_config.py |
| 32 |
discover_models() определяет каталог, вызывая agy с заведомо невалидной моделью и парся текст ошибки |
agy_subprocess.py |
| 33 |
AutoAssigner.check_duplicate_identity сравнивает auth_data["email"], но profile_oauth сохраняет только {"token", "auth_method"} → детектор дубликатов не срабатывает никогда |
auto_assigner.py |
6. P1 — Безопасность
| # |
Проблема |
| 34 |
CLIENT_ID/CLIENT_SECRET Google зашиты в код и склеены из фрагментов строк ("GOC" + "SPX" + …, oauth.py:26-28) — приём обхода сканеров секретов, чужой OAuth-клиент. Требует явного решения: собственный клиент или документированное заимствование. Отчёты «0 секретов при сканировании» проходят именно из-за склейки |
| 35 |
ProfileAuthManager.save_profile_auth пишет токены plaintext без ограничения ACL. docs/SECURITY_MODEL.md заявляет изоляцию «правами текущего пользователя ОС», но код прав не выставляет (chmod есть только в CredentialStore и на Windows не работает) |
| 36 |
access_token передаётся в query-строке при верификации (tokeninfo?access_token=…) — попадает в логи |
| 37 |
CORSMiddleware(allow_origins=["*"], allow_credentials=True) на локальном API с эндпоинтами delete, set-main, oauth/start (gui_server.py) |
| 38 |
Лаунчер генерирует и исполняет Python-скрипт %LOCALAPPDATA%\hermes\hermes_hub_entry.py при каждом запуске — вектор подмены между записью и стартом |
| 39 |
Нет подписи установщика и лаунчера; dist/checksums.txt формируется вручную и не проверяется при установке |
| 40 |
В hermes-hub.log пишутся тексты ошибок провайдеров без санитизации (могут содержать фрагменты токенов) |
7. P1 — Тесты и качество
| # |
Проблема |
| 41 |
pytest tests/ падает на сборе: test_ui_refinement.py → hermes_hub_app → customtkinter |
| 42 |
test_setup_exe_exists падает на чистом checkout: dist/ в .gitignore, ещё 2 теста уходят в skip. Реальный результат — 1 failed, 28 passed, 2 skipped, а не заявленные 35/35 |
| 43 |
Тесты мутируют реальные пользовательские данные: do_set_main пишет router_active_profile.json, do_set_orchestrator перезаписывает router_profiles.yaml, get_profile_env_dir создаёт каталоги профилей. Нет фикстуры с временным HERMES_HOME |
| 44 |
test_installer.py запускает реальный установщик: копирование в %LOCALAPPDATA%, ярлык, запись в реестр |
| 45 |
Тесты ходят в сеть (Google tokeninfo, OpenCode /models) — недетерминированность |
| 46 |
Нет тестов на: classify_error всех адаптеров, мастер подключения, YAML round-trip load↔save, CLI, логику установщиков |
| 47 |
Нет CI (.github/workflows отсутствует, репозиторий приватный) |
| 48 |
ruff в dev-зависимостях, но нет секции [tool.ruff] и запуска; нет mypy |
8. P1 — Упаковка, установка, дистрибуция
| # |
Проблема |
| 49 |
Два параллельных установщика: installer/HermesHubSetup.cs (711 строк, GUI-мастер, silent-режим, реестр, коды 0/10/11/12) и installer/HermesHubSetup.py (101 строка, копирует assets, но без реестра, ярлыка, silent-кодов). Неясно, какой канонический; dist/HermesHubSetup.exe собран из .cs |
| 50 |
Проверка совместимости фиктивна: compatibility.json не читается, MIN_HERMES_VERSION ни с чем не сравнивается, IsHermesCompatible = true всегда при наличии Python → код возврата 11 недостижим. То же в install.ps1 (версия печатается, но не проверяется) |
| 51 |
installer/HermesHubSetup.cs и scripts/install.ps1 не копируют assets/branding (исправлено только в .py-варианте) |
| 52 |
ui/assets.py:39 — захардкоженный fallback Path("E:/Agent projects/hermes-hub") в исходниках |
| 53 |
build_launcher.ps1 не передаёт /win32icon:HermesHub.ico → у самого .exe нет иконки (иконка ставится только в рантайме через iconbitmap) |
| 54 |
install.ps1 копирует __pycache__ |
| 55 |
launcher/HermesHub.exe — собранный бинарник в git; собранный установщик, наоборот, не отслеживается |
| 56 |
Нет single-instance guard — повторный клик по ярлыку открывает второе окно |
| 57 |
Нет логирования запуска: при pythonw.exe любая ошибка импорта = полная тишина |
| 58 |
Нет механизма обновления в самом приложении |
| 59 |
Версия расходится: 0.1.0 (pyproject, compatibility.json, installer) против 1.3.0 (About, FastAPI) |
| 60 |
scripts/launch_hermes_hub.bat запускает несуществующий веб-режим на :8765; CLI-аргументы hub --port/--no-browser парсятся и игнорируются |
9. P2 — Мёртвый и неподключённый код
| # |
Объект |
Строк |
Статус |
| 61 |
supervisor/lifecycle_supervisor.py, supervisor/policies.py, capability/capability_matrix.py, skills/skill_registry.py, scheduler/task_safety.py, adapters/deepseek_adapter.py |
819 |
Roadmap-модули из 7609ad8 не подключены ни к одному кодовому пути. Импортируются только тестами (task_safety и deepseek_adapter — вообще ничем). DeepSeekResponsesAdapter не зарегистрирован в _ADAPTERS → недостижим через get_adapter() |
| 62 |
gui_server.py + gui_cockpit.html |
1177 |
Параллельный веб-стек, не запускается ниоткуда; fastapi/uvicorn остаются обязательными зависимостями |
| 63 |
runtime.generate_chat_completion, antigravity_client.py, transform.py, openai_compat.py, cloudcode.py |
~590 |
Прямой API-путь к Antigravity, не используется (весь трафик через agy CLI) |
| 64 |
ui/views/dashboard_view.py («Главная») |
141 |
Не подключён к навигации |
| 65 |
ui/splash.py (SplashScreen) |
77 |
Не используется |
| 66 |
HealthTracker.get_status_summary() |
— |
Не вызывается ниоткуда |
| 67 |
Резолвинг hermes_home |
— |
Продублирован в 5 файлах с тремя разными реализациями (HERMES_HOME / LOCALAPPDATA / ~/.hermes) |
| 68 |
do_set_main/do_test_profile/do_delete_credentials |
— |
Дублируются между gui_server.py и hermes_hub_app.py |
10. P2 — Документация
| # |
Проблема |
| 69 |
docs/ARCHITECTURE.md и CHANGELOG.md описывают «Edge App Mode», «FastAPI Backend :8765», «health check gate (HTTP 200)» — этой архитектуры больше нет |
| 70 |
agents/AGENTS.md и отчёты в agents/done/ указывают Source of Truth E:\Agent projects\hermes-hub; ссылки вида file:///E:/Agent%20projects/... мертвы |
| 71 |
docs/SECURITY_MODEL.md не описывает фактическую подмену глобальной записи gemini:antigravity при маршрутизации |
| 72 |
Нет описания схемы router_profiles.yaml (единственный источник истины — код) и справочника CLI |
| 73 |
Отчёты в agents/done/ содержат утверждения, не подтверждаемые кодом: «35/35 PASSED», «интерактивные переключатели параметров», «9/9 экранов», «иконка вшита в лаунчер». Требуется выравнивание с фактами |
| 74 |
UI жёстко на русском, CLI на английском, строки не вынесены — i18n невозможен |
11. P2–P3 — Функциональные пробелы
| # |
Пробел |
| 75 |
Настройки не сохраняются: CTkSwitch/CTkOptionMenu не привязаны к self.settings (нет ни одного command=), _save_settings() пишет неизменённый словарь |
| 76 |
Ни один параметр не влияет на роутер: failover_attempts, session_affinity, auto_failover, model_timeout_sec, monitoring_interval_min нигде не читаются |
| 77 |
Пути в «Настройках» указывают на hermes\router_profiles.yaml и hermes\auth.json; реальные — hermes\config\router_profiles.yaml и hermes\agy_profiles\<id>\auth.json |
| 78 |
Нет ручного назначения профиля на роль (только автоматическое) |
| 79 |
Экран «Маршрутизация» read-only: нельзя изменить цепочку failover |
| 80 |
Нельзя добавить или удалить слот — 16 профилей жёстко зашиты в коде |
| 81 |
Кнопка «Обновить модели» не вызывает adapter.discover_models() — показываются preferred_models из конфига |
| 82 |
Фоновый мониторинг здоровья отсутствует, хотя переключатель для него есть |
| 83 |
Нет уведомлений (tray/toast) об исчерпании квоты и переключении на резерв |
| 84 |
Нет экспорта/импорта конфигурации и резервного копирования профилей |
| 85 |
Нет истории квот и использования |
| 86 |
Журнал живёт в памяти (200 записей) и обнуляется при перезапуске; hermes-hub.log пишется, но не читается обратно |
| 87 |
В журнале нет поиска и фильтра по уровню (только по категории) |
| 88 |
Тема жёстко тёмная, нет клавиатурной навигации и доступности |
| 89 |
compatibility.json заявляет linux/darwin, но Credential Manager, пути и лаунчер — только Windows |
12. Рекомендуемый план работ
Этап 1 — Вернуть работоспособность (P0, ~2 дня)
- Устанавливать
customtkinter/pillow в venv Hermes из установщика; проверять импорт после установки и падать с внятной ошибкой.
- Исправить
get_profile_dir (сделать @staticmethod либо импортировать функцию), добавить import json в мастер.
- Реализовать
AutoAssigner.auto_assign_all() либо убрать кнопки.
- Ввести маркер ошибки в
agy_generate ({"error": …} или исключение) и восстановить failover для Antigravity; покрыть регрессионным тестом.
- Починить мастер: статус
"failed", реальная валидация API-ключа, реальные модели, применение выбранной роли, обработчик assign_role.
- Прогонять
pytest в изолированном HERMES_HOME; убрать запуск установщика и сетевые вызовы из тестов; сделать test_setup_exe_exists skip-ом при отсутствии dist/.
Этап 2 — Корректность состояний и отзывчивость (P1, ~3 дня)
- Все сетевые операции — только в фоне;
after(0, …) получает готовые данные (включая _restore_status).
- Параллельный опрос профилей с общим бюджетом времени вместо последовательного.
- Обновление виджетов in-place вместо
destroy()/пересоздания в 5 экранах.
- Сброс
overall_state при истечении cooldown; единый источник истины по здоровью для роутера и UI.
- TTL и вытеснение в
SessionAffinityTracker; файловая блокировка router_state.json.
- Сузить
_CM_LOCK до операции подмены credential и восстанавливать прежнюю запись — либо перейти на изоляцию только через USERPROFILE.
- Связать
settings_view с реальными параметрами роутера; выровнять пути.
- Починить
check_duplicate_identity (сохранять email в auth.json при OAuth).
Этап 3 — Безопасность и упаковка (P1, ~2 дня)
- Решение по OAuth-клиенту; прекратить склейку секрета из фрагментов.
- ACL/DPAPI для
auth.json; санитизация логов; убрать токен из query-строки.
- Убрать
allow_origins=["*"] либо удалить веб-стек целиком.
- Выбрать один установщик; реальная проверка версии из
compatibility.json (сделать код 11 достижимым); исключить __pycache__; убрать E:/… fallback; встроить иконку через /win32icon.
- Логирование запуска в файл до инициализации UI; single-instance guard.
Этап 4 — Долг и документация (P2, ~2 дня)
- Подключить roadmap-модули к реальным кодовым путям (как минимум зарегистрировать
DeepSeekResponsesAdapter в _ADAPTERS) либо явно пометить их как экспериментальные.
- Удалить или вынести в
legacy/ веб-стек и прямой API-путь; убрать fastapi/uvicorn из обязательных зависимостей.
- Единый модуль
paths.py; устранить дублирование do_*.
- Синхронизировать версию (0.1.0), обновить
ARCHITECTURE.md/CHANGELOG.md под нативную архитектуру, исправить пути E:\.
- Выровнять отчёты в
agents/done/ с фактическим состоянием.
- Добавить CI (pytest + ruff) и конфигурацию ruff.
Этап 5 — Развитие (P3)
- Ручное назначение ролей и редактор цепочек failover.
- Динамические слоты, реальный
discover_models.
- Фоновый мониторинг + уведомления о квотах.
- Персистентный журнал с поиском; статистика использования.
- Стриминг ответов, учёт токенов и стоимости.
- Экспорт/импорт конфигурации, бэкап профилей.
13. Сводка
| Приоритет |
Пунктов |
Область |
Исправлено в 7609ad8 |
8 |
Кэш сканирования, resolver статусов, идентичность Windows, предпрогрев, assets, docs |
| P0 — блокеры |
9 |
Запуск приложения, failover Antigravity, падения UI, мастер подключения |
| P1 — критично |
51 |
Отзывчивость, корректность состояний, безопасность, тесты, упаковка |
| P2 — важно |
20 |
Неподключённый код, архитектурный долг, документация |
| P3 — развитие |
9 |
Новая функциональность |
| Итого к работе |
89 |
|