trading-system-docs/notes/trading/system-docs/modul-17-hermes-agent.md
Red Queen 7ae7249c00 feat(tolaria): migrate auto-safe knowledge batch 2 to schema v1
15 knowledge objects (log, note, index, module, history) migrated to
C3 knowledge schema v1. Bodies unchanged, metadata preserved.
2026-08-25 18:48:25 +00:00

14 KiB
Raw Permalink Blame History

id type role representation state knowledge_schema
object/3063a680-c35e-a62c-91b4-f382027930a5 arch module canonical current 1

Modul-17: Hermes-Agent (autonomer Research-/Analyse-Worker unter Paperclip)

Status: FREIGEGEBEN (21.08.2026) · Container Modul-17-Hermes-Agent · Image trading-modules-modul17-hermes-agent:latest

⚠️ Keine Namenskollision mit fremden Containern. Hermes läuft im trading-modules-Netz als Modul-17-Hermes-Agent. Fremdsysteme (paperclip-dn8l-paperclip-1 u.ä., Coolify) nie anfassen.

Zweck & Rolle

Hermes ist die autonome Research-/Analyse-Ausführungsebene unter Paperclip (M16). Er nimmt von Paperclip delegierte Tasks (Research, längere Analysen, Strategie-Review, Analytics-Auswertung, Backtest-/Optimization-Auswertung, Systemdiagnose READ-ONLY) entgegen und führt sie ausschließlich über die strikte Tool-Whitelist aus. Das Ergebnis geht als agent_run/result_refs an Paperclip zurück.

Kritische Grenze — Hermes ist NICHT Teil des kritischen Trading-Pfads und darf NICHT:

  • direkt auf Modul-09 Execution zugreifen, Orders erzeugen
  • Risk-/Portfolio-Regeln (M07/M08) überschreiben
  • M15-Control verändern (PAUSE/HALT/RESUME)
  • Strategien automatisch produktiv aktivieren
  • Optimierungsparameter automatisch deployen
  • Broker-Credentials erhalten

Paperclip bleibt Orchestrator. Hermes arbeitet NUR über die erlaubten Tools/APIs. Unbekannte oder unerlaubte Tools → REJECTED.

Architektur / Datenfluss

Paperclip (M16) ── Task ──▶ Modul-17-Hermes-Agent (Port 55017, NUR intern/expose)
                                  │
                                  ├── app/core/tool_whitelist.py  strikte Tool-Whitelist (Herzstück)
                                  ├── app/core/executor.py        Hermes-Executor (Tool-Ausführung + Audit + Idempotenz)
                                  ├── app/storage/storage.py      Persistenz hermes_task/run/tool_call/result
                                  ├── app/clients/clients.py      Read-Clients + 2 begrenzte Write-Clients
                                  └── app/api/main.py             REST-API (/health, /tasks, /runs, /tools)
                                  │
                                  ├── READ (tolerant): Modul-03 Market Data, M04 Regime, M05 Strategy,
                                  │                     M06 Ranking, M10 Journal, M11 Analytics, M15 Monitoring (GET)
                                  └── WRITE (begrenzt): Modul-12 POST /backtest, Modul-13 POST /optimization,
                                                        Proposal/Research-Ergebnis speichern

API (Docker-intern, Port 55017):

  • GET /health — Liveness ({"status":"ok","service":"hermes","version":"0.1.0"})
  • GET /health/ready — Readiness (DB-Schema vorhanden) → 200 {"status":"ready"}
  • POST /tasks — Task einreichen (task_type + payload.tools[]); Whitelist-Prüfung
  • GET /tasks / GET /tasks/{id} — Tasks + Status/result_refs
  • GET /runs/{id} — einzelner Run mit result_refs
  • GET /tools — Tool-Whitelist auflisten

Kein RabbitMQ: synchroner request/response. Port 55017 nur intern (expose:), kein öffentliches Mapping.

Tool-Whitelist (strikt, app/core/tool_whitelist.py)

Jeder Tool-Call wird gegen die Whitelist geprüft. Unbekannte/unerlaubte Tools → REJECTED.

Tool (namespace.method) Zielmodul Richtung Status
market_data.prices Modul-03 READ erlaubt
regime.analyze Modul-04 READ erlaubt
strategy.list Modul-05 READ erlaubt
ranking.current Modul-06 READ erlaubt
journal.entries Modul-10 READ erlaubt
analytics.portfolio Modul-11 READ erlaubt
monitoring.status Modul-15 READ erlaubt (NUR GET)
backtest.start Modul-12 WRITE erlaubt (CONTROLLED)
optimization.start Modul-13 WRITE erlaubt (CONTROLLED)
execution.place_order Modul-09 WRITE REJECTED
risk.set_limits Modul-07 WRITE REJECTED
portfolio.rebalance Modul-08 WRITE REJECTED
monitoring.pause/halt/resume Modul-15 Control REJECTED
alles andere REJECTED

M09/M07/M08 sind strukturell NICHT in den Clients implementiert (clients.py) — ein Verstoß ist damit zur Laufzeit unmöglich, zusätzlich zur Whitelist-Barriere. M15-Control-Writes fehlen ebenfalls.

Persistenz / Audit (Migration migrations/001_hermes.sql)

Vier Tabellen in Modul-01-PostgreSQL (PostgreSQL-01), NICHT im kritischen Trading-Pfad:

  • hermes_task — Aufgaben (id, task_type, agent_role, status, payload, result_refs, error_code)
  • hermes_run — Ausführungen (run_id, task_id, status, result, result_refs, idempotent)
  • hermes_tool_calljeder Tool-Call nachvollziehbar: task_id, tool, target_module, input_hash, status, timestamps, error_code
  • hermes_result — gespeicherte Ergebnisse/Reports

Jeder Tool-Call wird in hermes_tool_call auditier. Keine Secrets/Prompts mit Credentials in Logs.

Sicherheit

  • Keine öffentlichen Ports: 55017 nur expose (Docker-intern), kein Host-Mapping (verifiziert: docker port leer).
  • Internes Docker-Netzwerk trading-modules.
  • Kein Docker-Socket, kein Root-/Shell-Zugriff auf andere Container.
  • Secrets nur ENV/Secret (PG_PASSWORD aus Compose environment); keine Secrets in Logs/API/DB.
  • Keine Broker-Credentials (kein Client, kein ENV dafür).
  • Hermes-Ausfall beeinflusst M0316 NICHT (eigene DB-Tabellen, keine Kopplung an Trading-Core).
  • M15-Ausfall → Monitoring-/Systemdiagnose-Task FAILED (FAIL-CLOSED, nicht schönreden).

Read-Clients (tolerant)

Einzelne Quellen-Fehler (404/5xx/Timeout) werden als Teil-Ergebnis {"_error": code} gekapselt, der Gesamt-Task bleibt SUCCEEDED. Komplett unreachable (UNREACHABLE/TIMEOUT) → None.

Ausnahme — Monitoring (FAIL-CLOSED): Der monitoring.status-Call ruft M15 direkt auf. Ist M15 nicht erreichbar → Task FAILED (kein "ok" bei ausgefallenem M15). Gleiches gilt für M12/M13: Ausfall → zugehöriger Backtest-/Optimization-Task FAILED (UNREACHABLE), Hermes bleibt healthy, Trading-Core unbeeinflusst.

Idempotenz

Jeder task_id erzeugt genau einen hermes_run (SUCCEEDED). Wiederholte Einreichung derselben Task-Payload → je Task genau 1 Run, kein Doppel-Run. input_hash in hermes_tool_call identifiziert identische Aufrufe.

Tests & E2E-Verifikation (VPS, 21.08.2026)

  • Unit-Tests tests/test_hermes.py: 13/13 grün (Whitelist-Gates, Executor, Idempotenz, REJECTED).
  • T1 Research-Task → M03 → SUCCEEDED, result_refs mit tool_output + report.
  • T2 Analytics-Task → M11 → SUCCEEDED.
  • T3 Backtest-Task → M12 POST /backtest (BT_PULL, stock, 450 Candles) → SUCCEEDED.
  • T4 Optimization-Task → M13 POST /optimization (BT_PULL) → SUCCEEDED.
  • T5 Monitoring READ-ONLY → M15 GET /statusSUCCEEDED.
  • T6 Execution-Tool (execution.place_order) → REJECTED (TOOL_NOT_ALLOWED).
  • T7 M15-Control-Tool (monitoring.pause) → REJECTED.
  • T8 Risk/Portfolio-Write (risk.set_limits) → REJECTED.
  • T9 ungültiges Tool (bogus.tool) → REJECTED.
  • T10 Ausfall: M15 down → Monitoring FAILED; M12 down → Backtest FAILED; M13 down → Optimization FAILED (jeweils UNREACHABLE). Erholung → SUCCEEDED. Hermes bleibt healthy.
  • T11 Idempotenz: je Task genau 1 Run.
  • T12 Restart/Reconnect: nach docker restart ready, Daten persistent.
  • T13 keine Secrets: docker logs frei von password/secret/token/api_key.
  • T14 Port nur intern: ss -ltn kein Host-Listening auf 55017.
  • T15 Hermes-Ausfall: bei gestopptem Hermes sind M03/04/05/11/12/13/15/16 alle healthy (/health ok) → Trading-Core unbeeinflusst.

E2E-Gesamt: 15/15 grün.

Bugs / Fixes (modul-17, dokumentiert)

  1. Methodenname _last_succeeded_run vs _run_last_succeeded_run: Executor referenzierte _last_succeeded_run, Methode hieß anders → benannt in _last_succeeded_run (Idempotenz-Check).
  2. Test-Pfad: sys.path.insert musste auf ../app zeigen (nicht Modul-Root) für app.*-Importe.
  3. M12 422 "Unzureichende Daten": EURUSD/demo lieferte nur 1 Candlestick (< 220 nötig). Fix im E2E: Symbol BT_PULL (asset_class stock, 450 Candles) für Backtest/Optimization.
  4. M13 erfordert Pflichtfelder start_date+end_date (anders als M12): Optimization-Payload musste beide Datumsfelder enthalten, sonst HTTP 422.
  5. E2E-Skript: BusyBox wget im Alpine-Worker kennt kein --post-file → JSON per docker cp
    • --post-data="$(cat …)". Funktionsname mkpayloadmk_put (aufrufender Code nutzte mk_put).
  6. SSH-Quoting: verschachtelte $(…) mit doppelten Anführungszeichen im Inline-Python kappen das Kommando → Inline-Echo entfernt, reiner grep-Check.

Compose / Betrieb

  • Netz trading-modules, DB-Host Modul-01-PostgreSQL (Modul-01), Port 55017 nur intern (expose), restart: unless-stopped. Kein öffentliches Port-Mapping.
  • Kein Teil des kritischen Trading-Pfads: kein M09-, kein M07/M08-, kein M15-Control-Zugriff.
  • Paperclip (M16) bleibt Orchestrator; Hermes ist seine Worker-Ausführungsebene.

LLM-Schicht (KI-/Analyse-Erweiterung, M16 additiv llm=true)

Hermes kann Tasks zusätzlich über eine LLM-Schicht (Tool-Calling-Loop) ausführen. Die LLM-Schicht ist additiv (M16 setzt llm=true im Task-Payload); ohne Flag bleibt das deterministische M17-Verhalten unverändert. Die Tool-Whitelist und Task-Gates bleiben deterministisch außerhalb des LLM — das LLM führt nie selbst Tools aus, sondern liefert requested_tools[], die durch die bestehende Whitelist geprüft werden.

2-Stufen-Modellkonzept (final, 21.08.2026)

Stufe Modell Provider Verwendung
STANDARD deepseek-v4-flash:cloud ollama_cloud Normale Research-/Analyse-Tasks, voller Tool-Calling-Loop, Structured JSON, vollständiger Audit
LOKAL llama3.1:8b ollama_local NUR expliziter Privacy-/Offline-Modus (llm_mode=local), Analyse-only (kein Tool-Loop), CPU-/Timeout-Schutz

Regeln (hart):

  • KEIN automatischer Cloud→Local-Fallback. Cloud-Ausfall → Task FAILED (LLM_UNAVAILABLE), kein minutenlanger Wechsel auf das lokale Modell.
  • LOKAL nur explizit über llm_mode=local im Task-Payload (extra.llm_mode). Nicht als Standard verwenden.
  • LOKAL = Analyse-only: genau EIN Analyse-Call, keine Tool-Anfragen. llama3.1:8b auf CPU versteht den Tool-Calling-Loop nicht zuverlässig (LLM_MAX_ROUNDS) und ist langsam (~60-90s/Call).
  • FAIL-CLOSED: LLM down/Timeout/invalid JSON/Schema/Max-Runden → Task FAILED, kein Trading-Core-Effekt.
  • Safety unverändert: kein execution.place_order, kein risk.set_limits, kein portfolio.rebalance, kein M15 pause/halt/resume, Proposal maximal DRAFT/PROPOSED.

LLM-Audit (hermes_llm_run, Migration 002_hermes_llm.sql)

Fünfte Audit-Tabelle. Je LLM-Run: provider, model, config_version, input_context_hash, requested_tools, executed_tools, rejected_tools, tokens_in/out, latency_ms, structured_result, status, error_code. Vollständig AUCH im FAILED-Pfad (Timeout/Invalid-JSON/Max-Runden persistieren Metriken). Keine vollständigen sensiblen Prompts, keine Secrets.

LLM-ENV (Compose M17-Block)

  • LLM_PROVIDER=ollama_cloud (Standard = Cloud)
  • LLM_MODEL_CLOUD=deepseek-v4-flash:cloud
  • LLM_MODEL_LOCAL=llama3.1:8b
  • LLM_BASE_URL=http://10.0.4.2:11434 (Ollama-Netz)
  • LLM_TIMEOUT_SECONDS=300

LLM-E2E-Verifikation (final, 21.08.2026)

  • Cloud-Standard (llm=true, standard): SUCCEEDED (deepseek-v4-flash:cloud, 4 requested/2 executed, Audit vollständig).
  • Local explizit (llm=true, llm_mode=local): SUCCEEDED (llama3.1:8b, Analyse-only, Audit vollständig).
  • Cloud-down (isoliert, ungültige Base-URL): FAILED (LLM_UNAVAILABLE), kein Auto-Fallback (Provider bleibt Cloud).
  • llm=false: SUCCEEDED (deterministisches M17, kein LLM-Run).
  • Audit vollständig: requested/executed/rejected/tokens/latency/context_hash persistiert, auch im FAILED-Pfad.
  • M03M15 unbeeinflusst: alle Module healthy während der LLM-Tests.
  • Unit-Tests tests/test_llm.py: 20/20 grün (inkl. 4 REJECTED-Gate-Tests: place_order, monitoring_pause, risk_set_limits, unknown_tool).

LLM-Bugs / Fixes

  1. Audit-Metriken leer (Cloud-E2E): finish_llm_run schrieb nur status/result/error → um requested/executed/rejected_tools, tokens_in/out, latency_ms, structured_result, provider/model/config_version erweitert.
  2. FAILED-Pfad verlor Audit-Metriken: Max-Runden-Exception trug keine Metriken → Exception trägt jetzt Audit-Dict, execute() persistiert sie auch bei LLM_MAX_ROUNDS/Timeout/Invalid-JSON.
  3. NOT NULL-Constraint: requested_tools etc. NOT NULL DEFAULT '[]'finish_llm_run koerziert None[].
  4. Structured Output: Cloud-Modell ignoriert Schema ohne format-Constraint → format-Constraint im Payload.
  5. ToolCall-Format: ToolCall-Modell erwartet name/args (nicht tool/params) → Skript/Executor angepasst.
  6. Lokales Modell ungeeignet für Tool-Loop: llama3.1:8b auf CPU → LLM_MAX_ROUNDS (fragt endlos Tools an). Fix: lokaler Modus = Analyse-only (kein Tool-Loop), wie vom User empfohlen.

Geändert

Geändert von: Rain Ocampo
Datum: 21.08.2026
Grund: LLM-Schicht (2-Stufen-Konzept) ergaenzt: STANDARD=deepseek-v4-flash:cloud (voller Tool-Loop),
LOKAL=llama3.1:8b (nur explizit llm_mode=local, Analyse-only), KEIN Auto-Fallback, Cloud-down->FAILED
(LLM_UNAVAILABLE), llm=false deterministisch, Audit hermes_llm_run vollstaendig (auch FAILED-Pfad),
Unit 20/20, E2E 5/5 Szenarien gruen, M03-M15 unbeeinflusst. FREIGEGEBEN.
Geändert von: Rain Ocampo
Datum: 21.08.2026
Grund: Modul-17-Hermes-Agent implementiert + E2E verifiziert (Research/Analytics/Backtest/Optimization/Monitoring-Success,
Safety REJECTED 6/7/8/9, M12/M13/M15-Ausfall -> FAILED (FAIL-CLOSED), Idempotenz, Restart, keine Secrets, Port intern,
Hermes-Ausfall beeinflusst M03-16 nicht). Tool-Whitelist strikt, 4 Audit-Tabellen (hermes_task/run/tool_call/result),
6 API-Endpunkte. Unit 13/13, E2E 15/15 gruen. FREIGEGEBEN. Keine weiteren Module begonnen.