Files
hermes-agent/README.md
T

12 KiB

Hermes Agent — Claude-Max-Proxy + Hermes selbst, alles auf einer Maschine

Repliziert den Proxy-Trick, den wir fuer ARIA nutzen: claude-max-api-proxy (npm) wrappt die Claude Code CLI — angemeldet mit deiner Claude-Max- Subscription — als OpenAI-kompatiblen /v1/chat/completions-Endpoint. Hermes Agent (Nous Research, CLI/Desktop-Agent) redet darueber mit Claude, ohne API-Kosten — laeuft ueber die Subscription, nicht ueber Anthropic-API-Billing.

Entscheidung (Stefan): Hermes Agent selbst laeuft auf DERSELBEN Maschine wie dieser Proxy-Stack — alles was zu Hermes gehoert auf eine Box, minimale Latenz, nur ein Rechner zum Pflegen. ARIA bleibt komplett unangetastet (eigenes Docker-Netz hermes-net, eigenes Claude-CLI-Login-Volume, kein gemeinsamer Container, laeuft ja ohnehin auf einer anderen Maschine als Hermes).

Architektur

hermes-agent (Nous Research, network_mode: host)
        |  HTTP, Authorization: Bearer <TOKEN>, http://localhost:8447/v1
        v
  hermes-gateway  (Port 8447, NUR auf 127.0.0.1 gebunden)
        |  prueft Bearer-Token, sonst 401
        v
  hermes-proxy    (Port 3456, NUR docker-intern im hermes-net erreichbar)
        |  claude-max-api-proxy -> Claude Code CLI (Claude-Max-Login)
        v
     Anthropic (ueber deine Claude-Max-Subscription)

hermes-agent-dashboard (0.0.0.0:9119, Hermes' eigenes Web-UI/Setup, per
                         Basic-Auth gesichert seit 19.07.2026 — Stefans
                         Entscheidung, siehe Sicherheitshinweis)

Warum das Gateway trotzdem noch da ist, obwohl alles auf einer Maschine laeuft: hermes-proxy selbst hat keine Authentifizierung (genau wie ARIAs Proxy) — jeder andere Container im selben Docker-Host koennte sonst unbemerkt Deine Claude-Max-Subscription mitbenutzen. Das Gateway (proxy-patches/gateway.js, pure Node, keine Extra-Deps) prueft den Authorization: Bearer <TOKEN>-Header gegen ein Shared Secret, bevor irgendwas an hermes-proxy weitergeleitet wird. Seit "alles auf einer Maschine" bindet das Gateway nur noch auf 127.0.0.1, nicht mehr nach aussen — kein anderer Rechner kommt mehr direkt ran.

Was uebernommen wurde (1:1 aus ARIAs Proxy-Setup)

  • Sed-Patches an der npm-CLI-Bridge (host-binding, --dangerously-skip-permissions, echter --system-prompt-Kanal, 24h-Timeout, Prompt via stdin) — generische Infra-Fixes, nicht ARIA-spezifisch.
  • Adapter-Patches (proxy-patches/openai-to-cli.js, cli-to-openai.js) — die OpenAI-tools <-> <tool_call>-Text-Bruecke. Generisch, 1:1 uebernommen.
  • NICHT uebernommen: ARIAs routes.js-Patch (Live-Tool-Stream/Not-Aus fuers Diagnostic-UI, ARIA-Bridge-spezifisch). Stattdessen gibt's hier das eigene gateway.js fuer die Auth, die ARIA in der Form nicht braucht.

Setup

  1. Hermes-Agent-Sourcecode daneben klonen (das offizielle Docker-Image liegt nicht auf Docker Hub, sondern wird per build: aus dem Repo gebaut):

    cd /pfad/zum/deploy-ziel
    git clone https://github.com/NousResearch/hermes-agent.git hermes-agent-src
    

    (dieser Clone ist in .gitignore — bleibt lokal, wandert nicht in unser Repo)

  2. .env anlegen:

    cp .env.example .env
    openssl rand -hex 32   # Ausgabe in .env bei HERMES_GATEWAY_TOKEN eintragen
    
  3. Model-Config fuer Hermes seeden (einmalig, VOR dem ersten Start — danach fasst Hermes die Datei nicht mehr an):

    mkdir -p hermes-data/agent-home
    cp hermes-agent-config/config.yaml.example hermes-data/agent-home/config.yaml
    

    Die Datei zeigt Hermes per provider: custom auf unseren hermes-gateway (http://localhost:${HERMES_GATEWAY_PORT}/v1) — ${HERMES_GATEWAY_PORT}/${HERMES_GATEWAY_TOKEN} werden von Hermes selbst beim Config-Laden aus den Container-Env-Vars expandiert, die kommen aus Deiner .env. Kein manuelles Ausfuellen noetig.

  4. Dashboard-Auth einrichten (Pflicht seit 19.07.2026 — hermes-agent-dashboard laeuft mit --host 0.0.0.0 und Hermes verweigert diesen Bind hart, wenn kein Auth-Provider registriert ist: Refusing to bind dashboard to 0.0.0.0 — the auth gate engages on non-loopback binds, but no auth providers are registered. Ohne diesen Schritt kommt der Dashboard- Container beim naechsten Schritt gar nicht hoch):

    # HERMES_DASHBOARD_USER in .env ist Klartext, HERMES_DASHBOARD_PASSWORD_HASH
    # ist NIEMALS das Klartext-Passwort, sondern der Hash daraus. Erzeugen kannst
    # Du den erst NACHDEM Schritt 5 unten (docker compose up -d) mindestens
    # hermes-agent hochgefahren hat -- gegen "hermes-agent", NICHT
    # "hermes-agent-dashboard" (der braucht den Hash schon zum Start, Henne-Ei):
    docker exec -it hermes-agent python -c \
      "from plugins.dashboard_auth.basic import hash_password; print(hash_password('DEIN-PASSWORT'))"
    

    ACHTUNG DOLLARZEICHEN-FALLE: der Hash hat das Format pbkdf2:sha256:<n>$<salt>$<hash> — zwei rohe $-Zeichen drin. Docker Compose interpretiert $xyz in .env-Werten selbst als Variablen- Referenz und ersetzt nicht aufloesbare Teile stillschweigend durch Leerstring (sichtbar als WARN The "xyz" variable is not set beim docker-compose up). Ohne Escaping kommt im Container ein verstuemmelter Hash an und das Dashboard bleibt bei "no auth providers are registered" haengen, obwohl alles "richtig" eingetragen aussieht. Jedes $ im Hash durch $$ ersetzen, bevor Du ihn in .env eintraegst (z.B. 260000$abc$def260000$$abc$$def). Anfuehrungszeichen um den Wert helfen dabei NICHT — Compose expandiert $ in .env-Werten unabhaengig davon ob sie gequotet sind.

    Am einfachsten den Hash direkt per sed in der Zeile escapen, statt ihn von Hand nochmal abzutippen (verdoppelt NUR die $ in genau dieser einen Zeile):

    sed -i '/^HERMES_DASHBOARD_PASSWORD_HASH=/ s/\$/$$/g' .env
    

    Zur Kontrolle, ob noch unverdoppelte $ uebrig sind (sollte leer sein):

    grep -o '\$[^$]' .env
    

    Output (den escapeten Hash, nicht das Passwort) in .env bei HERMES_DASHBOARD_PASSWORD_HASH eintragen, HERMES_DASHBOARD_USER nach Belieben setzen (Default admin). Der dashboard.basic_auth-Block dazu liegt schon fertig in hermes-agent-config/config.yaml.example (Platzhalter, die Hermes selbst aus den Container-Env-Vars expandiert — kein manuelles Ausfuellen noetig, siehe Schritt 3).

    WICHTIG, wenn Du Schritt 3 schon VOR dem 19.07.2026 gemacht hast: Schritt 3 kopiert config.yaml.example nur EINMALIG nach hermes-data/agent-home/config.yaml — kein automatischer Re-Sync danach. Der dashboard:-Block wurde erst nachtraeglich in die .example-Datei ergaenzt, landet also NICHT von selbst in einer bereits existierenden config.yaml. Ohne ihn bleibt Hermes bei "no auth providers are registered" haengen, egal wie sauber .env escaped ist (weil Hermes den Block ja gar nicht sieht). Pruefen:

    grep -A3 '^dashboard:' hermes-data/agent-home/config.yaml
    

    Kommt nichts zurueck → Block manuell anhaengen:

    cat >> hermes-data/agent-home/config.yaml <<'EOF'
    
    dashboard:
      basic_auth:
        username: "${HERMES_DASHBOARD_USER}"
        password_hash: "${HERMES_DASHBOARD_PASSWORD_HASH}"
    EOF
    

    Danach neu hochziehen: docker compose up -d --build hermes-agent-dashboard

  5. Stack starten:

    docker compose up -d
    

    Baut beim ersten Mal das Hermes-Image aus hermes-agent-src (dauert ein paar Minuten), startet dann hermes-proxy, hermes-gateway, hermes-agent (Gateway/Messaging-Prozess) und hermes-agent-dashboard. Falls HERMES_DASHBOARD_PASSWORD_HASH noch leer ist (Schritt 4 noch nicht gemacht): hermes-agent-dashboard bricht beim Start ab, die anderen drei Services laufen trotzdem — Hash nachtragen, dann docker compose up -d hermes-agent-dashboard.

  6. Einmalig Claude-CLI-Login (oeffnet Browser-OAuth mit deiner Claude-Max-Subscription):

    docker exec -it hermes-proxy claude
    

    Danach liegen die Credentials persistent in hermes-data/claude-config/ — ueberlebt Container-Restarts.

  7. Testen, dass der Proxy laeuft:

    curl -s http://127.0.0.1:${HERMES_GATEWAY_PORT:-8447}/v1/chat/completions \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer <HERMES_GATEWAY_TOKEN aus .env>" \
      -d '{"model":"sonnet","messages":[{"role":"user","content":"Sag Hallo"}]}'
    

    Ohne oder mit falschem Bearer-Token gibt's 401 unauthorized.

  8. Mit Hermes chatten:

    docker exec -it hermes-agent hermes chat
    

    oder das Dashboard direkt im Browser oeffnen: http://<diese-maschine>:9119 — bindet auf 0.0.0.0, kein SSH-Tunnel mehr noetig, dafuer fragt es jetzt den Basic-Auth-Login aus Schritt 4 ab. Traffic ist weiterhin unverschluesseltes HTTP (siehe Sicherheitshinweis unten) — fuer mehr als "kurz testen" gehoert ein TLS-Reverse-Proxy davor.

Mobiler Client (Android)?

Zwei GRUNDVERSCHIEDENE Richtungen, nicht verwechseln:

  • Unser Proxy oben (hermes-gateway -> hermes-proxy): Claude ALS Backend FUER Hermes. Kein Client redet direkt damit ausser Hermes selbst.
  • Hermes' EIGENER OpenAI-kompatibler API-Server (API_SERVER_HOST / API_SERVER_KEY, auskommentiert im hermes-agent-Service): Hermes ALS Server FUER einen Chat-Client. Aktivieren + API_SERVER_KEY in .env setzen, dann kann ein Client wie gpt_mobile oder Maid (Open Source, Android, freier base_url+Key, kein Messenger — siehe Chat-Verlauf) sich direkt gegen Hermes verbinden. Fuer sowas wie die ARIA-App (Wake-Word, Sprachsteuerung, TTS) braeuchte man dagegen einen eigenen App-Build — kein fertiges Produkt zum Runterladen.

Sicherheitshinweis

hermes-gateway bindet nur auf 127.0.0.1 — von aussen kommt niemand mehr ran, solange Du nicht selbst was aendertst (z.B. fuer den mobilen API-Server oben, oder falls Hermes doch mal auf eine zweite Maschine zieht).

hermes-agent-dashboard bindet seit 19.07.2026 bewusst auf 0.0.0.0 (Stefans Entscheidung — ein SSH-Tunnel machte mit dem SSL-Setup Probleme) und ist per Basic-Auth gesichert (siehe Setup-Schritt 4, Hermes verweigert den 0.0.0.0-Bind sonst ohnehin hart). Das ist Testzustand, kein Dauerbetrieb-Haerten: das Passwort geht als HTTP Basic-Auth unverschluesselt raus (kein TLS auf Port 9119), Keys/Config/Sessions liegen dahinter offen. Fuer Dauerbetrieb gehoert ein TLS-Reverse-Proxy (Caddy/nginx) davor, der Basic-Auth + Zertifikat uebernimmt — Dashboard-Port dann wieder 127.0.0.1, nur der Proxy von aussen erreichbar.

Generell gilt: wenn Du HERMES_GATEWAY_PORT, API_SERVER_HOST oder den Dashboard-Port doch mal direkt nach aussen exposest, ist der Traffic unverschluesseltes HTTP, kein TLS. Nicht direkt ins offene Internet haengen — SSH-Tunnel, WireGuard/Tailscale oder einen TLS-Reverse-Proxy davorschalten.

Verzeichnisse (nicht committet, siehe .gitignore)

  • hermes-agent-src/ — geklonter Hermes-Agent-Sourcecode (Docker-Build-Context)
  • hermes-data/claude-config/ — Claude-CLI-Login (Browser-OAuth-Session)
  • hermes-data/agent-home/ — Hermes' ~/.hermes-Aequivalent: Config (config.yaml, aus hermes-agent-config/config.yaml.example geseedet), Sessions, Memory, Skills, .env
  • .env — Secrets (Token, ggf. API_SERVER_KEY)

Wo deployen?

Diese Compose-Datei ist ortsunabhaengig. Stefan deployt sie selbst auf der Ziel-Maschine (nicht aria-wohnung — komplett getrennt von ARIA). Repo klonen, die drei Setup-Schritte oben (Hermes-Source klonen, .env, Config seeden), dann docker compose up -d.