Files
knowledge-base/decisions/2026-06-18-german-hermes-agent-deploy.md

13 KiB
Raw Blame History

date, tags
date tags
2026-06-18
decision
ai
hermes
telegram
lxc
assistant

German — Hermes Agent как личный супер-ассистент Олега (LXC 141)

Контекст

Олег попросил поставить Hermes Agent (NousResearch, open-source, MIT, Python, model-agnostic — из ресёрча ../notes/claude/2026-06-08-145004-найди-аналог-openclaw-для-меня-нужен-аркестратор-и) на homelab. Сначала — «для тестирования самого Hermes через Telegram», затем расширил: настроить как супер-помощника с полным доступом к базе знаний. Имя — Герман (German).

Что развёрнуто

  • LXC 141 german (10.0.0.141, Debian 12, 2 vCPU / 3 GB / 12 GB на local-lvm, nesting, unprivileged, onboot=1). Создан из debian-12-standard 12.12.
  • Hermes Agent v0.16.0 — установлен официальным install.sh --non-interactive --skip-setup. Код /usr/local/lib/hermes-agent, данные /root/.hermes (uv + managed Node).
  • Telegram-бот «Герман Непомнящий» @german_dttb_bot (id 8885932329). Gateway = systemd hermes-german.service (hermes gateway run --replace, Restart=always, drain 210s, crash-guard, NoNewPrivileges/PrivateTmp).

Модель

  • Провайдер OmniRoute (OpenAI-совместимый шлюз на LXC 132): base_url http://10.0.0.179:20128/v1.
  • Активная модель: cc/claude-opus-4-8 (Opus 4.8 via Max). Fallback-цепочка (2026-06-21): cx/gpt-5.5cc/claude-sonnet-4-6; api_max_retries: 1. (Было api_max_retries: 6 + fallback cc/sonnet-4-6 первым → German отвечал по 533с/9мин: 6 ретраев с бэкоффом на каждой капнутой Max-модели. Фикс: 1 попытка + независимый cx/gpt-5.5 первым → failover за секунды. Ответ 13с после фикса.) Fallback в Hermes срабатывает на 400 «out of extra usage» (проверено по логам cc→fallback) — поэтому рабочий fallback критичен. ⚠️ Прежний fallback kr/claude-sonnet-4.5 сдох: OmniRoute потерял креды провайдера Kiro («No credentials for provider: kiro», 2026-06-19) → German падал ПОЛНОСТЬЮ (и primary капнут, и fallback мёртв). cx/gpt-5.5 выбран финальным fallback потому, что Codex — отдельный провайдер, не Max → переживает полный кап Max-квоты. Проверено: cx/gpt-5.5 работает через агентский цикл с тулами+KB.
  • КОРЕНЬ 400 «out of extra usage» (исправленное понимание): это НЕ персистентное исчерпание квоты. Олег верно заметил: «если бы лимиты — ты (Claude на Opus 4.8) тоже бы не работал». Проверка по факту 2026-06-18 ~22:30: curl cc/claude-opus-4-8 к OmniRoute с system-prompt 14B / 2КБ / 8КБ → все 200. То есть 400 в 19:11/19:18 был транзиентным — краткий кап 5-часового окна Max в момент пиковой нагрузки (Max делят this-session/German/openclaw/swarmclaw/code-server). Окно отпускает само. Если 400 участятся — включить overflow (pay-as-you-go) на claude.ai/settings/usage.
  • ⚠️ cc/claude-opus-4-8 (Max) ФЛАПАЕТ 400: You're out of extra usage: прямой curl к OmniRoute то проходит (19:16), то нет — реальные запросы Олега падали (19:11 «Привет», 19:18 «Бенелюкс», разные request_id). Причина: включённая Max-квота Opus в текущем окне исчерпана (overflow/pay-as-you-go выключен), а окно делят openclaw (cc/opus-4-7) + swarmclaw (cc/opus-4-8) + code-server через тот же OmniRoute cc/*.
  • 400 — non-retryable BadRequestError → fallback НЕ срабатывает (Hermes уводит в fallback только на rate-limit/5xx/connection). Поэтому fallback на Sonnet от Opus-400 не спасает.
  • Решение: primary = kr/claude-sonnet-4.5 (free, Kiro/AWS, не флапает, не ест Max-квоту, не конкурирует с другими ботами Олега). Проверено: запрос «Бенелюкс» через тулы вернул корректную сводку по KB.
  • Вернуть Opus, когда окно Max освободится / включён overflow: sed -i 's|kr/claude-sonnet-4.5|cc/claude-opus-4-8|' /root/.hermes/config.yaml && systemctl restart hermes-german.
  • Доп. правки сессии: отключён display.platforms.telegram.streaming (было задвоение сообщений в Telegram); fallback-цепочка протестирована (формат fallback_providers: [{provider,model,base_url}], ключ берётся из env — работает), но снята, т.к. от 400 не помогает.
  • Ключ — OPENAI_API_KEY + OPENAI_BASE_URL в /root/.hermes/.env (chmod 600).
  • auxiliary (vision/web_extract/compression/session_search) → provider: main — иначе лезли бы в OpenRouter (ключа нет) и падали.
  • ⚠️ Грабля для будущего: оба воркфлоу-ревьюера по коду утверждали, что на приватный IP-эндпоинт OPENAI_API_KEY из env «гейтится по хосту» и нужен model.api_key: ${OPENAI_API_KEY} в config.yaml, иначе 401. Эмпирически опровергнуто — CLI- и gateway-вызовы к 10.0.0.179 проходят с env-ключом без model.api_key. Если когда-нибудь начнёт давать 401 на первом вызове модели — добавить api_key: ${OPENAI_API_KEY} в секцию model: и systemctl restart hermes-german.

База знаний (KB)

  • Зеркало vault клонировано в /root/german/knowledge-base (workspace = /root/german, systemd WorkingDirectory).
  • Обновление: /root/kb-pull.sh (cron */15) делает git fetch + git reset --hard origin/main — это read-only зеркало (правки агента там затираются; для своих заметок у него /root/german/notes + native memory).
  • Безопасность кред: из .git/config workspace убран токен Gitea (был https://oleg:TOKEN@...); remote сделан tokenless, креды вынесены в /root/.git-credentials (chmod 600, вне workspace).
  • Доступ Германа к KB: тулсеты file (read_file/grep) + terminal. Семантического индекса нет — навигация через knowledge-base/CLAUDE.md → grep. Это прописано в SOUL.md.

Конфиг (ключевое, /root/.hermes/config.yaml, 600)

  • platform_toolsets.telegramявный список [web, terminal, file, memory, todo, skills, session_search, tts] (+ kanban по дефолту).
  • Почему не пресет hermes-telegram: security-ревью (читало toolsets.py:440) показало, что пресет тянет полный core-набор (browser, execute_code, computer_use), а disabled_toolsets их НЕ убирает (категории — web/browser/terminal/code_execution/image_gen). Для бота с доступом к секретам это лишние каналы исхода. Проверено hermes tools --summary: на Telegram 9/26 тулов, browser/code-exec/computer-use отсутствуют.
  • web-поиск без ключей: web.backend: ddgs (DuckDuckGo, бесплатно). Browser недоступен (требует BROWSERBASE_API_KEY) — веб через ddgs + curl в terminal.
  • SOUL.md (личность «Герман», навигация по KB, правила безопасности) — 600.
  • Прочее: display.language: ru, session_reset: idle 2880m, memory_enabled, security.redact_secrets: true + tirith_enabled: true, stt (faster-whisper base — голосовые транскрибируются).

Безопасность — модель угроз

  • Доступ только у Олега: TELEGRAM_ALLOWED_USERS=1292155421, guest_mode: false, unauthorized_dm_behavior: ignore. Проверено по коду (telegram.py/authz_mixin.py): пустой allowlist = deny-all, fail-closed. Чужие сообщения молча игнорируются.
  • terminal=local + root = Герман может выполнять любой bash на 141 и читать секреты из KB. Это намеренно (DevOps-ассистент), защита держится на allowlist. Жёсткий systemd-сэндбокс (ProtectSystem/ProtectHome) не ставил — сломал бы функции. Будущее ужесточение: отдельный непривилегированный юзер.
  • redact_secrets: true — секреты вычищаются из tool output/логов/ответов перед доставкой.

Эксплуатация

  • Статус/логи: systemctl status hermes-german; логи в файлах /root/.hermes/logs/{gateway,agent}.log (не в journal).
  • Рестарт после правок config/SOUL: systemctl restart hermes-german (дренаж до ~180с).
  • Бэкапы конфигов: /root/.hermes/config.yaml.bak.*, .env.bak.orig.
  • Эндпоинт-санити: curl -s http://10.0.0.179:20128/v1/models (должен отдавать cc/claude-opus-4-8).

Проверено

  • Установка, конфиг грузится, hermes status → Model cc/claude-opus-4-8 / Custom endpoint.
  • End-to-end CLI: вопрос по KB → grep → верный ответ (IP openclaw 10.0.0.239).
  • Прямой вызов OmniRoute cc/claude-opus-4-8 (стрим).
  • Gateway: ✓ telegram connected (polling), getMe ok, allowlist на Олега.
  • Тулсет Telegram безопасен (без browser/code-exec/computer-use).
  • Live gateway-раунд-трип: сообщение Олега «Привет» прошло Telegram→allowlist→OmniRoute→Anthropic (ключ резолвится в gateway без model.api_key — опасения ревью не подтвердились). На cc/opus упёрлись в квоту Max (400); на kr/claude-sonnet-4.5 — KB-вопрос через тулы вернул верный IP.

NetBird (добавлено 2026-06-18)

Чтобы German дотягивался до клиентских площадок в mesh (как openclaw/swarmclaw).

  • IP 100.70.99.82, german.netbird.cloud, группа Claude-Diag. Setup-key в ../projects/dttb/credentials.md (64DF527E-…, создан через PAT/API).
  • Установка только через apt-репозиторий pkgs.netbird.io — официальный curl install.sh | sh редиректит на github.com/netbirdio/.../releases/download/v0.73.0/install.sh, а GitHub releases таймаутят из RU-сети (connection timed out). Шаги: добавить ключ pkgs.netbird.io/debian/public.key в keyring + deb [...] https://pkgs.netbird.io/debian stable mainapt install netbird (v0.73.0). Работает в unprivileged-LXC через nesting=1 + kernel WireGuard (wt0, tun не нужен).
  • Enroll с --disable-dns: netbird up --setup-key <KEY> --disable-dns --hostname german. Это сознательно — на LXC 132/137 NetBird-DNS (resolv.conf через wt0) уже ломал связность; German держит свой 1.1.1.1/8.8.8.8 и ходит по IP. Magic-DNS *.netbird.cloud в контейнере НЕ резолвится (search dttb.ru хайджачит) — только по IP 100.70.x.
  • Проверено: openclaw 100.70.167.54:18789 → 200; github/telegram/OmniRoute/DNS целы; netbird autostart enabled; hermes-german не пострадал.

TODO / на будущее

  • Fallback-цепочка (cc/sonnet-4-6, kr/sonnet-4.5, cx/gpt-5.4) — формат fallback_providers с api_key_env (НЕ ${VAR} — путь резолва фолбэков не делает env-подстановку). Пока не ставил (primary надёжен).
  • Настоящий web-search через self-hosted SearXNG (SEARXNG_URL) или ключ Tavily/Firecrawl — если ddgs будет мало.
  • Рассмотреть запуск под non-root юзером для уменьшения blast radius.