trading-system-docs/notes/trading/system-docs/modul-17-hermes-agent.md

158 lines
9.6 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.

# 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_call`**jeder 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 /status`**SUCCEEDED**.
- **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 `mkpayload`→`mk_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.
---
## Geändert
```
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.
```