Changes before error encountered

Co-authored-by: Kenearos <86194771+Kenearos@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-12 15:00:09 +00:00
parent 3be3cb73b6
commit d4cfb34423
17 changed files with 1128 additions and 0 deletions

View file

@ -0,0 +1,64 @@
# Story 1.1: Docker-Compose-Umgebung aufsetzen
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Entwickler**,
möchte ich **eine vollständige lokale Docker-Compose-Umgebung**,
so dass ich **ohne lokale Python/Node-Installation entwickeln kann**.
## Acceptance Criteria
1. `docker compose up -d` startet drei Services: `db` (PostgreSQL 16), `api` (FastAPI Port 8000), `frontend` (Next.js Port 3000)
2. `GET /api/health` gibt `{"status": "ok"}` zurück
3. Frontend ist unter `http://localhost:3000` erreichbar
4. `pg_isready` im db-Container antwortet mit `accepting connections`
5. Named Volumes: `postgres_data` und `chroma_data` sind definiert
## Tasks / Subtasks
- [x] Task 1: `docker-compose.yml` mit drei Services erstellen (AC: 14)
- [x] Subtask 1.1: `db`-Service mit PostgreSQL 16-alpine, Healthcheck
- [x] Subtask 1.2: `api`-Service mit `depends_on db`, `env_file: .env`, `chroma_data`-Volume
- [x] Subtask 1.3: `frontend`-Service mit `NEXT_PUBLIC_API_URL`-Env
- [x] Task 2: `.env.example` mit allen Pflicht-Keys erstellen (AC: 5)
- [x] Task 3: `.gitignore` um `.env` erweitern
- [x] Task 4: `backend/Dockerfile` und `frontend/Dockerfile` erstellen
## Dev Notes
- `chroma_data` Volume wird auf `/app/chroma_db` im api-Container gemappt
- `postgres_data` Volume für DB-Persistenz über Neustarts hinweg
- `api` nutzt `service_healthy` Bedingung für db-Abhängigkeit
### Project Structure Notes
- `docker-compose.yml` im Projekt-Root
- `backend/Dockerfile` und `frontend/Dockerfile`
### References
- [Source: CLAUDE.md#Environment Variables]
- [Source: _bmad-output/planning-artifacts/architecture.md#Deployment-Architektur]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- PostgreSQL Healthcheck verhindert Race Condition beim API-Start.
- `chroma_data` Named Volume sichert ChromaDB-Persistenz zwischen Container-Neustarts.
### File List
- `docker-compose.yml`
- `.env.example`
- `backend/Dockerfile`
- `frontend/Dockerfile`

View file

@ -0,0 +1,62 @@
# Story 1.2: Backend-Python-Umgebung & Requirements
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Backend-Entwickler**,
möchte ich **eine vollständige `requirements.txt` mit allen Abhängigkeiten**,
so dass ich **reproduzierbar installieren kann**.
## Acceptance Criteria
1. `pip install -r requirements.txt` installiert FastAPI, LangGraph, langchain-anthropic, langchain-openai, SQLAlchemy, alembic, chromadb, tavily-python, pypdf, pytest
2. `pytest backend/tests/` läuft durch (0 oder mehr passed, 0 failed)
3. `asyncio_mode = auto` ist in `pytest.ini` konfiguriert für async Tests
4. Alle Pakete haben gepinnte Versionen für Reproduzierbarkeit
## Tasks / Subtasks
- [x] Task 1: `requirements.txt` mit allen Abhängigkeiten erstellen (AC: 1, 4)
- [x] Subtask 1.1: Core: fastapi, uvicorn, pydantic
- [x] Subtask 1.2: AI: langgraph, langchain-anthropic, langchain-openai
- [x] Subtask 1.3: DB: sqlalchemy[asyncio], asyncpg, alembic
- [x] Subtask 1.4: Tools: chromadb, tavily-python, pypdf, langchain-text-splitters
- [x] Subtask 1.5: Test: pytest, pytest-asyncio, httpx, aiosqlite
- [x] Task 2: `pytest.ini` mit `asyncio_mode = auto` konfigurieren (AC: 2, 3)
- [x] Task 3: `backend/__init__.py` für Package-Erkennung
## Dev Notes
- `aiosqlite` wird für In-Memory-SQLite in Tests verwendet (kein PostgreSQL in CI)
- `httpx` für async FastAPI-Test-Client (`AsyncClient`)
### Project Structure Notes
- `backend/requirements.txt`
- `backend/pytest.ini`
### References
- [Source: CLAUDE.md#Python Code Style]
- [Source: _bmad-output/planning-artifacts/architecture.md#Technische-Abhängigkeiten]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- `aiosqlite` ermöglicht In-Memory-SQLite für Tests ohne laufenden PostgreSQL-Server.
- `asyncio_mode = auto` ist essentiell für pytest-asyncio-Kompatibilität mit allen async Fixtures.
### File List
- `backend/requirements.txt`
- `backend/pytest.ini`
- `backend/__init__.py`

View file

@ -0,0 +1,73 @@
# Story 1.3: Datenbank-Migrationen mit Alembic
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Backend-Entwickler**,
möchte ich **Alembic-Migrationen für `blueprints` und `council_runs`**,
so dass ich **das Datenbankschema versioniert verwalten kann**.
## Acceptance Criteria
1. `alembic upgrade head` erstellt Tabellen `blueprints` und `council_runs`
2. `alembic current` zeigt die aktuelle Revision
3. `blueprints`-Tabelle hat: `id` (UUID), `version` (Int), `name`, `nodes` (JSON), `edges` (JSON), `created_at`, `updated_at`
4. `council_runs`-Tabelle hat: `id`, `blueprint_id` (nullable FK), `input_topic`, `status`, `final_draft`, `critic_score`, `iteration_count`, `created_at`, `completed_at`
5. SQLAlchemy async ORM-Modelle existieren für beide Tabellen
## Tasks / Subtasks
- [x] Task 1: Alembic-Konfiguration initialisieren (AC: 1, 2)
- [x] Subtask 1.1: `alembic.ini` mit `script_location` konfigurieren
- [x] Subtask 1.2: `alembic/env.py` für async SQLAlchemy anpassen
- [x] Task 2: Migration 001 — `blueprints`-Tabelle (AC: 3)
- [x] Task 3: Migration 002 — `council_runs`-Tabelle (AC: 4)
- [x] Task 4: SQLAlchemy ORM-Modelle (AC: 5)
- [x] Subtask 4.1: `models/blueprint.py`
- [x] Subtask 4.2: `models/council_run.py`
- [x] Task 5: `database.py` mit async Engine und Session-Fabrik
## Dev Notes
- Async SQLAlchemy mit `asyncpg` für PostgreSQL, `aiosqlite` für Tests
- `get_session()` als FastAPI-Dependency für Dependency Injection
- In Tests: `AsyncEngine` mit In-Memory-SQLite, `create_all()` statt Alembic
### Project Structure Notes
- `backend/alembic/versions/001_create_blueprints_table.py`
- `backend/alembic/versions/002_create_council_runs_table.py`
- `backend/models/blueprint.py`
- `backend/models/council_run.py`
- `backend/database.py`
### References
- [Source: _bmad-output/planning-artifacts/architecture.md#Datenbankschema]
- [Source: CLAUDE.md#Database — Alembic for migrations]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- Alembic `env.py` wurde für async Engine (run_async_migrations) angepasst.
- ORM-Modelle nutzen `String(36)` für UUIDs (portabel zwischen PostgreSQL und SQLite).
### File List
- `backend/alembic.ini`
- `backend/alembic/env.py`
- `backend/alembic/versions/001_create_blueprints_table.py`
- `backend/alembic/versions/002_create_council_runs_table.py`
- `backend/models/__init__.py`
- `backend/models/blueprint.py`
- `backend/models/council_run.py`
- `backend/database.py`

View file

@ -0,0 +1,66 @@
# Story 2.1: CouncilState TypedDict implementieren
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Backend-Entwickler**,
möchte ich **den `CouncilState` TypedDict mit allen Feldern und Reducern**,
so dass er **das einzige State-Objekt für alle Agents ist**.
## Acceptance Criteria
1. `feedback_history: Annotated[List[str], operator.add]` — append-only Reducer
2. `messages: Annotated[list, operator.add]` — akkumulierend
3. `APPROVAL_THRESHOLD = 8.0` importierbar aus `state`
4. `MAX_ITERATIONS = 5` importierbar aus `state`
5. Alle Felder haben korrekte Typ-Annotationen (mypy-kompatibel)
## Tasks / Subtasks
- [x] Task 1: `state.py` mit `CouncilState` TypedDict (AC: 15)
- [x] Subtask 1.1: `input_topic`, `current_draft`, `route_decision` (str)
- [x] Subtask 1.2: `feedback_history` mit `operator.add` Reducer
- [x] Subtask 1.3: `messages` mit `operator.add` Reducer
- [x] Subtask 1.4: `iteration_count` (int), `critic_score` (Optional[float])
- [x] Subtask 1.5: `run_id`, `active_node` (str)
- [x] Task 2: Konstanten `APPROVAL_THRESHOLD`, `MAX_ITERATIONS` (AC: 3, 4)
- [x] Task 3: Unit-Tests für Reducer-Verhalten (AC: 1, 2)
- [x] Subtask 3.1: Test `feedback_history` akkumuliert
- [x] Subtask 3.2: Test `messages` akkumuliert
- [x] Subtask 3.3: Test Konstanten-Import
## Dev Notes
- `operator.add` als Reducer → Werte werden niemals überschrieben, nur angehängt
- `typing_extensions.TypedDict` für Python 3.10-Kompatibilität
- Bezug: `from typing import Annotated, List, Optional` und `import operator`
### Project Structure Notes
- `backend/state.py`
- `backend/tests/test_state.py`
### References
- [Source: CLAUDE.md#Architecture Concepts — CouncilState]
- [Source: _bmad-output/planning-artifacts/architecture.md#CouncilState-TypedDict]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- `typing_extensions.TypedDict` statt `typing.TypedDict` für breitere Python-Kompatibilität.
- Tests benutzen `operator.add` direkt um Reducer-Semantik zu validieren.
### File List
- `backend/state.py`
- `backend/tests/test_state.py`

View file

@ -0,0 +1,65 @@
# Story 2.2: Master-Agent-Node implementieren
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Backend-Entwickler**,
möchte ich **den `master_agent_node`**,
so dass er **auf Basis von `input_topic` und `feedback_history` einen Draft erstellt**.
## Acceptance Criteria
1. Bei leerem `feedback_history` → Initialer Draft auf Basis von `input_topic`
2. Bei befülltem `feedback_history` → User-Prompt enthält Feedback-Einträge
3. Rückgabe enthält `current_draft` (nicht leer), `messages` (3 Elemente), `active_node = "master_agent"`
4. `iteration_count` wird um 1 erhöht
5. LLM: Claude 3.5 Sonnet, `temperature=0.7`
## Tasks / Subtasks
- [x] Task 1: `agents/master_agent.py` implementieren (AC: 15)
- [x] Subtask 1.1: `_build_master_prompt(state)` für initiale vs. Feedback-Iterationen
- [x] Subtask 1.2: `master_agent_node(state)` LangGraph-Node-Funktion
- [x] Subtask 1.3: `SystemMessage`, `HumanMessage`, `AIMessage` in `messages`
- [x] Task 2: `agents/__init__.py` mit Exports
- [x] Task 3: Unit-Tests (AC: 14)
- [x] Subtask 3.1: Test initiale Draft-Erstellung (kein Feedback)
- [x] Subtask 3.2: Test Feedback-Integration im Prompt
- [x] Subtask 3.3: Test `iteration_count` +1
## Dev Notes
- Mock-Pattern: `@patch("agents.master_agent.ChatAnthropic")`
- LLM-Aufruf: `llm.invoke([SystemMessage(...), HumanMessage(...)])` → AIMessage
- Bezug: `from langchain_anthropic import ChatAnthropic`, `from langchain_core.messages import ...`
### Project Structure Notes
- `backend/agents/master_agent.py`
- `backend/agents/__init__.py`
- `backend/tests/test_api.py` (integrationsähnliche Tests)
### References
- [Source: CLAUDE.md#Python Code Style]
- [Source: _bmad-output/planning-artifacts/architecture.md#CouncilState-TypedDict]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- `_build_master_prompt()` unterscheidet via `if not state["feedback_history"]` zwischen erstem Aufruf und Iterations.
- `feedback_block` formatiert alle Feedback-Runden nummeriert.
### File List
- `backend/agents/master_agent.py`
- `backend/agents/__init__.py`

View file

@ -0,0 +1,56 @@
# Story 2.4: Writer-Agent-Node implementieren
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Backend-Entwickler**,
möchte ich **den `writer_agent_node`**,
so dass er **den finalen, vom Critic genehmigten Draft professionell poliert**.
## Acceptance Criteria
1. Erhält `current_draft` aus State, gibt polierten finalen Draft zurück
2. `active_node = "writer_agent"` im Rückgabe-Dict
3. LLM: Claude 3.5 Sonnet, `temperature=0.3` (deterministische Ausgabe)
4. System-Prompt instruiert: Grammatik/Stil verbessern, Inhalt NICHT ändern
5. Tests: LLM gemockt, Rückgabe-Dict validiert
## Tasks / Subtasks
- [x] Task 1: `agents/writer_agent.py` implementieren (AC: 14)
- [x] Subtask 1.1: `_SYSTEM_PROMPT` für finale Politur-Anweisung
- [x] Subtask 1.2: `writer_agent_node(state)` LangGraph-Node-Funktion
- [x] Subtask 1.3: Rückgabe: `current_draft`, `messages`, `active_node`
- [x] Task 2: Unit-Tests (AC: 13, 5)
## Dev Notes
- `temperature=0.3` für konsistente Formatierung (weniger Kreativität als Master)
- System-Prompt: "Do NOT change the factual content or overall structure"
- Einfachster der drei Agent-Nodes — kein bedingtes Routing
### Project Structure Notes
- `backend/agents/writer_agent.py`
### References
- [Source: _bmad-output/planning-artifacts/epics.md#Story-2.4]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- Writer-Agent gibt nur `current_draft`, `messages`, `active_node` zurück (kein `route_decision`).
### File List
- `backend/agents/writer_agent.py`

View file

@ -0,0 +1,62 @@
# Story 2.5: LangGraph-Graph bauen und ausführen
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Backend-Entwickler**,
möchte ich **einen kompilierten LangGraph-Graphen** (`build_council_graph`),
so dass er **Master→Critic→(conditional)→Writer ausführt**.
## Acceptance Criteria
1. `build_council_graph()` gibt kompilierten, `invoke`-fähigen `StateGraph` zurück
2. Graph-Topologie: `master_agent → critic_agent → (rework: master_agent | approve: writer_agent) → END`
3. `route_after_critic(state)` liest `route_decision` und leitet korrekt weiter
4. `run_council_async()` führt den Graph in einem Thread-Pool aus (blockiert Event Loop nicht)
5. WebSocket-Callback `on_node_start` wird vor jedem Node aufgerufen
## Tasks / Subtasks
- [x] Task 1: `services/graph_builder.py` mit `build_council_graph()` (AC: 1, 2)
- [x] Subtask 1.1: `StateGraph(CouncilState)` mit drei Nodes
- [x] Subtask 1.2: `set_entry_point("master_agent")`
- [x] Subtask 1.3: `add_conditional_edges` für Critic-Routing
- [x] Task 2: `route_after_critic(state)` Routing-Funktion (AC: 3)
- [x] Task 3: `run_council_async()` mit `asyncio.get_event_loop().run_in_executor()` (AC: 4, 5)
- [x] Task 4: Integration-Tests (LLMs gemockt) (AC: 13)
## Dev Notes
- `interrupt_before` nicht in Phase-1-Graph (nur in dynamischem Builder Phase 3)
- `on_node_start`-Callback: `lambda run_id, node_name: broadcast_event(run_id, {...})`
- Thread-Pool: `asyncio.get_event_loop().run_in_executor(None, graph.invoke, state)`
### Project Structure Notes
- `backend/services/graph_builder.py`
- `backend/tests/test_routing.py`
### References
- [Source: _bmad-output/planning-artifacts/architecture.md#ADR-002]
- [Source: CLAUDE.md#Key Design Constraints — Cycles are first-class]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- `run_in_executor(None, ...)` nutzt Standard-Thread-Pool von asyncio; kein expliziter ThreadPoolExecutor nötig.
- Wrapper-Nodes für `on_node_start`-Callback werden zur Laufzeit um den Original-Node gelegt.
### File List
- `backend/services/graph_builder.py`
- `backend/tests/test_routing.py`

View file

@ -0,0 +1,72 @@
# Story 2.6: FastAPI-Run-Endpunkte implementieren
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **API-Nutzer**,
möchte ich **`POST /api/councils/run` und `GET /api/councils/run/{run_id}`**,
so dass ich **einen Council-Run starten und sein Ergebnis abrufen kann**.
## Acceptance Criteria
1. `POST /api/councils/run``202 Accepted` mit `run_id`, asynchroner Hintergrundausführung
2. `GET /api/councils/run/{run_id}``status`, `final_draft` bei abgeschlossenem Run
3. Unbekannte `run_id``404 Not Found`
4. Leeres `input_topic` → Validierungsfehler (422)
5. `GET /api/health``{"status": "ok", "service": "CouncilOS API"}`
6. FastAPI-App registriert alle Router unter `/api`-Prefix
## Tasks / Subtasks
- [x] Task 1: `api/routes.py` mit Run-Endpunkten (AC: 14)
- [x] Subtask 1.1: `CouncilRunRequest` Pydantic-Modell
- [x] Subtask 1.2: `POST /councils/run` mit `BackgroundTasks`
- [x] Subtask 1.3: `GET /councils/run/{run_id}` mit `run_store.get()`
- [x] Subtask 1.4: `GET /health` (AC: 5)
- [x] Task 2: `api/run_store.py` In-Memory-Run-Store (AC: 2, 3)
- [x] Task 3: `main.py` FastAPI-Entrypoint (AC: 6)
- [x] Subtask 3.1: Router-Registrierung mit `/api` Prefix
- [x] Subtask 3.2: CORS-Middleware
- [x] Task 4: Unit-Tests für alle Endpunkte (AC: 15)
## Dev Notes
- `BackgroundTasks.add_task()` für nicht-blockierende LangGraph-Ausführung
- `run_store.create(run_id, input_topic)` → status: "pending"
- Test-Setup: `AsyncClient(app=app)` via `httpx` ohne echten LangGraph-Lauf
### Project Structure Notes
- `backend/api/routes.py`
- `backend/api/run_store.py`
- `backend/main.py`
- `backend/tests/test_api.py`
### References
- [Source: _bmad-output/planning-artifacts/architecture.md#REST-Endpunkte]
- [Source: _bmad-output/planning-artifacts/architecture.md#ADR-002]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- `run_store` ist ein einfaches `dict` mit Thread-Safe-Operationen (GIL ausreichend für MVP).
- `BackgroundTasks` erlaubt sofortige `202`-Antwort ohne auf LangGraph zu warten.
- CORS: `allow_origins=["*"]` für lokale Entwicklung; in Produktion einschränken.
### File List
- `backend/api/__init__.py`
- `backend/api/routes.py`
- `backend/api/run_store.py`
- `backend/main.py`
- `backend/tests/test_api.py`

View file

@ -0,0 +1,76 @@
# Story 3.1: React-Flow-Canvas mit Custom Agent Node
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Nutzer**,
möchte ich **Agent-Nodes per Drag & Drop auf den Canvas ziehen**,
so dass ich **meinen KI-Rat visuell zusammenstellen kann**.
## Acceptance Criteria
1. Drag & Drop aus linker Seitenleiste → Custom `AgentNode` erscheint auf Canvas
2. Klick auf Node → rechtes `NodeSettingsPanel` öffnet sich
3. Änderung von Name/Prompt/Modell → Canvas-Node aktualisiert sich live (Zustand-Bindung)
4. Node zeigt: Name, LLM-Modell (farblich kodiert), Tool-Badges (🔍, 📄)
5. Pulsier-Animation (`animate-pulse`) wenn `isActive = true`
## Tasks / Subtasks
- [x] Task 1: `components/nodes/AgentNode.tsx` Custom React Flow Node (AC: 1, 4, 5)
- [x] Subtask 1.1: Anzeige: Name, Modell-Badge, Tool-Icons (Globe, FileText)
- [x] Subtask 1.2: `isActive`-Zustand → `animate-pulse` + Indigo-Rahmen
- [x] Subtask 1.3: `Handle` (source + target) für Verbindungen
- [x] Task 2: `store/council-store.ts` Zustand-Store (AC: 3)
- [x] Subtask 2.1: `nodes`, `edges`, `selectedNodeId`, `councilName`
- [x] Subtask 2.2: `addAgentNode()`, `updateNodeData()`, `selectNode()`
- [x] Task 3: `components/panels/NodeSidebar.tsx` (AC: 1)
- [x] Task 4: `components/panels/NodeSettingsPanel.tsx` (AC: 2, 3)
- [x] Task 5: `components/ArchitectCanvas.tsx` React Flow Wrapper
- [x] Task 6: Frontend-Tests für Store und Parser (AC: 3)
## Dev Notes
- `NODE_TYPES = { agent: AgentNode }` in `ArchitectCanvas` registrieren
- Zustand via `useCouncilStore` (Zustand) — kein React-Kontext-Drill
- `memo()` für Performance bei vielen Nodes
### Project Structure Notes
- `frontend/app/components/nodes/AgentNode.tsx`
- `frontend/app/components/ArchitectCanvas.tsx`
- `frontend/app/components/panels/NodeSidebar.tsx`
- `frontend/app/components/panels/NodeSettingsPanel.tsx`
- `frontend/app/store/council-store.ts`
- `frontend/app/types/council.ts`
### References
- [Source: _bmad-output/planning-artifacts/ux-design.md#Agent-Node-Canvas-Element]
- [Source: CLAUDE.md#React-Conventions — custom node components]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- `MODEL_LABELS` und `MODEL_COLORS` als Lookup-Maps für farbliche Modell-Kodierung.
- `memo()` verhindert unnötige Re-Renders bei vielen Nodes auf dem Canvas.
### File List
- `frontend/app/types/council.ts`
- `frontend/app/store/council-store.ts`
- `frontend/app/components/nodes/AgentNode.tsx`
- `frontend/app/components/ArchitectCanvas.tsx`
- `frontend/app/components/NavTabs.tsx`
- `frontend/app/components/panels/NodeSidebar.tsx`
- `frontend/app/components/panels/NodeSettingsPanel.tsx`
- `frontend/app/app/__tests__/council-store.test.ts`

View file

@ -0,0 +1,63 @@
# Story 3.2: Lineare und bedingte Edges
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Nutzer**,
möchte ich **Pfeile zwischen Nodes ziehen und als linear/bedingt markieren**,
so dass ich **den Workflow meines Councils definieren kann**.
## Acceptance Criteria
1. Verbindungsziehen zwischen Handles → lineare graue Edge entsteht
2. Klick auf Edge → `EdgeSettingsPanel` öffnet sich
3. Edge als „bedingt" markieren mit Condition-Label → gestrichelte rote Linie mit Label
4. Edge als „approve" markieren → grüne Linie mit Label
5. Edge-Typ und Condition werden im Zustand persistiert
## Tasks / Subtasks
- [x] Task 1: `components/edges/ConditionalEdge.tsx` Custom React Flow Edge (AC: 3, 4)
- [x] Subtask 1.1: `SmoothStepPath` Basis-Routing
- [x] Subtask 1.2: Farb-Kodierung: grau (linear), rot gestrichelt (rework), grün (approve)
- [x] Subtask 1.3: Label-Rendering via `EdgeLabelRenderer`
- [x] Task 2: `components/panels/EdgeSettingsPanel.tsx` (AC: 2, 3, 4)
- [x] Subtask 2.1: Typ-Auswahl (linear/conditional)
- [x] Subtask 2.2: Condition-Label-Eingabe wenn `type = conditional`
- [x] Task 3: `store/council-store.ts``updateEdgeData()` Funktion (AC: 5)
- [x] Task 4: `EDGE_TYPES` Registrierung in `ArchitectCanvas`
## Dev Notes
- `EDGE_TYPES = { conditional: ConditionalEdge }` + `defaultEdgeType: "conditional"`
- Edge-Daten: `edge.data.type` (linear|conditional), `edge.data.condition` (string)
- `EdgeLabelRenderer` positioniert Label absolut relativ zum Edge-Mittelpunkt
### Project Structure Notes
- `frontend/app/components/edges/ConditionalEdge.tsx`
- `frontend/app/components/panels/EdgeSettingsPanel.tsx`
### References
- [Source: _bmad-output/planning-artifacts/ux-design.md#Edge-Verbindungspfeil]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- `getSmoothStepPath` aus `@xyflow/react` liefert `edgePath`, `labelX`, `labelY` in einem Aufruf.
- Farben werden direkt via `stroke`-Prop auf `BaseEdge` gesetzt.
### File List
- `frontend/app/components/edges/ConditionalEdge.tsx`
- `frontend/app/components/panels/EdgeSettingsPanel.tsx`

View file

@ -0,0 +1,66 @@
# Story 3.3: Blueprint-Parser (React Flow → JSON)
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **System**,
möchte ich **eine `parseGraphToBlueprint()`-Funktion**,
so dass sie **den Canvas-State in ein valides Blueprint-JSON konvertiert**.
## Acceptance Criteria
1. Nodes + Edges → `CouncilBlueprint` mit `version`, `name`, `nodes[]`, `edges[]`
2. Bedingte Edge → `type: "conditional"` und `condition: "<label>"` im Edge-JSON
3. Lineare Edge → `type: "linear"` ohne `condition`-Feld
4. `version: 1` als Startwert
5. Tests: Alle drei Fälle (linear, conditional, gemischt)
## Tasks / Subtasks
- [x] Task 1: `utils/blueprint-parser.ts` implementieren (AC: 14)
- [x] Subtask 1.1: Nodes-Mapping: `id`, `label`, `systemPrompt`, `model`, `tools`, `position`
- [x] Subtask 1.2: Edges-Mapping: `id`, `source`, `target`, `type`, optional `condition`
- [x] Subtask 1.3: Rückgabe: `CouncilBlueprint` mit `version: 1`
- [x] Task 2: `types/council.ts``CouncilBlueprint`, `BlueprintNode`, `BlueprintEdge` (AC: 1)
- [x] Task 3: Vitest-Tests (AC: 15)
- [x] Subtask 3.1: Test lineares Blueprint
- [x] Subtask 3.2: Test bedingte Edge
- [x] Subtask 3.3: Test leeres Canvas (0 Nodes/Edges)
## Dev Notes
- `existingId?: string` Parameter für Updates (PUT vs POST)
- Tool-Mapping: `webSearch`, `pdfReader` (camelCase im Frontend, snake_case im Backend)
- `EdgeType = "linear" | "conditional"` als Union-Typ
### Project Structure Notes
- `frontend/app/utils/blueprint-parser.ts`
- `frontend/app/types/council.ts`
- `frontend/app/__tests__/blueprint-parser.test.ts`
### References
- [Source: _bmad-output/planning-artifacts/architecture.md#Blueprint-JSON-Schema]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- `position`-Feld wird mitgespeichert, damit Blueprint-Reload die Node-Positionen wiederherstellt.
- `existingId` ermöglicht `PUT`-Updates ohne neue ID-Generierung.
### File List
- `frontend/app/utils/blueprint-parser.ts`
- `frontend/app/types/council.ts`
- `frontend/app/__tests__/blueprint-parser.test.ts`
- `frontend/app/__tests__/types.test.ts`

View file

@ -0,0 +1,76 @@
# Story 3.4: Blueprint CRUD — Frontend & Backend
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Nutzer**,
möchte ich **Councils speichern, laden und löschen können**,
so dass ich **meine Konfigurationen wiederverwenden kann**.
## Acceptance Criteria
1. `POST /api/councils/` mit Blueprint → `201 Created` + gespeichertes Blueprint mit `id`
2. `GET /api/councils/` → Liste aller Blueprints
3. `GET /api/councils/{id}` → Einzelnes Blueprint; unbekannte ID → `404`
4. `PUT /api/councils/{id}` → Aktualisiertes Blueprint
5. `DELETE /api/councils/{id}``204 No Content`; unbekannte ID → `404`
6. Frontend: „Speichern"-Button ruft `councilApi.create()` auf, Bestätigung erscheint
7. Frontend: Blueprint-Export als JSON-Datei
## Tasks / Subtasks
- [x] Task 1: `api/blueprint_routes.py` mit CRUD-Endpunkten (AC: 15)
- [x] Subtask 1.1: Pydantic-Request/Response-Modelle
- [x] Subtask 1.2: Alle fünf CRUD-Endpunkte mit DB-Session
- [x] Task 2: `services/blueprint_service.py` Service-Layer (AC: 15)
- [x] Subtask 2.1: `create_blueprint()`, `get_blueprint()`, `list_blueprints()`
- [x] Subtask 2.2: `update_blueprint()`, `delete_blueprint()`
- [x] Task 3: Frontend `utils/api-client.ts` Blueprint-Client (AC: 6)
- [x] Task 4: Speichern/Export-Buttons in `rat-architekt/page.tsx` (AC: 6, 7)
- [x] Task 5: Unit-Tests für Service und API (AC: 15)
## Dev Notes
- Service-Layer nutzt `AsyncSession` von SQLAlchemy
- Tests: In-Memory SQLite, `create_all()` statt Alembic
- Frontend: `URL.createObjectURL()` für JSON-Download ohne Server
### Project Structure Notes
- `backend/api/blueprint_routes.py`
- `backend/services/blueprint_service.py`
- `backend/tests/test_blueprint_api.py`
- `backend/tests/test_blueprint_service.py`
- `frontend/app/utils/api-client.ts`
- `frontend/app/rat-architekt/page.tsx`
### References
- [Source: _bmad-output/planning-artifacts/architecture.md#REST-Endpunkte]
- [Source: _bmad-output/planning-artifacts/epics.md#Story-3.4]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- `blueprint_router` wird in `main.py` unter `/api` Prefix eingehangen.
- Service-Layer macht API-Tests unabhängig von HTTP-Details.
- Alle 9 Blueprint-API-Tests und 10 Service-Tests bestehen.
### File List
- `backend/api/blueprint_routes.py`
- `backend/services/blueprint_service.py`
- `backend/tests/test_blueprint_api.py`
- `backend/tests/test_blueprint_service.py`
- `frontend/app/utils/api-client.ts`
- `frontend/app/rat-architekt/page.tsx`
- `frontend/app/__tests__/api-client.test.ts`

View file

@ -0,0 +1,66 @@
# Story 4.2: WebSocket-Agent-Events integrieren
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Frontend**,
möchte ich **WebSocket-Events vom Backend**,
so dass **der aktive Node im Canvas in Echtzeit pulsiert**.
## Acceptance Criteria
1. Backend sendet `{"event": "node_active", "node": "master_agent", ...}` bei Node-Eintritt
2. Backend sendet `{"event": "run_complete", "final_draft": "..."}` nach Abschluss
3. Backend sendet `{"event": "run_paused", "next_nodes": [...]}` bei God-Mode-Pause
4. Frontend `useCouncilWebSocket` Hook verbindet sich mit `WS /ws/council/{run_id}`
5. `markNodeActive(nodeName)` setzt `isActive = true` für den entsprechenden Canvas-Node
6. Bei WS-Verbindungsabbruch: Graceful-Handling ohne Crash
## Tasks / Subtasks
- [x] Task 1: `api/websocket.py` WebSocket-Endpoint (AC: 13)
- [x] Subtask 1.1: `_connections` Dict für aktive WS-Sessions
- [x] Subtask 1.2: `broadcast_event(run_id, event)` Funktion
- [x] Subtask 1.3: `WS /ws/council/{run_id}` Route
- [x] Task 2: `hooks/useCouncilWebSocket.ts` (AC: 46)
- [x] Subtask 2.1: `onComplete`, `onError`, `onPaused`, `onResumed` Callbacks
- [x] Subtask 2.2: Event-Dispatching per `event.type`
- [x] Subtask 2.3: Cleanup bei Unmount / Verbindungsabbruch
- [x] Task 3: Store-Integration: `markNodeActive(nodeName)` (AC: 5)
- [x] Task 4: WS-Router in `main.py` registrieren (AC: 1)
## Dev Notes
- `broadcast_event()` bereinigt tote Verbindungen automatisch (disconnected-Liste)
- Frontend: `useRef(null)` für WS-Instanz → sicheres Cleanup in useEffect-Return
- Event-Typen: `node_active`, `run_complete`, `run_paused`, `run_failed`
### Project Structure Notes
- `backend/api/websocket.py`
- `frontend/app/hooks/useCouncilWebSocket.ts`
### References
- [Source: _bmad-output/planning-artifacts/architecture.md#ADR-003]
- [Source: _bmad-output/planning-artifacts/architecture.md#WebSocket]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- WebSocket-Verbindungen werden per `run_id` isoliert — kein Cross-Event zwischen Runs.
- `try/except` in `broadcast_event()` verhindert, dass ein einzelner WS-Fehler alle Clients betrifft.
### File List
- `backend/api/websocket.py`
- `frontend/app/hooks/useCouncilWebSocket.ts`

View file

@ -0,0 +1,66 @@
# Story 4.3: Konferenzzimmer — Live-Execution-UI
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Nutzer**,
möchte ich **im Konferenzzimmer-Tab den laufenden Council in Echtzeit sehen**,
so dass ich **transparent nachvollziehen kann, was die KI gerade tut**.
## Acceptance Criteria
1. Eingabebereich für Prompt-Text und Mode-Toggle (Auto-Pilot / God Mode)
2. „Starten"-Button startet Blueprint-Run via `POST /api/councils/{id}/run`
3. WS-Verbindung öffnet sich → aktiver Node pulsiert gelb im Live-Canvas
4. Nach Abschluss: finales Dokument erscheint im Output-Bereich
5. Bei Fehler: klare Fehlermeldung mit Fehlergrund
6. God-Mode-Overlay erscheint bei `status=paused`
## Tasks / Subtasks
- [x] Task 1: `konferenzzimmer/page.tsx` Hauptkomponente (AC: 16)
- [x] Subtask 1.1: Prompt-Eingabe + Blueprint-Auswahl-Dropdown
- [x] Subtask 1.2: Mode-Toggle (Auto-Pilot / God Mode)
- [x] Subtask 1.3: `useCouncilWebSocket` Hook-Integration
- [x] Subtask 1.4: Live-Canvas (read-only React Flow)
- [x] Subtask 1.5: Output-Bereich mit `whitespace-pre-wrap`
- [x] Task 2: God-Mode-Overlay-Komponente (AC: 6)
- [x] Subtask 2.1: Approve / Reject / Modify Buttons
- [x] Subtask 2.2: Modify-Modus: Textarea für Draft-Bearbeitung
- [x] Task 3: Blueprint-Run-Endpunkt `POST /api/councils/{id}/run` (AC: 2)
- [x] Task 4: `GET /api/councils/` für Blueprint-Liste im Dropdown
## Dev Notes
- Live-Canvas ist schreibgeschützt: `nodesDraggable={false}`, `nodesConnectable={false}`
- `markNodeActive(nodeName)` sucht Node per `data.label`-Übereinstimmung
- God-Mode-Overlay: `fixed inset-0` Backdrop mit zentriertem Modal
### Project Structure Notes
- `frontend/app/konferenzzimmer/page.tsx`
- Wiederverwendung: `ArchitectCanvas`, `useCouncilWebSocket`, `council-store`
### References
- [Source: _bmad-output/planning-artifacts/ux-design.md#User-Journey-God-Mode]
- [Source: _bmad-output/planning-artifacts/architecture.md#Sequenzdiagramm-Council-Run]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- Polling-Fallback für Blueprint-Liste: `councilApi.list()` beim Mount einmalig aufgerufen.
- God-Mode-Overlay nutzt `onPaused`-Callback aus `useCouncilWebSocket`.
### File List
- `frontend/app/konferenzzimmer/page.tsx`

View file

@ -0,0 +1,62 @@
# Story 5.1: Tavily Web-Suche als Agent-Tool
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Nutzer**,
möchte ich **Web-Suche als optionales Tool für jeden Agent**,
so dass **Agents aktuelle Informationen aus dem Internet nutzen können**.
## Acceptance Criteria
1. Agent mit `tools.web_search = true``web_search`-Tool wird gebunden
2. `TAVILY_API_KEY` gesetzt → formatierte Trefferliste wird zurückgegeben
3. `TAVILY_API_KEY` nicht gesetzt → Fehlermeldung, kein Crash
4. `max_results=5` als Standard; `search_depth="basic"`
5. Tests: gemockt via `@patch("tools.web_search.TavilyClient")`
## Tasks / Subtasks
- [x] Task 1: `tools/web_search.py` mit `@tool`-Decorator (AC: 14)
- [x] Subtask 1.1: `TavilyClient`-Import (lazy, innerhalb der Funktion)
- [x] Subtask 1.2: API-Key-Check vor Client-Initialisierung (AC: 3)
- [x] Subtask 1.3: Response-Formatierung: Titel, URL, Snippet
- [x] Task 2: Tool-Binding im dynamischen Graph-Builder (AC: 1)
- [x] Subtask 2.1: `tools.web_search = true``llm.bind_tools([web_search])`
- [x] Task 3: Unit-Tests (AC: 2, 3, 5)
## Dev Notes
- `from tavily import TavilyClient` lazy import — kein `ImportError` wenn nicht installiert
- LangChain `@tool`-Decorator macht die Funktion Tool-Aufruf-kompatibel
- Fehlerpfad gibt `"[Web Search Error] ..."` String zurück (kein Exception-Raise)
### Project Structure Notes
- `backend/tools/web_search.py`
- `backend/tests/test_tools.py`
### References
- [Source: _bmad-output/planning-artifacts/prd.md#FR-05.2]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- Lazy-Import von `TavilyClient` verhindert `ImportError` in Umgebungen ohne das Paket.
- Mock-Pattern: `@patch("tools.web_search.TavilyClient")` mockt direkt den Client.
### File List
- `backend/tools/web_search.py`
- `backend/tools/__init__.py`
- `backend/tests/test_tools.py`

View file

@ -0,0 +1,66 @@
# Story 5.2: PDF-Upload & ChromaDB-Ingestion
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Nutzer**,
möchte ich **ein PDF hochladen, das als Wissensquelle für Agents dient**,
so dass **Agents auf Inhalte aus langen Dokumenten zugreifen können**.
## Acceptance Criteria
1. `POST /api/councils/upload-pdf` mit PDF → `chunks_ingested` in Response
2. Nicht-PDF-Datei → `400 Bad Request`
3. PDF wird in Chunks aufgeteilt (PyPDF + LangChain TextSplitter) und in ChromaDB gespeichert
4. `pdf_search(query)` gibt Top-K semantisch ähnliche Chunks zurück
5. Agent mit `tools.pdf_reader = true` bekommt `pdf_search`-Tool gebunden
## Tasks / Subtasks
- [x] Task 1: `tools/pdf_reader.py` (AC: 3, 4)
- [x] Subtask 1.1: `ingest_pdf(file_path)` → Chunks → ChromaDB
- [x] Subtask 1.2: `pdf_search(query, top_k)` → semantische Suche
- [x] Subtask 1.3: `_get_chroma_collection()` mit In-Memory-Cache
- [x] Task 2: `POST /api/councils/upload-pdf` Endpunkt in `routes.py` (AC: 1, 2)
- [x] Subtask 2.1: `UploadFile` + MIME-Type-Validierung
- [x] Subtask 2.2: Temp-Datei erstellen, `ingest_pdf()` aufrufen, bereinigen
- [x] Task 3: Tool-Binding im dynamischen Graph-Builder (AC: 5)
- [x] Task 4: Unit-Tests (gemockt) (AC: 14)
## Dev Notes
- ChromaDB `PersistentClient` mit `CHROMA_PERSIST_DIR`-Umgebungsvariable
- Cosine-Similarity als Distance-Metrik: `{"hnsw:space": "cosine"}`
- `_collection_cache` dict verhindert mehrfache ChromaDB-Initialisierungen
- Tests mocken `chromadb.PersistentClient` komplett
### Project Structure Notes
- `backend/tools/pdf_reader.py`
- `backend/tests/test_tools.py`
### References
- [Source: _bmad-output/planning-artifacts/architecture.md#ADR-006]
- [Source: _bmad-output/planning-artifacts/prd.md#FR-05.3]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- `UploadFile.content_type` für MIME-Validierung; `.filename.endswith(".pdf")` als Fallback.
- `tempfile.NamedTemporaryFile` mit `delete=False` für sicheres Temp-File-Handling.
- ChromaDB-Kollektion wird pro `collection_name` gecacht.
### File List
- `backend/tools/pdf_reader.py`
- `backend/tests/test_tools.py`

View file

@ -0,0 +1,67 @@
# Story 5.4: Run-Verlauf (History)
<!-- 🏃 Prepared by BMAD SM Skill — Agent: Bob (Scrum Master) -->
<!-- Skill Command: /bmad-agent-bmm-sm → [CS] Context Story -->
Status: done
## Story
Als **Nutzer**,
möchte ich **vergangene Council-Runs einsehen**,
so dass ich **Ergebnisse nachträglich nachschlagen kann**.
## Acceptance Criteria
1. `GET /api/runs/` → paginierte Liste aller Runs (neueste zuerst)
2. `GET /api/runs/{run_id}``status`, `final_draft`, `critic_score`, `iteration_count`, `input_topic`
3. Unbekannte `run_id``404 Not Found`
4. Abgeschlossene Runs werden in PostgreSQL persistiert (`council_runs`-Tabelle)
5. `?skip=0&limit=20` Query-Parameter für Paginierung
## Tasks / Subtasks
- [x] Task 1: `services/run_service.py` (AC: 14)
- [x] Subtask 1.1: `create_run()`, `get_run()`, `list_runs(skip, limit)`
- [x] Subtask 1.2: `update_run_completed()`, `update_run_failed()`
- [x] Task 2: `api/run_history_routes.py` REST-Endpunkte (AC: 13, 5)
- [x] Subtask 2.1: `RunHistoryResponse` Pydantic-Modell
- [x] Subtask 2.2: `GET /runs/` und `GET /runs/{run_id}`
- [x] Task 3: Run nach Abschluss in DB persistieren
- [x] Subtask 3.1: `create_run()` beim Start (in `routes.py`)
- [x] Subtask 3.2: `update_run_completed()` nach Graph-Abschluss
- [x] Task 4: Unit-Tests (AC: 15)
## Dev Notes
- `run_history_router` unter `/api`-Prefix eingehangen
- DB-Persistierung: `create_run()` bei `POST /run``update_run_completed()` nach Fertigstellung
- `list_runs()` nutzt `ORDER BY created_at DESC` für neueste-zuerst Sortierung
### Project Structure Notes
- `backend/services/run_service.py`
- `backend/api/run_history_routes.py`
- `backend/tests/test_run_service.py`
### References
- [Source: _bmad-output/planning-artifacts/architecture.md#Datenbankschema — council_runs]
- [Source: _bmad-output/planning-artifacts/prd.md#FR-02.7]
## Dev Agent Record
### Agent Model Used
Amelia (💻 BMAD Dev Agent)
### Completion Notes List
- `run_history_router` ist von `routes.py` getrennt, da Council-Runs und Run-History konzeptionell verschiedene Ressourcen sind.
- Tests nutzen `AsyncSession` mit In-Memory-SQLite (`aiosqlite`).
### File List
- `backend/services/run_service.py`
- `backend/api/run_history_routes.py`
- `backend/tests/test_run_service.py`