147 lines
9.6 KiB
Markdown
147 lines
9.6 KiB
Markdown
# Modul-16: Paperclip (Agent-Orchestrierungsebene)
|
||
|
||
**Status: FREIGEGEBEN (21.08.2026)** · Container `Modul-16-Paperclip` · Image `trading-modules-modul16-paperclip:latest`
|
||
|
||
> ⚠️ **Achtung Namenskollision:** Es existiert ein **Fremdsystem** `paperclip-dn8l-paperclip-1`
|
||
> (Coolify, Hostinger hvps-paperclip) im `coolify`-Netz. Dies ist **NICHT** unser Modul-16.
|
||
> Unser Modul-16 läuft im `trading-modules`-Netz als `Modul-16-Paperclip`. Fremdsystem nie anfassen.
|
||
|
||
## Zweck
|
||
Paperclip ist die **übergeordnete Agent-/Orchestrierungsebene** des Trading-Systems. Er nimmt
|
||
Research-, Analytics-, Strategieüberprüfungs-, Backtest-, Optimization- und Monitoring-Aufträge
|
||
entgegen, wählt eine **Agent-Rolle** (Strategy/Research/Analytics/Validation/System) und führt sie
|
||
deterministisch aus. Später (V2) können daraus Hermes-Agent-Aufgaben entstehen.
|
||
|
||
**Kritische Grenze:** Paperclip ist **NICHT Teil des kritischen Trading-Pfads.**
|
||
- ✅ Er liest READ-ONLY aus Modul-03–15 und startet klar begrenzte, nicht-kritische Writes (Backtest, Optimization).
|
||
- ❌ Er DARF KEINE Broker-Orders senden, Modul-09 nicht zur Orderausführung anweisen, Risk-/Portfolio-Regeln nicht
|
||
überschreiben, M15-Safety nicht überschreiben, Strategien nicht automatisch produktiv aktivieren,
|
||
Optimierungsparameter nicht automatisch deployen.
|
||
- **Trading-Core (M03–15) läuft bei komplettem Ausfall von Modul-16 unverändert weiter.**
|
||
|
||
## Architektur / Datenfluss
|
||
```
|
||
Client/Auftrag
|
||
│ POST /tasks (task_type + payload)
|
||
▼
|
||
Modul-16-Paperclip (Port 55016, NUR intern/expose)
|
||
│
|
||
├── app/core/orchestrator.py Rollen-Wahl + deterministische Ausführung
|
||
├── app/core/proposal_gate.py Proposal-Gate (NIE Auto-APPROVED/aktiviert)
|
||
├── app/storage/storage.py Persistenz agent_task/agent_run/agent_proposal/agent_audit
|
||
├── app/clients/clients.py Read-Clients + 2 begrenzte Write-Clients
|
||
└── app/api/main.py REST-API (/health, /tasks, /proposals)
|
||
│
|
||
├── Read-Only (tolerant): Modul-03/04/06 (research), M05+M11 (strategy_review),
|
||
│ M11 (analytics), M15 (monitoring, NUR GET)
|
||
└── Write (begrenzt): Modul-12 POST /backtest, Modul-13 POST /optimization
|
||
```
|
||
|
||
**API (Docker-intern, Port 55016):**
|
||
- `GET /health` — Liveness (`{"status":"ok","service":"paperclip","version":"0.1.0"}`)
|
||
- `GET /health/ready` — Readiness (DB + Schema vorhanden) → 200 `{"status":"ready"}`
|
||
- `POST /tasks` — Auftrag einreichen (validiert; unbekannte/`execute_order` → 422)
|
||
- `GET /tasks` / `GET /tasks/{id}` — Aufträge + Status/result_refs
|
||
- `POST /tasks/{id}/run` — Aufgabe synchron ausführen (erzeugt `agent_run`)
|
||
- `POST /proposals` — Proposal als DRAFT erzeugen (APPROVED via Paperclip → 422)
|
||
- `GET /proposals` / `GET /proposals/{id}`
|
||
|
||
Kein RabbitMQ: synchroner request/response. Port 55016 ist **nur intern** (`expose:`), kein öffentliches Mapping.
|
||
|
||
## Agent-Rollen (V1, vorbereitet)
|
||
| Rolle | task_type | liest | schreibt |
|
||
|-------|-----------|-------|----------|
|
||
| `analytics` | `analytics` | Modul-11 (portfolio/strategy/regime) | — |
|
||
| `research` | `research` | Modul-03/04/06 | — |
|
||
| `strategy` | `strategy_review` | Modul-05 (Strategien) + Modul-11 | — |
|
||
| `validation` | `backtest`, `optimization`, `validation` | Modul-12/13 | M12 POST /backtest, M13 POST /optimization |
|
||
| `system` | `monitoring` | Modul-15 (NUR GET) | — |
|
||
|
||
Alle Rollen sind per `ENABLED_ROLES` registriert; es werden **nicht unnötig viele aktiviert**.
|
||
`TASK_TO_ROLE` mappt `task_type → Rolle`.
|
||
|
||
## Proposal-Prinzip (ProposalGate)
|
||
Wenn Paperclip eine Änderung empfiehlt, wird sie **als Proposal** gespeichert, niemals direkt angewendet:
|
||
|
||
```
|
||
STRATEGY_CHANGE_PROPOSAL mit:
|
||
proposal_id, source/agent, strategy, aktuelle Version, vorgeschlagene Änderung (JSON),
|
||
Begründung (rationale), Analytics-/Backtest-Referenzen, Confidence, created_at, status
|
||
Status: DRAFT / PROPOSED / REVIEWED / APPROVED / REJECTED
|
||
```
|
||
|
||
- `create_proposal` läuft ausschließlich über `ProposalGate` (nur `status in {DRAFT, PROPOSED}` erlaubt).
|
||
- Paperclip kann `APPROVED` **NIE selbst setzen** (validiert in `proposal_gate.py`; API gibt 422).
|
||
- Proposal wird mit `auto_activated=false` persistiert — **keine automatische Aktivierung**.
|
||
- **Keine automatische Übernahme optimierter Parameter** in Produktion.
|
||
|
||
## Sicherheit
|
||
- **Keine öffentlichen Ports**: 55016 nur `expose` (Docker-intern), kein Host-Mapping.
|
||
- **Internes Docker-Netzwerk** `trading-modules`.
|
||
- **Secrets nur ENV/Secret** (`PG_PASSWORD` aus Compose `environment`); keine Secrets in Logs/API/DB.
|
||
- **Keine Broker-Credentials.**
|
||
- **Kein Docker-Socket-/Root-Recht, kein Shell-Zugriff auf andere Container.**
|
||
- **M15-Ausfall kann Paperclip nicht dazu bringen, Safety zu umgehen** (Paperclip hat KEINE M15-Control-Write-Methode).
|
||
- **Paperclip-Ausfall stoppt Trading nicht** (eigene DB-Tabellen, kein Einfluss auf Trading-Core).
|
||
|
||
## Persistenz / Audit (Migration `migrations/001_paperclip.sql`)
|
||
- `agent_task` — Aufträge (id, agent_role, task_type, status, payload, result_refs, error_*).
|
||
- `agent_run` — Ausführungen (run_id, task_id, agent_role, status, result, idempotent).
|
||
- `agent_proposal` — Vorschläge (proposal_id, source, strategy, proposed_change, rationale, references, confidence, status, auto_activated).
|
||
- `agent_audit` — lückenloses Audit-Log (actor, action, detail, task_id, run_id, proposal_id).
|
||
|
||
Jede Agent-Aktion (task_created, run_started, run_succeeded, run_failed, run_rejected, proposal_*) wird in `agent_audit` protokolliert.
|
||
|
||
## Read-Clients (tolerant)
|
||
Einzelne Quellen-Fehler (404/5xx/Timeout) werden über `_safe_get` als `{"_error": code, "_message": …}`
|
||
Teil-Ergebnis gekapselt → der Gesamt-Task bleibt SUCCEEDED. Nur wenn ein Service **komplett unreachable**
|
||
(`UNREACHABLE`/`TIMEOUT`) ist, liefert er `None`. Write-Aktionen (Backtest/Optimization) bleiben strikt FAILED bei Fehlern.
|
||
|
||
**Ausnahme — Monitoring (FAIL-CLOSED):** Der `monitoring`-Task ruft M15 **nicht** über `_safe_get`,
|
||
sondern direkt auf. M15 ist die autoritative Trading-Status-Quelle — ist sie nicht erreichbar, wird der
|
||
Monitoring-Task **FAILED** (kein „ok"-Status bei ausgefallenem M15). Bei M12/M13-Ausfall → betroffener
|
||
Task (Backtest/Optimization) FAILED, M16 bleibt healthy, Trading-Core unbeeinflusst.
|
||
|
||
## Tests & E2E-Verifikation (VPS, 21.08.2026)
|
||
- **Unit-Tests** `tests/test_paperclip.py`: 10/10 grün (ProposalGate-Safety, Task-Validierung, Worker, Idempotenz, Auto-Aktivierungs-Verbot).
|
||
- **Backtest-Flow**: M16 → M12 `POST /backtest` → **SUCCEEDED**, `agent_run` + `run_id`/`result_refs` korrekt (`kind:"backtest"`).
|
||
- **Optimization-Flow**: M16 → M13 `POST /optimization` → **SUCCEEDED**, `run_id`/`result_refs` korrekt (`kind:"optimization"`).
|
||
- **Monitoring-Flow**: M16 → M15 `/monitoring/status` (READ-ONLY) → **SUCCEEDED**, Ergebnis gespeichert.
|
||
- **Research-Flow**: M16 → M03/M04/M06 → **SUCCEEDED** (M04 404 „Kein Regime" als Teil-Ergebnis gekapselt, kein Crash).
|
||
- **Strategy-Review-Flow**: M16 → M05+M11 → **SUCCEEDED**.
|
||
- **Proposal**: erstellt als `DRAFT`/`auto_activated=false`; `APPROVED` → 422. **NICHT auto-aktiviert.**
|
||
- **Kein M09-Zugriff**: `task_type=execute_order` → 422; kein `ExecutionClient`, keine `/order`-Endpunkte, kein `execution_base_url`.
|
||
- **Kein M15-Override**: Paperclip hat nur GET-Methoden auf M15, keine Control-Write.
|
||
- **Ausfall-Toleranz**: M12 down → backtest FAILED, M13 down → optimization FAILED, M15 down → monitoring
|
||
**FAILED (FAIL-CLOSED)**, M15 up → SUCCEEDED. M16 bleibt in allen Fällen **healthy**, Trading-Core unbeeinflusst,
|
||
Erholung → SUCCEEDED.
|
||
- **Idempotenz**: erneuter `run` erzeugt neuen `agent_run`, überschreibt frühere Runs nicht.
|
||
- **Ungültige Aufgabe**: `task_type=delete_database` → 422.
|
||
- **Keine Secrets in Logs/API**: `docker logs` frei von password/secret/token/api_key.
|
||
- **Health/Ready**: beide 200.
|
||
|
||
## Bugs / Fixes (modul-16, dokumentiert)
|
||
1. **`references`-SQL-Keyword** → Spalte in Migration + Queries als `"references"` gequoted.
|
||
2. **Migration-Commit**: psycopg2 braucht `conn.autocommit = True` (Muster wie M15), sonst bleiben Tabellen
|
||
uncommittet → `relation agent_task does not exist` bei `/health/ready`. Behoben in `_connect`.
|
||
3. **create_task RETURNING-Mismatch**: RETURNING listete 7 Spalten, `_task_row` erwartete 10 →
|
||
`IndexError`. Behoben: vollständige Spaltenliste.
|
||
4. **Research-Einzelfehler-Crash**: Einzelne fehlende Quelle (M04 404) failte den ganzen Research-Task.
|
||
Behoben: `_safe_get` kapselt Einzelquellen-Fehler, Task bleibt SUCCEEDED mit Teil-Ergebnissen.
|
||
5. **Monitoring-FAIL-CLOSED (21.08.2026)**: `monitoring`-Task nutzte `_safe_get`, was M15-`UNREACHABLE`
|
||
zu `None` kapselte → Task blieb SUCCEEDED trotz ausgefallenem M15. Fix: `_monitoring` ruft M15 **direkt**
|
||
auf, damit ein M15-Ausfall als `ClientError` weiterfaellt und der Task **FAILED** wird. Verifiziert:
|
||
M15 down → monitoring FAILED, M15 up → SUCCEEDED.
|
||
|
||
## Compose / Betrieb
|
||
- Netzwerk `trading-modules`, DB-Host `Modul-01-PostgreSQL` (Modul-01), Port 55016 nur intern (`expose`),
|
||
`restart: unless-stopped`. Kein öffentliches Port-Mapping.
|
||
- **Kein Teil des kritischen Trading-Pfads:** kein M09-, kein M15-Control-Zugriff.
|
||
|
||
---
|
||
## Geändert
|
||
```
|
||
Geändert von: Rain Ocampo
|
||
Datum: 21.08.2026
|
||
Grund: Modul-16-Paperclip implementiert + E2E verifiziert (Backtest/Optimization/Monitoring/Research/Strategy-Success, Safety 422, ProposalGate, Ausfall-toleranz, Idempotenz). Monitoring-FAIL-CLOSED-Fix: M15-Ausfall -> monitoring-Task FAILED statt SUCCEEDED. Ausfall-Toleranz-E2E (M12/M13/M15 down -> FAILED, healthy) gruen. Image neu gebaut (Fix persistent). FREIGEGEBEN. Modul-17 NICHT begonnen.
|
||
```
|