Files
nexus/docs/architecture-board-first-orchestration.md
T
AzuTear cd8c78d165
CI - Build & Test / Backend (.NET) (push) Successful in 45s
CI - Build & Test / Backend integration (PostgreSQL/Toxiproxy) (push) Failing after 1m0s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 2m49s
CI - Build & Test / Security Check (push) Successful in 7s
CI - Build & Test / Deploy Nexus (push) Has been skipped
feat(stability): unify readiness and recovery
2026-08-01 01:21:33 +02:00

21 KiB

Nexus Board-First Orchestration & Sichere OpenClaw-Integration

Gegenprüfung: Architekt, 2026-06-22 Gegenstand: Sicherster Pfad für Board-first-Agent-Orchestrierung und MCP-artige/strukturierte OpenClaw-Integration im Nexus-Backend Kein Frontend-direkter MCP-Pfad. Kein Deploy.

1. Executive Summary

1.1 Prüfergebnis

Der eingeschlagene Pfad ist architektonisch korrekt und sicher. Das Board-first-Modell mit Nexus-Backend als zentraler Brücke zwischen Benutzer, Board und OpenClaw-Gateway ist der richtige Ansatz. Das Backend fungiert bereits als sichere Schicht zwischen allen Akteuren.

1.2 Kernbewertung

Aspekt Status Bewertung
Board-first Architektur Umsetzung läuft Parent/Child-Modell korrekt implementiert
Kein Frontend-direkter Gateway-Zugriff Eingehalten Frontend spricht NUR mit Nexus-Backend
Backend als sichere Brücke Eingehalten API-Container proxyt alle Gateway-Calls
Auth-/Rechte-Modell Solide JWT + ApiKey + X-Agent-Id Enforcement
Gateway-Security ⚠️ Verbesserbar loopback-Bind muss auf lan für Docker
Migration-Reihenfolge 📋 Vorgeschlagen (siehe Abschnitt 8)

2. Ist-Architektur: Wer spricht mit wem?

┌──────────────────────────────────────────────────────────────────┐
│ Browser (Bao/Iris)                                                │
│  auth.ts → JWT Access Token (15m) + HttpOnly Refresh Cookie      │
│  api.ts → fetch /api/v1/* → Authorization: Bearer <JWT>          │
└───────────────────────────┬──────────────────────────────────────┘
                            │ HTTPS :443 (Traefik/npm)
                            ▼
┌──────────────────────────────────────────────────────────────────┐
│ Nexus web (nginx container)                                       │
│  location /api/ → proxy_pass http://api:8080                     │
│  location / → SPA (index.html)                                   │
│  CSP: connect-src 'self' — kein externer Gateway-Call möglich    │
└───────────────────────────┬──────────────────────────────────────┘
                            │ HTTP :8080 (internal network)
                            ▼
┌──────────────────────────────────────────────────────────────────┐
│ Nexus API (.NET 10 Container)          ← SICHERE BRÜCKE          │
│                                                                   │
│  Auth-Middleware (JWT → Controller)                              │
│  ApiKey-Middleware (X-Nexus-Api-Key → Role=Service)               │
│  SecurityHeaders-Middleware (HSTS, CSP, XFO)                     │
│  Rate Limiter (auth: 5/min/caller, agents: 30/min/caller)        │
│                                                                   │
│  IOpenClawGatewayClient                                           │
│   ├─ InvokeToolAsync("session_status", ...)                      │
│   ├─ InvokeToolAsync("sessions_list", ...)                       │
│   ├─ InvokeToolAsync("sessions_history", ...)                    │
│   ├─ InvokeToolAsync("memory_search", ...)                       │
│   ├─ InvokeToolAsync("sessions_send", ...)                       │
│   └─ InvokeToolAsync("cron", ...)                                 │
│                                                                    │
│  GatewayBridgeController (/api/bridge/*)  <- NEU (2026-06-22)    │
│   └─ ITaskBridgeService                                            │
│      create_task / create_child_task / update_status /           │
│      append_activity / handoff / get_board / get_agent_overview  │
│                                                                    │
│  IAgentRuntime (OpenClawRuntime)                                  │
│   └─ POST /v1/chat/completions (OpenAI-compat)                   │
│                                                                   │
│  ALLE Gateway-Calls → Authorization: Bearer <Gateway-Password>   │
└──────────────┬──────────────────────────────┬────────────────────┘
               │ host.docker.internal:18789    │
               │ (Gateway loopback/lan)        │
               ▼                               │
┌──────────────────────────────┐              │
│ OpenClaw Gateway Container    │              │
│ Port 18789                    │              │
│ Auth: password                │              │
│ Bind: loopback (127.0.0.1)    │              │
│                               │              │
│ HTTP Deny-List (default):     │              │
│  exec, spawn, shell,          │              │
│  fs_write, fs_delete,         │              │
│  fs_move, apply_patch,        │              │
│  sessions_spawn, sessions_send│              │
│  cron, gateway, nodes,        │              │
│  whatsapp_login               │              │
└───────────────────────────────┴──────────────┘

2.1 Wichtig: Das Frontend sieht den Gateway NICHT

Browser ─── [Nexus API] ─── Gateway
               │
               +─ Gateway-Passwort lebt NUR im Backend
               +─ CSP: connect-src 'self' blockiert jeden Direktzugriff
               +─ Gateway HTTP Deny-List blockiert alle RCE-Tools

Das ist die korrekte Architektur. Kein Frontend-komponenten-direkter MCP-Pfad existiert und keiner sollte eingeführt werden.

3. Board-First Orchestrierung: Ist-Stand & Bewertung

3.1 Das Parent/Child-Task-Modell (Phase 3)

Parent-Task (Owner: Iris)
├── Child-Task A (AssignedTo: programmer)
├── Child-Task B (AssignedTo: reviewer)
└── Child-Task C (AssignedTo: architekt)

Status: Implementiert (2026-06-21)

Datenmodell (WorkTask):

  • ParentTaskId (Guid?) — verknüpft Child mit Parent
  • IsAgentTask (bool) — markiert programmatisch erstellte Agent-Tasks
  • ExpectedFrom (string?) — wer als nächstes antworten soll
  • AssignedTo (string?) — operativer Owner der Task

State Machine:

Parent: Backlog → InProgress → Review → Done
                            ↘ Blocked → Backlog

Child:  Backlog → InProgress → Done
                        ↘ Blocked → Backlog

Rules (aus TaskStateHelper.CanChangeState):

  • Nur Iris & Bao dürfen State-Änderungen vornehmen
  • Sub-Agenten (programmer, reviewer, architekt, researcher, executor) niemals
  • nexus-system als technischer Fallback für Cron/Reset-Stale

3.2 Bewertung: Architektonisch korrekt

Kriterium Bewertung Begründung
Board als Single Source of Truth Task Board = sichtbare Aufgabenwahrheit
Delegation sichtbar Child-Tasks statt unsichtbarem Delegations-Status
Feingliedrige Berechtigung State-Change nur durch Iris/Bao
Agenten arbeiten gegen Child-Tasks Klare Ownership durch AssignedTo
Parent bleibt bei Iris Parent in InProgress während Koordination
Review-Gate für Bao Parent erst in Review, dann Bao-Entscheidung

3.3 Offene Punkte im Board-Modell

  1. Keine automatische Child-Task-Erstellung — Das Board-Modell setzt voraus, dass Iris manuell Child-Tasks anlegt. Ein strukturierter Workflow für automatische Child-Task-Erstellung bei spawn/Subagent-Aufrufen fehlt.

  2. Keine Task→Session-Verknüpfung — Es gibt keine direkte Verknüpfung zwischen einer Child-Task und der OpenClaw-Subagent-Session, die sie bearbeitet. Der AgentService kennt Sessions, TaskService kennt Tasks — aber sie sind nicht verknüpft.

  3. Reset-Stale ist ungeschütztPOST /api/v1/tasks/reset-stale hat [AllowAnonymous] und kann von jedem aufgerufen werden (siehe Risiko #1).

4. Auth- und Rechte-Modell

4.1 Authentifizierungsebenen

Ebene 1: Browser-JWT  (Access Token 15m, Refresh Token HttpOnly Cookie)
Ebene 2: X-Nexus-Api-Key (Service-zu-Service, Role=Service)
Ebene 3: Gateway-Password (Backend → Gateway, Bearer Authorization)
Ebene 4: X-Agent-Id Header (Agent-Identität für Task-State-Enforcement)

4.2 Berechtigungsmatrix

Aktion Bao Iris Sub-Agent nexus-system Service (ApiKey)
Task State ändern (intern)
Task Inhalt editieren
Agent-Config lesen
Gateway-Tool aufrufen (intern)
Dashboard-Metriken sehen

4.3 Bewertung

Positiv:

  • JWT-Sicherheit entspricht Best Practices (PBKDF2-SHA256, 210k Iterationen, Rotating Refresh Tokens)
  • Refresh-Token-Reuse-Detection verhindert Token-Theft
  • Rate-Limiting auf Login und Refresh
  • Cookie-backed Refresh/Logout über Secure, HttpOnly, SameSite=Strict plus Origin-/Sec-Fetch-Site-Prüfung; die unvalidierte Legacy-CSRF-Route wurde in v0.2.60 entfernt
  • Security Headers (HSTS, CSP, XFO, Referrer-Policy)

Kritisch:

  • [AllowAnonymous] auf /tasks/board und /tasks/reset-stale — siehe Risiko-Analyse
  • Kein Scoped-ApiKey — der X-Nexus-Api-Key gibt volle Service-Rechte
  • Keine Audit-Protokollierung für ApiKey-Nutzung

5. Gateway-Integration: Sicherheitsanalyse

5.1 Tool-Invoke-Pfad (Ist-Stand)

POST /api/v1/operations/snapshot
  → DashboardService → OpenClawGatewayClient.InvokeToolAsync()
    → POST http://host.docker.internal:18789/tools/invoke
       Authorization: Bearer <Gateway-Password>

Aufgerufene Tools (durch Nexus-Backend):

  • session_status — Agent-Status abfragen (read-only)
  • sessions_list — Session-Liste (read-only)
  • sessions_history — Chat-Verlauf (read-only)
  • memory_search — Memory-Suche (read-only)
  • sessions_send — Chat-Nachricht senden (write, aber kontrolliert)
  • cron — Cron-Jobs verwalten (write)

Gateway HTTP Deny-List blockiert:

  • Alle Exec-Tools (exec, spawn, shell)
  • Alle Filesystem-Tools (fs_write, fs_delete, fs_move, apply_patch)
  • Gateway-Control-Plane (gateway)
  • Node-Relay (nodes)
  • Session-Orchestrierung (sessions_spawn, sessions_send)

5.2 Docker-Netzwerk & Gateway-Bind

Aktuelles Problem:

compose.yaml:
  api:
    extra_hosts:
      - host.docker.internal:host-gateway
    networks:
      - nexus
      - openclaw_default  ← API-Container ist im Gateway-Netzwerk

Gateway-Konfiguration:
  gateway.bind: "loopback"  ← Bindet nur 127.0.0.1 IM GATEWAY-CONTAINER

Ergebnis:

  • host.docker.internal:18789 funktioniert, weil extra_hosts auf den Docker-Host zeigt
  • ABER: Docker-Port-Forward (wenn vorhanden) sendet an Container-IP, nicht loopback
  • Die openclaw_default Netzwerk-Mitgliedschaft des API-Containers wird NICHT genutzt

Empfehlung (siehe gateway-api-research.md, Abschnitt 6):

// openclaw.json
{
  gateway: {
    bind: "lan"  // war "loopback"
  }
}

Alternativ: API-Container über Gateway-Container-Namen ansprechen:

Integrations__OpenClaw__BaseUrl: http://openclaw_gateway:18789

(Vorausgesetzt der Gateway-Container heißt openclaw_gateway und ist im openclaw_default Netzwerk)

5.3 MCP-artige Integration: Bewertung

Das Gateway /tools/invoke ist bereits MCP-artig:

  • JSON-RPC-ähnliche Aufrufe mit tool + args + sessionKey
  • Strukturierte Responses mit { ok, result, error }
  • Tool-Discovery via Policy (Deny/Allow-List)
  • Request/Response mit eindeutiger Fehlersemantik

Was fehlt für ein vollständiges MCP-Interface:

  • Keine Tool-Listing/Discovery über API (kein tools/list)
  • Keine Schema-Validierung für Tool-Arguments
  • Keine Structured Outputs (function-calling-ähnliches Format)

Empfehlung: NICHT ein MCP-Protokoll zwischen Nexus und Gateway einführen. Stattdessen den bestehenden /tools/invoke-Pfad weiter nutzen und strukturieren.

5.4 Neue Backend-Bridge (Implementiert 2026-06-22)

Der Nexus-eigene strukturierte Kommando-Adapter wurde eingeführt:

Nexus Backend
  ├─ GatewayToolClient (bestehender OpenClawGatewayClient)
  │   └─ POST /tools/invoke (Gateway)
  │
  ├─ GatewayBridgeController (NEU — /api/bridge/)
  │   ├─ POST /api/bridge/tasks                    (create_task)
  │   ├─ POST /api/bridge/tasks/{id}/children      (create_child_task)
  │   ├─ PATCH /api/bridge/tasks/{id}/status       (update_status)
  │   ├─ POST /api/bridge/tasks/{id}/activity      (append_activity)
  │   ├─ POST /api/bridge/tasks/{id}/handoff       (handoff)
  │   ├─ GET  /api/bridge/board                    (get_board)
  │   ├─ GET  /api/bridge/tasks/{id}               (get_task)
  │   ├─ GET  /api/bridge/tasks/{id}/children      (get_children)
  │   ├─ GET  /api/bridge/tasks/{id}/activity      (get_activity)
  │   └─ GET  /api/bridge/agent-overview           (get_agent_overview)
  │
  ├─ ITaskBridgeService / TaskBridgeService (NEU)
  │   └─ Typisierte TaskBridgeResult<T> mit Outcome: Success/NotFound/InvalidState/Unauthorized/ValidationError
  │
  ├─ BoardOrchestrationService (offen)
  │   ├─ Tasks erstellen/aktualisieren
  │   ├─ Session-Status abfragen
  │   └─ Agent-Progress berechnen
  │
  └─ AgentDelegationService (offen)
      ├─ Subagent-Task anlegen
      ├─ Session verfolgen
      └─ Ergebnis integrieren

Auth-Modell für /api/bridge:

  • Primär: X-Agent-Id Header (Agent-Identität vom Gateway)
  • Fallback: JWT (Browser-authenticated user → bao)
  • Fallback: X-Nexus-Api-Key (Backend-zu-Backend Service-Identität)
  • Rate-Limiting: 30 Requests/Minute (agents-Policy)

Das Frontend sieht /api/bridge NICHT. Der Pfad ist ausschließlich für Agent-zu-Backend-Kommunikation.

6. Risikoanalyse

6.1 KRITISCH — [AllowAnonymous] auf Board-Endpunkten BEHOBEN (2026-06-22)

Betroffen (vorher):

// TasksController.cs
[AllowAnonymous]
[HttpGet("board")]          // Gab ALLE Tasks zurück — inkl. Detail-Texte

[AllowAnonymous]
[HttpPost("reset-stale")]   // Konnte Tasks zurücksetzen — datenändernd

Fix angewandt:

  • [AllowAnonymous] entfernt
  • Inline-Auth-Check: X-Agent-Id Header, ApiKey-Rolle, oder JWT erforderlich
  • reset-stale erfordert zusätzlich X-Agent-Id: iris Identität (nur Iris darf)
  • Neuer /api/bridge/ Pfad als sauberer Agent-zu-Backend-Adapter
  • Rate-Limiting (agents-Policy: 30/min) auf Bridge-Endpunkte

6.2 MITTEL — Keine Task→Session-Verknüpfung

Wenn ein Subagent eine Child-Task bearbeitet, gibt es keine technische Verknüpfung zwischen der Child-Task und der OpenClaw-Session. Das bedeutet:

  • Keine automatische Status-Aktualisierung bei Session-Abschluss
  • Keine Sitzungs-Historie direkt von der Task aus erreichbar
  • Iris muss manuell prüfen, ob ein Agent fertig ist

Empfehlung:

  • WorkTask um SessionKey (string?) erweitern
  • Bei Child-Task-Erstellung Session-Key speichern
  • Status-Polling: Wenn Session inaktiv, Child-Task auf Done/Blocked prüfen

6.3 MITTEL — Gateway-Passwort in Config-Dateien

Das Gateway-Passwort ieDmOjBiVfbbDM0ibrEebPAg ist:

  • In .env auf dem Host (OK)
  • In gateway-api-research.md (maskiert: ieDm...PAg)
  • Im openclaw.json auf dem Host (OK)
  • In der API-Container-Umgebungsvariable (notwendig)

Empfehlung:

  • Gateway-Rate-Limiting aktiv halten (10 attempts/60s → 5min lockout)
  • Gateway-Bind auf lan ändern (nicht öffentlich exponiert)
  • gateway-api-research.md aus dem öffentlichen Repo entfernen oder Passwort entfernen

6.4 NIEDRIG — Keine Scoped API-Keys

Der X-Nexus-Api-Key gibt volle Service-Rechte. Es gibt keine Möglichkeit, verschiedene API-Keys mit unterschiedlichen Rechten zu vergeben.

Empfehlung (spätere Phase):

  • API-Key-Scopes einführen (read, write, admin)
  • Rate-Limiting pro API-Key
  • Audit-Log für ApiKey-Nutzung

6.5 NIEDRIG — Kein Structured Output vom Gateway

Die /tools/invoke-Responses sind JSON, aber ohne Schema-Garantie. Die Backend-Logik extrahiert Felder mit vielen Fallbacks (??-Kettenantworten).

Empfehlung:

  • Response-Typen für jedes Tool definieren (DTOs)
  • Deserialisierung mit Schema-Validierung
  • Fallback-Logik zentralisieren

7. Migrationsreihenfolge (Vorschlag)

Phase 3b — Sichere Board-Grundlage (JETZT)

1. [AllowAnonymous] auf /tasks/board und /tasks/reset-stale fixen
   → ApiKey-Auth plus X-Agent-Id-Validierung
   → README/Iris-Doku aktualisieren

2. Gateway-Bind von loopback auf lan ändern
   → API-Container über openclaw_default Netzwerk ansprechen
   → host.docker.internal-Fallback entfernen

Phase 4 — Strukturierte Orchestrierung

3. Task→Session-Verknüpfung einführen
   → WorkTask.SessionKey (string?)
   → Bei Child-Task-Erstellung Session speichern

4. AgentDelegationService
   → Zentralisierte Subagent-Task-Erstellung
   → Session-Status-Monitoring
   → Automatische Status-Propagation (Session done → Task done)

5. BoardOrchestrationService
   → Refresh-Intervall für Agent-Progress
   → Stale-Erkennung mit Session-Status
   → Priorisierung nach Workload

Phase 5 — Erweiterte Integration

6. Structured Tool Responses
   → DTOs für jedes Gateway-Tool
   → Schema-Validierung
   → Caching für häufige Abfragen

7. API-Key-Scopes
   → read/write/admin Scopes
   → Audit-Log für ApiKey-Nutzung

8. Automatische Child-Task-Erstellung
   → Bei spawn/subagent-Aufrufen automatisch Child-Task anlegen
   → Session-Key verknüpfen
   → Activity-Feed erweitern

8. Sichere Brücke: Architekturprinzipien

8.1 Das Backend ist die einzige Brücke

Browser ←→ Nexus API ←→ OpenClaw Gateway
         ↑              ↑
    JWT Auth       Gateway Password
    (pro User)     (nur im Backend)

Niemals:

  • Gateway-Passwort im Frontend
  • Direkter Browser→Gateway API-Call
  • MCP-Protokoll zwischen Frontend und Gateway
  • Agent-Sessions direkt aus dem Frontend steuern

8.2 Prinzipien für jede neue Integration

  1. Neue Endpunkte immer im Nexus-Backend
  2. Auth über bestehendes JWT/ApiKey-System
  3. Gateway-Calls immer serverseitig mit Gateway-Passwort
  4. Kein Gateway-Tool direkt aus dem Frontend aufrufen
  5. State-Änderungen nur durch Iris/Bao (via Backend-Enforcement)
  6. Activity/Audit für jede State-Änderung

8.3 Strukturierte OpenClaw-Integration (MCP-artig)

Der Gateway /tools/invoke-Endpunkt ist bereits strukturell MCP-artig. Eine formale MCP-Implementierung zwischen Nexus und Gateway ist nicht notwendig. Stattdessen:

Nexus.Backend.Services
├── IOpenClawGatewayClient  (Gateway-Tool-Abstraktion)
│   └── InvokeToolAsync(tool, args) → JsonNode?
│
├── IBoardOrchestrationService (NEU)
│   ├── CreateDelegateTask(agentId, title, detail) → WorkTask
│   ├── WatchAgentSession(agentId) → SessionWatcher
│   └── SyncAgentProgress() → Progress[]
│
└── IAgentDelegationService (NEU)
    ├── DelegateToAgent(parentTaskId, agentId, instruction)
    ├── CollectResult(subTaskId) → AgentResult
    └── HandleBlocker(subTaskId, reason) → void

9. Zusammenfassung

Was gut ist (nicht ändern):

  • Board-first-Ansatz mit Parent/Child-Tasks
  • Backend als einzige Gateway-Brücke
  • JWT + Gateway-Password-Trennung
  • State-Change-Restriktion auf Iris/Bao
  • HTTP Deny-List des Gateways
  • CSP im Frontend (kein externer Connect)

Was verbessert werden muss:

  • [AllowAnonymous] auf Board-Endpunkten → ApiKey-Auth
  • Gateway-Bind loopback → lan (oder Netzwerk-Routing korrigieren)
  • Task→Session-Verknüpfung fehlt

Was später kommen kann:

  • Automatische Child-Task-Erstellung bei Subagent-Aufrufen
  • Structured Gateway Responses mit Schema-Validierung
  • API-Key-Scopes und Audit
  • Session-gesteuertes Progress-Tracking

Was niemals kommen darf:

  • Gateway-Passwort im Frontend
  • Direkter MCP-Pfad Browser→Gateway
  • Agent-Session-Steuerung aus dem Frontend
  • Task-State-Änderung durch Sub-Agenten