Files
obsidian-vault/personal/tech/hermes-fork-vs-upstream.md
T

396 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <bot> 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 в `<HERMES_HOME>/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/<name>/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 память (наша фича)