# Hermes — апстрим vs наш форк **Дата:** 2026-09-15 **Статус:** актуально — анализ расхождения сделан, **стратегия подтяжки НЕ выбрана** **Репо:** `~/.hermes/hermes-agent` (сабмодуль, форк `mallexxx/hermes-agent`) ## Текущее решение: не принято На 2026-09-15 Alex'у представлены 3 варианта (A merge / B перенос на свежий апстрим / C заморозка). **Выбор не сделан.** Работа над подтяжкой не начата. Наш форк запушен и стабилен — можно спокойно решать. Открытый вопрос, определяющий выбор: **нужны ли нам вообще 24k коммитов апстрима?** Мы используем Hermes как Zulip-бота с webhook-флоу; апстрим растёт десктопом/TUI/платформами, которые нам не нужны. Точечный cherry-pick (security-фиксы, фиксы `agent/`, новые модели) может быть достаточен. ## Масштаб расхождения | Метрика | Значение | |---------|----------| | Коммитов апстрима впереди | **24 407** | | Наших коммитов | 32 | | Common ancestor | `4ed63170e4` (2026-06-04) | | Дата последнего коммита апстрима | 2026-09-15 | | Наша версия | `0.15.1` | | Версия апстрима | `0.21.3` | | Файлов изменено апстримом | 12 634 | | Строк: +2 362 152 / −763 849 | | **Вывод:** это не «догнать на пару недель», а **~3.5 месяца и 24k коммитов**. Апстрим ушёл на мажорные версии вперёд (0.15 → 0.21). ## Ремоуты | Remote | URL | Роль | |--------|-----|------| | `origin` | `git@github.com:mallexxx/hermes-agent.git` | наш форк | | `upstream` | `https://github.com/NousResearch/hermes-agent.git` | источник | ## Главные изменения апстрима ### 1. Платформы переехали из ядра в плагины ⚠️ КРИТИЧНО Апстрим **удалил** `telegram.py`, `slack.py`, `matrix.py`, `discord.py` из `gateway/platforms/` — теперь они живут в `plugins/platforms/`. **Состав `plugins/platforms/` у апстрима (19):** `a2a, buzz, dingtalk, discord, email, feishu, google_chat, homeassistant, irc, line, matrix, mattermost, ntfy, photon, raft, simplex, slack, sms, teams, telegram, wecom, whatsapp` **Наш `plugins/platforms/` (8):** `discord, google_chat, irc, line, mattermost, ntfy, simplex, teams` **⚠️ Zulip НЕТ ни в ядре апстрима, ни в `plugins/platforms/` апстрима.** Проверено: `git ls-tree -r upstream/main | grep -i zulip` → пусто. Zulip — **полностью наша разработка**: - `gateway/platforms/zulip.py` — не существует в апстриме - `tools/zulip_thread_tool.py` — не существует в апстриме Это значит: наш Zulip-адаптер живёт в **устаревшем месте** (`gateway/platforms/`), которое апстрим планомерно опустошает. При подтяжке его придётся переносить в `plugins/platforms/` — иначе он повиснет на api, которого больше нет. ### 2. Смежные подсистемы Топ-скоупы по числу feat/refactor коммитов: | Скоуп | Коммитов | Что это | |-------|----------|---------| | `desktop` | 773 | Десктоп-приложение — крупнейшая новая подсистема | | `hermes_cli` | 607 | CLI переработан | | `gateway` | 412 | Ядро гейтвея | | `tools` | 339 | Инструменты | | `agent` | 217 | Ядро агента | | `cli` / `tui` / `tui_gateway` | 141/100/76 | TUI (Ink/React) | | `state` | 106 | Персистентность сессий | | `cron` | 91 | Планировщик | | `computer_use` | 86 | Управление десктопом | | `mcp` | 79 | MCP-клиент | | `relay` | 54 | Новая подсистема релея | Типы коммитов: `fix` 10 994, `refactor` 4 158, `feat` 2 113, `test` 1 836. **Читается так:** апстрим прошёл большую волну рефакторинга (4158) + массовый десктоп/TUI-фронт. Наши правки в `gateway/run.py` (13 коммитов) и `gateway/stream_consumer.py` (9) — ровно в эпицентре этого рефакторинга. ## Масштаб по коду (не «перетаскивание папок») Апстрим — это не реорганизация, а **+322 новых модуля**. Считать так: | Область | У нас | У апстрима | Нового | |---------|-------|-----------|--------| | `agent/` | 88 файлов | 229 | **+144** | | `tools/` | 86 файлов | 262 | **+178** | | `hermes_state*` | 1 монолит | 25 модулей | разбит | | `apps/` | — | 2674 файла | desktop-приложение | | `evals/` | — | 203 файла | eval-фреймворк | | `contributors/` | — | 1176 | данные контрибьюторов | | `plugin-catalog/` | — | 27 | каталог плагинов | Команда для повторной проверки: ```bash cd ~/.hermes/hermes-agent comm -13 <(git ls-tree --name-only main:agent/ | sort) \ <(git ls-tree --name-only upstream/main:agent/ | sort) | wc -l ``` ## Что реально нового в апстриме (по функциональности) **1. Desktop-приложение (`apps/desktop`, 2674 файла)** — крупнейшая подсистема (773 коммита в скоупе `desktop`). Нативный GUI. Внутри — `bootstrap-installer`, `shared`. Это фронт на TypeScript/Ink. **2. Bot Mode** (`website/docs/user-guide/bot-mode.md`) — **прямо про наш кейс.** Профили превращаются в roster именованных **ботов**: у каждого своя роль, модель, память, скиллы, аватар. Боты ходят в групповые чаты и **пишут друг другу**. Ключевое: «A Bot **is** a Hermes profile» — никакого нового примитива, всё видно из CLI (`hermes -p chat`), роутины в `hermes cron list`. Есть «forever-chat» на бота: `/new` внутри перехватывается в `/compact`. Секции (папки) для группировки, скрытие ботов, фильтр «Active now». **Сопоставление с нашим:** у нас Eagle + Whale + Balda роутятся через самодельный `plugins/whale-thread-guard` (файл `thread_ownership.json`). У апстрима это **нативная фича с UI**. ⚠️ Открытый вопрос для Alex — не выбрасывается ли наш роутинг в пользу нативного (см. «Ключевой вопрос» ниже). **3. Автономные циклы — три механизма:** - `/goal` — стоящая цель через turns; lightweight judge-модель после каждого turn проверяет достижение, агент сам кормит continuation prompt до успеха. Их версия Ralph loop (вдохновлено Codex CLI 0.128.0). - `/loop` — перезапуск промпта по таймеру **внутри сессии**; каждый wakeup — это реальный turn. Их версия Claude Code `/loop` (алиас `/proactive`). - `/heartbeat` — один промпт на сессию, срабатывает когда сессия idle. Разница: `/goal` judge-driven («работай пока не достигнешь»), `/loop` timer-driven («делай это каждые N минут»). **4. Encrypted credential vault** (`agent/vault_store.py`) — зашифрованное (Fernet) локальное хранилище для browser autofill. Profile-scoped, **model-blind**: модель видит только opaque handles + метаданные (kind, label, origin), значения резолвятся server-side и не попадают в tool results / логи / session DB. Три вида: `login`, `payment`, `address`. Ключ и файл 0600 в `/vault/`. Портировано из Merit-Systems/OpenInstinct (MIT). **5. Turn-цикл разобран на 29 модулей** — `turn_facade`, `turn_preflight`, `turn_preflight_gate`, `turn_recovery`, `turn_overflow`, `turn_truncation`, `turn_tool_round`, `turn_finalizer`, `turn_liveness`, `turn_retry_state` и др. `run_conversation` из монолита стал конвейером. **6. MCP переписан — 20 модулей:** `mcp_tool_discovery`, `mcp_oauth_provider`, `mcp_death_supervisor`, `mcp_tool_sampling`, `mcp_tool_schema`, `mcp_schema_cache`, `mcp_tool_health`, `mcp_tool_scope`, `mcp_oauth_device` и др. **7. Браузер — 16 модулей** `browser_tool_*`: CDP, cloud, lightpanda fallback, snapshot, vision, origin, session, lifecycle, install, real_profile, supervisor_dialogs/frames, eval_policy, vault_tool. **8. `native/fts5_cjk`** — C-расширение SQLite FTS5 для **китайского/японского/ корейского** токенизации полнотекстового поиска. Нативная сборка (`build.sh` + `vendor/sqlite3.h`). Прямо относится к багу китайского вывода — см. [[deepseek-language-drift]]. **9. Skills Hub — 8 модулей:** `skills_hub_github`, `skills_hub_official`, `skills_hub_clawhub`, `skills_hub_skillssh`, `skills_hub_search`, `skills_hub_install`, `skills_hub_sources`, `skills_hub_models`. Плюс `skill_linter`, `skill_ledger`, `skill_manager_batch`, `skill_manager_guards`. **10. Прочее новое:** `pets` (питомцы в UI), `skins`, `personality`, `mixture-of-agents` (`moa_loop.py`, `moa_trace.py`), `honcho` (memory-провайдер), `code_kernel` + `code_kernel_remote` (Python-кирнел), `deliverable-mode` (файлы как нативные вложения в мессенджерах), `credential-pools`, `provider-routing`, `subscription-proxy`, `tool-gateway`, `tool-search`, `kanban-multi-gateway`, `relay` (NeMo Relay runtimes, 54 коммита), `lsp`, `heartbeat`, `goals`, `loops`, `curator`, `skills`, `context-references`, `document-extraction`, `batch-processing`, `computer-use`, `codex-app-server-runtime`, `plugin-catalog`, `extending-the-dashboard`. **Новые top-level каталоги:** `apps/`, `native/`, `evals/`, `contributors/`, `plugin-catalog/`, `providers/`, `locales/`, `web/`, `website/i18n`. ## Ответ: есть ли замена Zulip? **Нет.** Проверено по существу — по модели общения, а не по имени платформы. | Платформа | Треды | Self-hosted | Stream+topic | |-----------|-------|-------------|--------------| | `matrix` | ✅ threads | ✅ | ❌ room-based | | `mattermost` | ✅ thread-mode replies | ✅ | ❌ channel-based | | `slack` | ✅ threads | ❌ SaaS | ❌ | | `teams` | ✅ | ❌ SaaS | ❌ | | `google_chat` | ❌ | ❌ SaaS | ❌ | | `irc` | ❌ | ✅ | ❌ flat | | `simplex` | ❌ groups | ✅ p2p | ❌ | | `a2a` | — | ✅ | — (agent-to-agent, не чат) | Проверка на stream+topic: `git grep -li 'stream.*topic' upstream/main -- plugins/platforms/` → только `dingtalk` и `telegram`, и там это не топик-модель. **Ключевое различие:** Zulip — единственная известная чат-платформа с нативной моделью «поток → топик», где топик = **адресуемая сущность** с собственным именем. Matrix и Mattermost дают треды как *свойство сообщения* (reply-thread), а не как *адресуемую сущность*. У нас `stream::topic` — это **ключ роутинга** (`thread_ownership.json`: `"stream::topic": "eagle"|"whale"`), на их модели такой ключ не построить. **Вывод:** миграция на matrix/mattermost = потеря топик-роутинга между ботами. `whale-thread-guard` на их модели не переносится. Подробности платформ: `plugins/platforms//plugin.yaml` — там `description`, `requires_env`, `optional_env` с описаниями (`MATTERMOST_REPLY_MODE: 'thread'|'off'`, `MATRIX_HOMESERVER`, mention-gating и т.д.). ## Ответ: сделаны ли custom prompts как у нас? **Нет, аналога не существует.** Проверено: ```bash git grep -l 'prompt_overrides' upstream/main # пусто git grep -l '_load_prompt_block' upstream/main # пусто ``` В апстриме **все** блоки промпта — **захардкоженные константы** в `agent/prompt_builder.py`: `MEMORY_GUIDANCE`, `USER_PROFILE_GUIDANCE`, `SESSION_SEARCH_GUIDANCE`, `SKILLS_GUIDANCE`, `KANBAN_GUIDANCE`, `TOOL_USE_ENFORCEMENT_GUIDANCE`, `TASK_COMPLETION_GUIDANCE`, `PARALLEL_TOOL_CALL_GUIDANCE`, `OPENAI_MODEL_EXECUTION_GUIDANCE`, `GOOGLE_MODEL_OPERATIONAL_GUIDANCE`, `HERMES_AGENT_HELP_GUIDANCE` (+ вариант `_NO_SKILLS`), `DEFAULT_AGENT_IDENTITY`, `PLATFORM_HINTS` (по платформе). `build_system_prompt_parts()` в апстриме просто делает `stable_parts.append(CONST)` — точки подмены из файла нет. **Что у апстрима ЕСТЬ для кастомизации:** - `SOUL.md` — идентичность агента (есть и у нас, грузится через `load_soul_md()`) - context-файлы из CWD (`AGENTS.md`, `HERMES.md`) — `_find_hermes_md()`, `_scan_context_content()`, `_strip_yaml_frontmatter()` - `system_message` из конфига **Чего НЕТ:** подмены отдельных блоков промпта файлами. То есть наш `_load_prompt_block()` + `agent.prompt_overrides` — **уникальная фича форка** (коммит `207a1de16`). См. [[hermes-agent-improvements]]. **Практический смысл различия:** у нас правка промпта = редактирование `.md` в `hermes-whale/review/` без касания Python. У них = правка Python-константы в `prompt_builder.py`, который апстрим постоянно переписывает (131 коммит). **`agent/thread_scoped_output.py`** — существует в апстриме. У нас thread-scoped память сделана с нуля (`972004546f`). Стоит сравнить перед вариантом B: возможно, у апстрима есть готовое решение лучше. Не проверено детально. ## НАШИ 32 КОММИТА Сгруппированы по темам: **Zulip-платформа (наша, в апстриме отсутствует):** - `b0f097a299` feat: add Zulip platform adapter (parallel to Discord) - `ce093b0a75` zulip: switch approval prompt emojis ✅/❌ → 👍/👎 - `b1e36eb2d7` fix(zulip): lock→locked emoji name - `f39671ba97` fix(routing): pre_gateway_dispatch before session guard - `d4e98a8b07` / `ef69e76413` / `40149225af` / `ddc6aa7c46` webhook: zulip in BUILTIN_DELIVER_PLATFORMS, session_chat_id из X-Chat-Id - `8255ea0c64` feat(zulip): session_search читает Zulip thread history - `049922d8cc` fix(session_search): dual Zulip+SQLite recall, FTS5 sanitization **Webhook no-edit streaming (наш флоу без редактирования сообщений):** - `fa271d2d19`, `ee41e8a847`, `9003d4cbbe` (revert), `1ac06d593a`, `9253fe52cc`, `88cbb77b95`, `6c95153aa8`, `eb8dd7b5ed`, `a92840dcde`, `a08527e85c`, `a88bec1762`, `5d3e95841b`, `ec9efce7e7` **Фичи профиля Whale:** - `972004546f` feat: thread-scoped memory + configurable memory instruction - `4ebad4f692` test: thread-scoped memory persistence, drift guard, snapshot - `da146da5fa` feat: configurable background review prompts and allowed toolsets - `207a1de16c` feat: load stable system-prompt blocks from config prompt_overrides **Прочее:** - `6e09a3704a` fix(approval): interrupt pending approvals on user message - `a17d02f31e` fix(api_server): tool.progress → plain-text chunks - `81f10c306f` approval: reaction emoji на webhook ## Риск конфликтов при merge Dry-run `git merge-tree` → **16 конфликтных файлов, 160 конфликтных маркеров**. | Файл | Наших коммитов | Коммитов апстрима | Риск | |------|---------------|-------------------|------| | `gateway/run.py` | 13 | **974** | 🔴 экстремальный | | `cron/scheduler.py` | 1 | 307 | 🟠 | | `agent/agent_init.py` | 3 | 226 | 🟠 | | `gateway/platforms/base.py` | 2 | 208 | 🟠 | | `gateway/platforms/api_server.py` | 1 | 177 | 🟠 | | `agent/model_metadata.py` | 1 | 164 | 🟡 | | `tools/approval.py` | 1 | 147 | 🟡 | | `agent/prompt_builder.py` | 0 | 131 | auto-merge ok | | `gateway/stream_consumer.py` | 9 | 73 | 🟠 (наш файл, апстрим переписал) | | `agent/system_prompt.py` | 2 | 65 | 🟡 | | `gateway/platforms/webhook.py` | 7 | 59 | 🟠 | | `toolsets.py` | 2 | 62 | 🟡 | | `tools/session_search_tool.py` | 2 | 41 | 🟢 | | `tools/memory_tool.py` | 1 | 34 | 🟢 | | остальные | 1 | <30 | 🟢 | **Худший случай:** `gateway/run.py` — 974 коммита апстрима против наших 13. Это фактически переписывание файла. Конфликты там будут не «принять/отклонить строку», а разбор чужой новой архитектуры. ## Стратегии подтяжки ### Вариант A: merge (сейчас) — НЕ рекомендуется `git merge upstream/main` → 16 конфликтных файлов, ручной разбор 160 маркеров в горячих файлах. Оценка: **дни на `run.py` + `stream_consumer.py`**, риск потерять наши webhook/Zulip-правки. ### Вариант B: перенос нашей функциональности на свежий апстрим (рекомендуется) Взять чистый `upstream/main` как новую базу, затем **перенести наши фичи патчами заново**: 1. Сохранить наши diff'ы как patch-файлы (`git format-patch`) 2. Форк от свежего апстрима 3. Переносить по темам: Zulip-адаптер (в `plugins/platforms/`!), 3 фичи Whale (thread-scoped memory, bg-review, prompt_overrides), webhook-стриминг 4. Webhook-стриминг — вероятно **уже решён апстримом**, проверить перед переносом **Плюс:** результат на актуальной кодовой базе, без 24k коммитов долга. **Минус:** ручная работа по переносу, ~1-2 дня. ### Вариант C: заморозить и жить на форке Принять, что мы отдельный продукт. Апстрим берём точечно (`git cherry-pick` отдельных фиксов безопасности). Дёшево сейчас, растёт долг потом. ## Ключевой вопрос перед подтяжкой **Нужны ли нам вообще 24k коммитов апстрима?** Мы используем Hermes как **Zulip-бота с webhook-флоу**. Ядро апстрима прирастает десктопом, TUI и платформами (telegram/slack/...), которые нам не нужны. Реальная ценность от апстрима для нас: - фиксы безопасности (`tools/tirith_security.py`, approval) - фиксы багов в `agent/` (метаданные моделей, сжатие контекста) - поддержка новых моделей/провайдеров Это можно брать точечно (cherry-pick), не таща весь долг. ### Что уточнилось 2026-09-15 (второй проход) Alex задал три прямых вопроса, ответы получены (детали — в разделах выше): | Вопрос | Ответ | |--------|-------| | Что нового в апстриме? | Не только платформы: +322 модуля, desktop, Bot Mode, `/goal` `/loop` `/heartbeat`, vault, turn-рефакторинг, MCP, Skills Hub | | Есть ли замена Zulip? | **Нет** — ни одна платформа не даёт stream+topic как адресуемую сущность | | Сделаны ли custom prompts как у нас? | **Нет** — блоки захардкожены, `prompt_overrides` уникален для форка | **Новый открытый вопрос (важнее прежних):** Bot Mode апстрима делает нативно то, что у нас держится на самодельном `whale-thread-guard` + `thread_ownership.json`. Если переезжать на вариант B — надо решить, **переносить наш роутинг или выбрасывать в пользу нативного Bot Mode**. Этот вопрос не задан Alex'у явно, вынесен как следующий шаг. **Также не проверено:** `agent/thread_scoped_output.py` в апстриме vs наша thread-scoped память — возможно, у них готовое решение лучше нашей реализации. **Статус:** стратегия подтяжки по-прежнему **не выбрана**. Форк запушен и стабилен. ## Команды для проверки расхождения ```bash cd ~/.hermes/hermes-agent # Обновить инфу об апстриме git fetch upstream # Насколько отстали git rev-list --count main..upstream/main # апстрим впереди git rev-list --count upstream/main..main # мы впереди # Общий предок git merge-base main upstream/main # Изменения по нашим файлам git diff --name-only $(git merge-base main upstream/main)..main | sort -u # Насколько апстрим трогал конкретный файл git rev-list --count $(git merge-base main upstream/main)..upstream/main -- gateway/run.py # Dry-run merge (безопасно, не трогает рабочую копию) git merge-tree --write-tree --name-only main upstream/main # Есть ли файл в апстриме git cat-file -e upstream/main:gateway/platforms/zulip.py && echo yes || echo NO ``` ## Связанное - [[hermes-git-repo]] — структура репо, .gitignore, что коммитить - [[hermes-whale-system-prompt]] — prompt_overrides (наша фича) - [[hermes-memory-architecture]] — thread-scoped память (наша фича)