KI-Konzil/_bmad-output/planning-artifacts/architecture.md
copilot-swe-agent[bot] e37cb6f4c0 Add BMAD planning artifacts: PRD, Architecture, UX Design (pre-skill attribution)
Co-authored-by: Kenearos <86194771+Kenearos@users.noreply.github.com>
2026-03-12 14:14:41 +00:00

424 lines
19 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.

---
stepsCompleted:
- step-01-init
- step-02-context
- step-03-starter
- step-04-decisions
- step-05-patterns
- step-06-structure
- step-07-validation
- step-08-complete
inputDocuments:
- _bmad-output/planning-artifacts/prd.md
- CLAUDE.md
- README.md
---
# Architektur-Entscheidungsdokument — CouncilOS
**Autor:** KI-Konzil Dev-Team
**Datum:** 2026-03-12
**Version:** 1.0.0
**Bezug:** PRD v1.0.0
---
## 1. Architektur-Überblick
CouncilOS ist eine Full-Stack-Webanwendung bestehend aus einem **FastAPI-Backend** (Python), einem **Next.js-Frontend** (TypeScript) und einer **PostgreSQL-Datenbank**. Die Kernintelligenz liegt im **LangGraph-Engine**, der zyklische Multi-Agenten-Graphen ausführt.
```
┌─────────────────────────────────────────────────────────────────┐
│ Browser (Next.js) │
│ ┌─────────────────────┐ ┌──────────────────────────────┐ │
│ │ Rat-Architekt Tab │ │ Konferenzzimmer Tab │ │
│ │ (React Flow Canvas)│ │ (Execution View) │ │
│ └─────────────────────┘ └──────────────────────────────┘ │
└─────────────┬───────────────────────────┬───────────────────────┘
│ REST (HTTP/JSON) │ WebSocket
▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ FastAPI Backend │
│ ┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ REST Routes │ │ WebSocket Route │ │ Background Tasks│ │
│ └──────────────┘ └──────────────────┘ └──────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ LangGraph Engine │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ Master Agent│ │ Critic Agent│ │ Writer Agent │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │ │
│ │ └────────────────┼──────────────────┘ │ │
│ │ CouncilState (TypedDict) │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────┬──────────────────────────────────────────┘
┌─────────────┼─────────────┐
▼ ▼ ▼
PostgreSQL ChromaDB LLM APIs
(Blueprints) (PDF-Vektor) (Anthropic/OpenAI)
```
---
## 2. Architektur-Entscheidungen (ADRs)
### ADR-001: LangGraph als KI-Orchestrierungs-Framework
**Kontext:** Wir benötigen ein Framework, das echte Zyklen (Loops) in KI-Agenten-Pipelines unterstützt und Human-in-the-Loop ermöglicht.
**Entscheidung:** LangGraph (Python) wird als einziges KI-Orchestrierungs-Framework verwendet.
**Begründung:**
- LangGraph unterstützt native Zyklen (A → B → A) ohne Workarounds.
- `interrupt_before` für Human-in-the-Loop ist eingebaut.
- `CouncilState` als TypedDict mit `operator.add`-Reducern für sichere State-Akkumulation.
- Andere Frameworks (LangChain, AutoGen) unterstützen keine echten Zyklen.
**Konsequenzen:** LangGraph-Versionen ≥ 0.2.x sind Voraussetzung. Synchrone `graph.invoke()`-Aufrufe werden in Thread-Pools ausgeführt, um den FastAPI-Event-Loop nicht zu blockieren.
---
### ADR-002: FastAPI mit asyncio + Background Tasks
**Kontext:** LangGraph-Runs können mehrere Minuten dauern und müssen nicht-blockierend ausgeführt werden.
**Entscheidung:** FastAPI mit `BackgroundTasks` + `asyncio.get_event_loop().run_in_executor()` für alle LangGraph-Aufrufe.
**Begründung:**
- Background Tasks ermöglichen sofortige HTTP-Antwort (202 Accepted) mit `run_id`.
- `run_in_executor()` verhindert Blockierung des Event Loops durch synchrones LangGraph.
- Clients können per WebSocket live-Updates empfangen oder per GET pollen.
**Konsequenzen:** Run-Status wird in einem In-Memory-Store (`run_store`) zwischen Tasks geteilt. Bei Neustart des Servers gehen laufende Runs verloren — akzeptabel für MVP.
---
### ADR-003: WebSocket für Echtzeit-Agent-Events
**Kontext:** Das Frontend muss in Echtzeit wissen, welcher Agent gerade arbeitet, damit der Node im Canvas pulsiert.
**Entscheidung:** Dedizierter WebSocket-Endpoint `/ws/council/{run_id}` pro Council-Run.
**Begründung:**
- Polling ist nicht akzeptabel (zu hohe Latenz, unnötige Serverlast).
- WebSockets ermöglichen Push-Events innerhalb von 500 ms.
- Jede `run_id` hat eine eigene isolierte WebSocket-Session — kein Event-Crossover.
**Konsequenzen:** WebSocket-Sessions werden in einem `connection_manager` verwaltet. Verbindungsabbrüche müssen graceful behandelt werden.
---
### ADR-004: Dynamischer Graph-Builder (Phase 3)
**Kontext:** Ab Phase 3 muss der LangGraph-Graph aus einem JSON-Blueprint dynamisch aufgebaut werden.
**Entscheidung:** `dynamic_graph_builder.py` interpretiert das Blueprint-JSON und konstruiert den `StateGraph` zur Laufzeit.
**Begründung:**
- Hartcodierte Graphen in Produktion verstoßen gegen die Kern-Design-Constraint.
- Das Blueprint-JSON ist das kanonische Austauschformat zwischen Frontend und Backend.
- Zyklen müssen im JSON darstellbar sein und dürfen nicht zu DAGs vereinfacht werden.
**Konsequenzen:** Blueprint-JSON muss versioniert sein (`version`-Feld). Ungültige Blueprints müssen mit klaren Fehlermeldungen abgelehnt werden.
---
### ADR-005: PostgreSQL + JSONB für Blueprints
**Kontext:** Blueprint-Konfigurationen sind komplexe, verschachtelte Objekte, die sich über die Zeit ändern.
**Entscheidung:** PostgreSQL 16 mit JSONB-Spalte für Blueprint-Daten.
**Begründung:**
- JSONB ermöglicht flexible Schema-Entwicklung ohne Migrationszwang bei jedem neuen Feld.
- PostgreSQL ist für alle anderen relationalen Daten (Council-Run-Verlauf) bereits vorhanden.
- Ein `version`-Feld im JSONB ermöglicht Schema-Versionierung.
**Konsequenzen:** Alembic verwaltet Datenbankmigrationen. SQLAlchemy (async) wird als ORM verwendet.
---
### ADR-006: ChromaDB für PDF-Vektor-Suche
**Kontext:** Agents müssen auf Inhalte aus hochgeladenen PDFs zugreifen können.
**Entscheidung:** ChromaDB (lokal, persistent) als Vektor-Datenbank.
**Begründung:**
- Kostenlos, lokal betreibbar, keine externe API.
- `PersistentClient` überlebt Server-Neustarts.
- Cosine-Similarity für semantische Suche eingebaut.
**Konsequenzen:** `CHROMA_PERSIST_DIR` Umgebungsvariable muss gesetzt sein. In Docker Compose wird ein Named Volume verwendet.
---
## 3. Datenmodell
### 3.1 CouncilState (TypedDict)
```python
class CouncilState(TypedDict):
input_topic: str # Ursprungsthema / PDF-Inhalt des Nutzers
current_draft: str # Das aktuell bearbeitete Dokument
feedback_history: Annotated[List[str], operator.add] # Akkumuliertes Kritiker-Feedback (append-only)
route_decision: str # Routing-Signal: "rework" | "approve" | benutzerdefiniert
messages: Annotated[list, operator.add] # LLM-Nachrichtenverlauf (akkumulierend)
iteration_count: int # Anzahl Rework-Schleifen
critic_score: Optional[float] # Numerische Bewertung (010)
run_id: str # Eindeutige Run-ID (für WebSocket-Events)
active_node: str # Name des aktuell aktiven Agent-Nodes
```
**Wichtig:** `feedback_history` und `messages` verwenden `operator.add` als Reducer — sie werden niemals überschrieben, nur angehängt.
### 3.2 Blueprint-JSON-Schema
```json
{
"version": "1.0",
"name": "Mein Content-Rat",
"nodes": [
{
"id": "node-1",
"type": "agent",
"name": "Master KI",
"system_prompt": "Du bist ein erstklassiger Content-Writer...",
"model": "claude-3-5-sonnet-20241022",
"tools": {
"web_search": false,
"pdf_reader": false
}
}
],
"edges": [
{
"id": "edge-1",
"source": "node-1",
"target": "node-2",
"type": "linear"
},
{
"id": "edge-2",
"source": "node-2",
"target": "node-1",
"type": "conditional",
"condition": "rework"
}
]
}
```
### 3.3 Datenbankschema (PostgreSQL)
**Tabelle `blueprints`:**
| Spalte | Typ | Beschreibung |
|--------|-----|--------------|
| `id` | UUID | Primärschlüssel |
| `name` | VARCHAR(255) | Name des Councils |
| `data` | JSONB | Blueprint-JSON |
| `created_at` | TIMESTAMP | Erstellungszeitpunkt |
| `updated_at` | TIMESTAMP | Letzter Update |
**Tabelle `council_runs`:**
| Spalte | Typ | Beschreibung |
|--------|-----|--------------|
| `id` | UUID | Primärschlüssel |
| `blueprint_id` | UUID | FK → `blueprints.id` (nullable für Phase-1-Runs) |
| `input_topic` | TEXT | Eingabe-Prompt |
| `status` | VARCHAR(50) | `pending`\|`running`\|`completed`\|`failed`\|`paused` |
| `final_draft` | TEXT | Finaler Output |
| `critic_score` | FLOAT | Letzte Critic-Bewertung |
| `iteration_count` | INT | Anzahl Rework-Schleifen |
| `created_at` | TIMESTAMP | Startzeitpunkt |
| `completed_at` | TIMESTAMP | Endzeitpunkt |
---
## 4. Komponentenstruktur
### 4.1 Backend-Verzeichnisstruktur
```
backend/
├── main.py # FastAPI-Entrypoint
├── state.py # CouncilState TypedDict + Konstanten
├── database.py # Async SQLAlchemy setup + Session
├── alembic.ini # Alembic-Konfiguration
├── alembic/ # Datenbankmigrationen
│ └── versions/
├── api/
│ ├── routes.py # Run-Endpunkte + Health-Check
│ ├── blueprint_routes.py # Blueprint-CRUD-Endpunkte
│ ├── run_history_routes.py # Run-Verlauf-Endpunkte
│ ├── run_store.py # In-Memory-Run-Status-Store
│ └── websocket.py # WebSocket-Connection-Manager + Route
├── agents/
│ ├── master_agent.py # Master-Agent-Node
│ ├── critic_agent.py # Critic-Agent-Node + Routing
│ └── writer_agent.py # Writer-Agent-Node
├── services/
│ ├── graph_builder.py # Phase-1-Hartcodierter-Graph
│ ├── dynamic_graph_builder.py # Phase-3-Dynamischer-Graph aus Blueprint
│ ├── blueprint_service.py # Blueprint-CRUD-Service
│ └── run_service.py # Run-History-Service
├── models/
│ ├── blueprint.py # SQLAlchemy Blueprint-Modell
│ └── council_run.py # SQLAlchemy CouncilRun-Modell
└── tools/
├── web_search.py # Tavily-Web-Suche-Wrapper
└── pdf_reader.py # PyPDF + ChromaDB-Wrapper
```
### 4.2 Frontend-Verzeichnisstruktur
```
frontend/
├── app/
│ ├── layout.tsx # Root-Layout + Navigation-Tabs
│ ├── page.tsx # Redirect → /rat-architekt
│ ├── rat-architekt/ # Tab A: Visueller Canvas
│ │ └── page.tsx
│ ├── konferenzzimmer/ # Tab B: Ausführung
│ │ └── page.tsx
│ ├── components/
│ │ ├── ArchitectCanvas.tsx # React Flow Canvas
│ │ ├── NavTabs.tsx # Tab-Navigation
│ │ ├── nodes/ # Custom React Flow Nodes
│ │ ├── edges/ # Custom React Flow Edges
│ │ └── panels/ # Sidebar + Settings-Panels
│ ├── hooks/ # Custom React Hooks (WebSocket, API)
│ ├── store/ # Zustand State Management
│ ├── types/ # TypeScript-Typen
│ └── utils/
│ ├── blueprint-parser.ts # React Flow → Blueprint JSON
│ └── api-client.ts # HTTP-Client für Backend-API
└── __tests__/ # Vitest-Tests
```
---
## 5. API-Vertragsübersicht
### 5.1 REST-Endpunkte
| Methode | Pfad | Beschreibung |
|---------|------|--------------|
| `POST` | `/api/councils/` | Blueprint erstellen |
| `GET` | `/api/councils/` | Alle Blueprints auflisten |
| `GET` | `/api/councils/{id}` | Einzelnen Blueprint abrufen |
| `PUT` | `/api/councils/{id}` | Blueprint aktualisieren |
| `DELETE` | `/api/councils/{id}` | Blueprint löschen |
| `POST` | `/api/councils/run` | Phase-1-Run starten |
| `POST` | `/api/councils/{id}/run` | Blueprint-Run starten |
| `GET` | `/api/councils/run/{run_id}` | Run-Status/Ergebnis abrufen |
| `POST` | `/api/councils/run/{run_id}/approve` | God Mode: genehmigen/ablehnen/modifizieren |
| `GET` | `/api/councils/run/{run_id}/state` | God Mode: pausierten State abrufen |
| `POST` | `/api/councils/upload-pdf` | PDF hochladen und in ChromaDB einlesen |
| `GET` | `/api/runs/` | Run-Verlauf auflisten |
| `GET` | `/api/runs/{run_id}` | Run-Verlauf-Detail |
| `GET` | `/api/health` | Health-Check |
### 5.2 WebSocket
| Endpoint | Event-Format |
|----------|--------------|
| `WS /ws/council/{run_id}` | `{"node": "master_agent", "status": "running"}` |
**Status-Werte:** `running` \| `completed` \| `done`
---
## 6. Sequenzdiagramm: Council-Run
```
Nutzer Frontend FastAPI LangGraph LLM API
│ │ │ │ │
├─POST /run───►│ │ │ │
│ ├─POST /run────►│ │ │
│ │ ├─202 (run_id)──►│ │
│ │ ├─Background:─────────────────► │
│ │ │ graph.invoke() │ │
│ ├─WS connect───►│ │ │
│ │ │ │ │
│ │ │ [Node: master_agent running] │
│ │ ├─WS event───────────────────────►
│ │◄─WS: running──┤ │ │
│ │ │ ├─LLM call──────►
│ │ │ │◄──response────┤
│ │ │ [Node: critic_agent running] │
│ │◄─WS: running──┤ │ │
│ │ │ ├─LLM call──────►
│ │ │ │◄──response────┤
│ │ │ [score < 8 → rework] │
│ │ │ [Node: master_agent running] │
│ │◄─WS: running──┤ │ │
│ │ │ ... │
│ │ │ [score ≥ 8 → approve] │
│ │ │ [Node: writer_agent running] │
│ │◄─WS: running──┤ │ │
│ │ │ ├─LLM call──────►
│ │ │ │◄──response────┤
│ │◄─WS: done─────┤ │ │
├─GET /result─►│ │ │ │
│ ├─GET result───►│ │ │
│ │◄──final draft─┤ │ │
```
---
## 7. God Mode — Sequenzdiagramm
```
Nutzer Frontend FastAPI LangGraph
│ │ │ │
├─POST /run (god_mode=true)────► │
│ │ ├─interrupt_before──────────────►
│ │ │◄──paused state─┤
│ │◄─WS: paused───┤ │
│ │ │ │
├─Approve/Reject/Modify────────► │
│ │ ├─resume──────────────────────►
│ │ │◄──next state───┤
│ │ │ │
│ │ (repeat per node if god_mode=true) │
```
---
## 8. Sicherheitsüberlegungen
1. **API-Key-Management:** Alle Secrets ausschließlich in `.env`-Dateien (gitignored).
2. **CORS:** In Produktion auf bekannte Origins beschränken.
3. **PDF-Upload-Validierung:** Nur `.pdf`-Dateien akzeptieren, Größenlimit durchsetzen.
4. **Input-Validierung:** Pydantic-Modelle für alle API-Inputs mit `min_length`/`max_length`.
5. **SQL-Injection:** SQLAlchemy ORM verhindert Raw-SQL-Injection.
6. **Infinite-Loop-Schutz:** `MAX_ITERATIONS = 5` — nach 5 Rework-Schleifen automatische Genehmigung.
---
## 9. Deployment-Architektur (Lokal)
```yaml
# docker-compose.yml (vereinfacht)
services:
db: # PostgreSQL 16
api: # FastAPI + LangGraph (Port 8000)
frontend: # Next.js (Port 3000)
volumes:
postgres_data:
chroma_data:
```
**Umgebungsvariablen (.env):**
```
ANTHROPIC_API_KEY=
OPENAI_API_KEY=
TAVILY_API_KEY=
DATABASE_URL=postgresql+asyncpg://user:password@db:5432/councilOS
CHROMA_PERSIST_DIR=./chroma_db
```