34 KiB
Plan: creator3 — Themen-Guide-Generator (Neubau unter /home/arbeit/projects/creator3)
STATUS: FINAL. Basis: 10 Recherche-Agenten (Befunde R1-R11 im Anhang) + 1 Design-Agent.
Kontext
creator3 ersetzt creator2 für den Themen-Modus: Guides zu Themen (Symfony, Kommunikation, Psychologie) aus Web-Recherche. Kernziel: Abdeckung nahe 100 % (Lücken minimieren), dann Länge minimieren (Dopplungen minimieren). Nur MiniMax-API als LLM. Neu ggü. creator2: Pipeline als konfigurierbarer Graph (pipeline.yaml), Agenten bekommen Skill-Dateien (Rolle/Aufgabe/Stil), Graphensicht statt Kanban, Gates ÜBERALL hart, Budget default AN. uni-Modus, Artefakte (Flashcards/Üben) und Level-Durchgänge entfallen. creator2 nur Lesereferenz. NIEMALS git commit.
Stack
Python 3.12 + FastAPI + SQLite (WAL, EINE Writer-Connection) + Vue 3/Vite. LLM: MiniMax-M3 via https://api.minimax.io/anthropic/v1/messages (thinking IMMER explizit: disabled für Extraktion/Judge, adaptive für Writer). Suche: Wikipedia de+en immer + Kette Tavily→Brave→ddgs (Circuit Breaker). Extraktion: trafilatura ≥2.1 markdown + ftfy NFC (einmalig vor Snapshot). Frontend: markdown-it + Shiki (statt marked/hljs), Graph als Eigenbau (HTML-Knoten + SVG-Kanten + @panzoom/panzoom).
Pipeline-Graph (24 Knoten, 5 Stufen; Stufe = Spalte in Graphensicht)
Stufe 1 korpus: recherche_plan (llm) → suche (system) → laden (system) → soll_extraktion (llm, 40k-Chunks) → soll_konsens (llm, Barriere; Judge gruppiert, CODE zählt ≥2 Quellen) → gate_korpus. Stufe 2 inventar: atom_extraktion (llm, 6k-Abschnitte × 2 Reader) → anker_fix (llm; erst det. 5-Stufen-Re-Match) → dedup (llm, Barriere; Auto-Merge Anker-Overlap ≥0.5 + Kandidaten-Kanäle + 2er-Panel einstimmig) → soll_zuordnung (llm) → luecke (llm; art nachextraktion|freispruch) → gate_inventar (Coverage: jeder bestätigte Soll-Punkt ≥1 Atom oder Freispruch — deterministisch). Stufe 3 struktur: lernziele → bausteine (Band 4-8) → ordnung (Barriere; themen_schnitt + permutation, exakt validiert, Prior-Fallback) → gate_struktur (Coverage: jeder Soll-Punkt ≥1 Baustein). Stufe 4 guide: writer (DELIMITED ===TEXT===, „bekannt"-Kontext, Verweis statt Wiederholung) + kapitel_intro → det_pruefung (system) → llm_pruefung (2er-Panel, qa_hash fail-closed) → fix (Cap 3) → redundanz (Barriere; Satzpaar-Jaccard ≥0.6 über Kapitelgrenzen → 2er-Panel) → gate_guide (3er-Klärung für Eskalationen). Stufe 5: montage (system; Marker-Einmaligkeit, Anker-Links, topic=fertig).
Rücklauf-Kanten (nur von Gates, Task-Rezepte mit Runde+1 im Item-Schlüssel): gate_korpus→recherche_plan (neue Lenses, Cap 4); gate_inventar→suche (Web-Nachrecherche für Lücken, Queries deterministisch aus Punkt+Zitat gegroundet, Cap 1). Reparatur-Kanten: gate_korpus→soll_konsens; gate_inventar→anker_fix|luecke; gate_struktur→bausteine; gate_guide→fix|det_pruefung|redundanz. Gate-Schutz dreifach: max_versuche je Task, GATE_RUNDEN_MAX je Gate, Stillstand-Fingerprint (SHA-256 sortierte Befundmenge, 2× identisch → runs.status='paused', fail-closed).
Backend-Dateiplan (~4700 Zeilen, deutsche Namen)
- config.py (160): Konstanten + .env-Loader (Datei gewinnt), Topic-Namens-Regex.
- db.py (260): WAL, eine Writer-Connection, busy_timeout 5000, on_change-Hook, Schema.
- engine.py (420): Scheduler auf asyncio.Event-Wecker + 2-s-Poll; Claim UPDATE…WHERE status='offen' (rowcount==1); Zombie-Reset beim Start; Barriere-Logik; Gate-Trigger bei leerer Stufe; Ergebnis+fertig+Folgetasks in EINER Transaktion; Infra-Retries getrennt von versuch; Budget-/Pause-Prüfung.
- graph.py (140): pipeline.yaml laden/validieren (graphlib-DAG, Worker-Registry, Skills existieren, 1 Gate/Stufe) + Layout-Export.
- pipeline.yaml (120): Knoten (id, typ, worker, skills[], stufe, max_parallel, max_versuche, barriere) + Kanten (von, nach, art normal|reparatur|ruecklauf).
- llm.py (280): call()-Zustandsmaschine ok|parse|cap|infra|error|pause (cap = KEIN Retry); Backoff 8/16/32 + Jitter → LaufPause; globale Drossel (429 + Statuscodes 1002/1041/2045, Breite halbieren min 4, 20 Erfolge = +1); Hedge (max(90, timeout/2), Verlierer verbuchen); Budget VOR+NACH; Fake-Hook vor allem außer Pause-Check.
- skills.py (120): Frontmatter, Komposition 1 Rolle + 1 Aufgabe + 0..n Stil (Format nur+zuletzt in Aufgabe), {{platzhalter}} per str.replace mit Vorab-Validierung, zweistufiges Hashing → skill_hash ins Ledger.
- ledger.py (100): events im finally JEDES Versuchs (inkl. hedge/hedge_cancel), budget_pruefen, Kennzahlen (Coverage, Dopplungsquote, Kosten je Knoten).
- panels.py (80): einstimmig (None = keine Stimme), panel mit Ersatz-Richter, klaerung (3er-Mehrheit).
- textkit.py (380): norm, finde_zitat (5 Stufen mit Negations-/Eindeutigkeits-Guards), titel_kern, abschnitte, ueberlappung, negations_menge; NEU satz_split, jaccard_gestemmt (PyStemmer), akronym_kanal.
- suche.py (220): Wikipedia REST+Action-API (UA mit KONTAKT_MAIL, max 3 parallel) + Tavily/Brave/ddgs-Kette, Timeout 10 s/1 Retry, Circuit Breaker, URL-Dedupe; degraded-Befund statt Crash.
- laden.py (200): httpx (Bytes! → trafilatura markdown → ftfy NFC → Snapshot via Temp+os.replace), Roh-HTML archivieren, robots.txt-Cache, 1 req/s/Domain, JS-/PDF-Detektor → Befund.
- korpus.py (280), inventar.py (420), dedup.py (220), struktur.py (280), guide.py (380), redundanz.py (110), montage.py (90), belege.py (60): Stufen-Worker wie im Graph.
- fakes.py (320): Handler-Registry über
<!-- aufgabe:name -->-Marker, harter Fehler ohne Handler; Fakes LESEN Prompt (Zitat-Gate mitgetestet); Fake-Suche/-Laden mit präparierten Quellen (Dublette inkl. Akronym, Lücke die erst Nachrecherche deckt, Kapitel-übergreifende Dopplung). - ws.py (60): Hub eine Queue + ein Sender-Task; dirty-Signal {typ, topic, bereich}.
- main.py (260): Lifespan (kein on_event), Routen, ensure_future + _laeufe-Dict.
DB-Schema
topics(name PK, titel, status neu→…→fertig, auto, erstellt); runs(id, topic, status running|done|paused|stopped|budget|failed, stufe, budget_tokens, grund, git_hash); tasks(id, run_id, topic, knoten, item, status offen|laufend|fertig|fehler, art, versuch, infra_versuch, runde, payload, ergebnis, fehler, erzeugt_von, gestartet, beendet, UNIQUE(topic,knoten,item)); gate_laeufe(run_id, knoten, runde, status gruen|rot|pause, fingerprint, befunde, UNIQUE(run_id,knoten,runde)); events(run_id, ts, stufe, knoten, item, skills, skill_hash, model, role, status, dur_ms, wait_ms, tok_in/out/cache_read/cache_write, meta); quellen(id, topic, titel, url, url_norm UNIQUE(topic,url_norm), backend, snapshot, roh, hash, runde, zweck korpus|luecke:, status, grund, atome_stand); soll(id, topic, punkt, status, belege JSON, kapitel_id, geprueft, freispruch); atome(id, topic, titel, typ, definition, status, soll_id, ziel_id, baustein_id, ord, merged_into); anker(atom_id, quelle_id, start, ende, zitat; start=-1 unverankert); kanten(von_atom, zu_atom, art='verwandt', UNIQUE); lernziele; bausteine(ziel_id, titel, ord, kapitel_id); kapitel(titel, intro, ord); sections(baustein_id PK, stage, text, qa_hash, fix_versuche) — EIN Text, kein kompakt/lang-Dual; befunde(run_id, stufe, knoten, art, item, detail, status offen|behoben|freispruch|veraltet, quelle, runde, geklaert).
Skills (Frontmatter: name, art, beschreibung, [platzhalter])
- rolle/: extraktor, richter, autor, planer (je mit konkreten Verhaltensregeln, kein Rollen-Theater).
- stil/: deutsch-praezise, guide-lesbarkeit (R10-Regeln inkl. „Ein-Satz-Refresher + Anker-Link statt Wiederholung").
- aufgabe/ (20): recherche-plan, soll-extraktion, soll-konsens, soll-beleg, atom-extraktion, anker-fix, dedup-urteil, soll-zuordnung, luecke-nachextraktion, luecke-freispruch, lernziele, bausteine, themen-schnitt, ordnung, writer, kapitel-intro, guide-pruefung, guide-fix, redundanz-urteil, klaerung. Alle DELIMITED-Ausgabe (===BLOCK===), zusammengesetzt ≤ ~1000 Tokens, ≤ 8-10 Regeln; Rolle+Stil als stabiler Cache-Präfix.
API + Frontend
Routen: GET/POST/DELETE /api/topics; /start {budget?}, /pause, /weiter, /stop; GET /graph (Struktur + Zähler-Snapshot + Gate-Status — KEIN Monolith); GET /knoten/{id}/tasks (lazy); GET /guide (Kapitel-Struktur); GET /kennzahlen (Coverage %, Dopplungsquote, Kosten je Knoten, Gate-Runden); GET /befunde; WS /ws (dirty-Signale, 250 ms koalesziert). Frontend: App.vue (Tabs Graph/Guide/Kennzahlen, Drawer mobil), Graph.vue (Eigenbau nach R7), KnotenPanel.vue (Task-Liste + Befunde), Guide.vue (nach R10: 68ch, content-visibility, TOC + Scroll-Spy, print-CSS), Kennzahlen.vue, store.js, markdown.js (markdown-it html:false + anchor + DOMPurify + Shiki dual-theme, Sprachen php/twig/yaml/bash/sql/json/xml, KEIN KaTeX), api.js, style.css (Custom-Properties, dark default, Breakpoint 768px). Deps: vue ^3.5, vite ^8, markdown-it ^14.3, markdown-it-anchor, shiki ^4.3 + @shikijs/markdown-it, dompurify ^3.4, @panzoom/panzoom, @fontsource-variable/inter.
Konfiguration
.env: MINIMAX_API_KEY (aus creator2/.env kopieren), TAVILY_API_KEY?, BRAVE_API_KEY?, SEARXNG_URL?, KONTAKT_MAIL, RUN_BUDGET_TOKENS, MAX_PARALLEL_LLM, CREATOR_FAKE, CREATOR_DB. config.py-Kernwerte: ROLLEN alle MiniMax-M3 (extraktion/judge thinking=disabled temp 0.2/0.1; writer adaptive 0.7; max_tokens 32000); MAX_PARALLEL_LLM 12; INFRA 3×, Backoff 8/16/32+Jitter; DROSSEL_MIN_BREITE 4; HEDGE_NACH_S 90; RUN_BUDGET_TOKENS 10_000_000 (AN, Worst Case ~$24); RECHERCHE_LENSES generisch (ueberblick/tutorial/referenz/praxis); RECHERCHE_RUNDEN_MAX 4; SOLL_MIN_BELEGE 2; ABSCHNITT_CHARS 6000; READER 2; ANKER_OVERLAP 0.5; FUZZY 40/0.08; MERGE_PANEL 2; DEDUP_JACCARD 0.3; DEDUP_TITEL_FUZZY 85; PAARE/CALL 10; LUECKEN_RUNDEN 2; WEB_NACHRECHERCHE 1; BAUSTEIN 4-8; SECTION_WOERTER (40,220); PRUEF_PANEL 2; KLAERUNG 3; FIX_CAP 3; REDUNDANZ_JACCARD 0.6; STILLSTAND_N 2; GATE_RUNDEN_MAX je Gate. Python-Deps: fastapi, uvicorn, httpx, pyyaml, trafilatura, ftfy, ddgs, fuzzysearch, rapidfuzz, PyStemmer, pytest, pytest-asyncio.
Baureihenfolge (Meilensteine mit „fertig wenn")
- M1 Gerüst: config, db, skills, graph, engine + Mini-Testgraph. Fertig: test_engine/test_graph/test_skills grün + Mini-Fake-E2E inkl. Resume=0-Events und Stillstand→Pause.
- M2 LLM-Schicht: llm, ledger, panels, Fake-Hook. Fertig: test_llm grün (cap-kein-Retry, Drossel, Hedge-Verbuchung, Budget-Vorab-Stopp).
- M3 Suche+Laden: suche, laden, textkit. Fertig: Tests grün + EIN manueller Echt-Fetch (Wikipedia + Doku-Seite) mit intakten Code-Fences.
- M4 Korpus: bis gate_korpus im Fake grün (Zitat-Fehler blockiert Gate, Rücklauf mit neuen Lenses).
- M5 Inventar: bis gate_inventar grün (Dublette inkl. Akronym gemerged, Lücke via Web-Rücklauf geschlossen, Coverage 100 %).
- M6 Struktur+Guide+Montage: test_e2e komplett grün (Dopplung gefunden+gefixt, Marker einmalig, Resume 0 Events, Pause+Neustart ohne Duplikate).
- M7 API+Frontend: Fake-Lauf im Browser live verfolgbar; Guide mobil+Desktop lesbar.
- M8 Echtlauf-Härtung: kleines Thema, Budget aktiv, ohne Eingriff fertig; Mini-Deutsch-Eval (5 Sections); Konstanten nachjustieren.
Tests (tests/)
test_graph, test_engine (Claim atomar, Zombie-Reset, Barriere, atomare Folgetasks, Runde-im-Item), test_llm, test_skills, test_suche (Fallback, Circuit Breaker, degraded), test_laden (Normalisierung einmalig, robots, Roh-Archiv), test_dedup (Akronym-Kanal!, Subset-100-Falle, verwandt-Kante = 0 neue Calls), test_redundanz (gepflanzte Dopplung → Befund; selbes Kapitel → kein Befund), test_gates (Gate blockiert hart, Stillstand→Pause, Coverage-Formel), test_e2e.
Risiken
- MiniMax-Deutsch unbelegt → M8-Eval, Stil-Skills, Prüfer-Panel.
- trafilatura-Code-Fences → Roh-HTML-Archiv, M3-Checkpunkt.
- Harte Gates → mehr Pausen: gewollt; Ventile = Freispruch-Panels, 3er-Klärung, Stillstand-Fingerprint.
- Web-Nachrecherche bläht Kosten → Cap 1 Runde, gegroundete Queries, Budget-Vorab-Check.
- Eigenbau-Engine-Races → eine Writer-Connection, atomare Transaktionen, M1 testet zuerst; Notbremse: mehr Barrieren.
ANHANG: Recherche-Befunde (R1-R11)
Auftrag (Nutzer)
- Neubau creator3 unter /home/arbeit/projects/creator3, creator2 NUR Lesereferenz (kein Code übernehmen).
- Nur Themen-Modus (Websuche als Quelle); uni-Variante entfällt. Keine Artefakte (Flashcards/Fragen/Üben).
- LLM: NUR MiniMax per API (Anthropic-kompatibler Endpoint), Claude komplett raus.
- Kernziel: Guideumfang maximieren (Abdeckung nahe 100 %, Lücken minimieren), dann Guidelänge minimieren (Dopplungen minimieren). Guide gut lesbar, responsiv. Deutsch.
- Nutzer-Vision: Pipeline-System als Graph; Knoten haben genau EINE Aufgabe (erstellen, filtern, zusammenführen, prüfen …); Agenten bekommen Skill-Dateien (Rolle/Aufgabe/Stil) beim Start; Frontend = Graphensicht (Knoten mit Task-Zählern, Kanten) statt Kanban.
- Annahmen bestätigt: Suche „etwas Zuverlässiges" = Multi-Backend mit Fallback; Guides Deutsch; Token-Budget diesmal default AN; kein Eingreifen (Pause nur bei echten Fehlern); MiniMax-Key aus creator2/.env übernehmen; Stack Python+SQLite+Vue.
- Lektionen aus creator2-Analyse: Gates diesmal ÜBERALL hart (creator2 gated nur Korpus), Budget default an.
Recherche-Befunde (kondensiert)
R1: creator2 Frontend/API (fertig)
- Lauf-Start:
asyncio.ensure_future+_laeufe-Dict gegen Doppelstart (pipeline.py:43-52); kein Task-Framework. Übernehmen. - WS-Muster übernehmen: Hub mit EINER Queue + EIN Sender-Task (Ordering!),
call_soon_threadsafeaus DB-Schicht (ws.py:20-44). Frontend: Delta = nur Ping → debounced (400 ms) Full-State-Reload + Reconnect mit Cleanup-Flag +{typ:'verbunden'}-Resync + 5-s-Poll nur bei laufendem Run (App.vue:59-62, api.js:54-74). - Schwächen vermeiden: State-Monolith (alles bei jedem Reload) → creator3: kompakter Zähler-Snapshot, Task-Liste je Knoten lazy per Klick; WS-Payload tote Fracht → leeres dirty-Signal mit Topic-Filter; Rechtsklick-Menü mobil unbrauchbar → Klick-Panel;
on_event("startup")deprecated → Lifespan. - markdown.js-Muster übernehmen: marked+marked-highlight+hljs, DOMPurify über End-HTML, Render-Cache (600 ms/Scroll-Falle), Kapitel-Virtualisierung (nur aktiv ±1 im DOM). KaTeX-Ausheben nur falls Formeln (creator3: vermutlich weglassen, Themen sind nicht mathelastig — Entscheidung im Plan).
- Lesetypografie übernehmen: max-width 680px, 19px/1.55, Inter Variable, hyphens auto, Tabellen overflow-x, CSS-Custom-Properties-Themes (dark default), EIN Breakpoint 768px mit Drawer.
- Deps: vue ^3.5, vite ^8, marked ^18, marked-highlight, highlight.js ^11, dompurify ^3, @fontsource-variable/inter. Keine Graph-Lib im Bestand.
- API-Formen als Vorlage: POST /api/topics {name}, /start {budget}, /pause, /state-Snapshot, /guide (Kapitel-Struktur statt Roh-MD — 0,43 MB gespart), /kennzahlen. encodeURIComponent überall + Server-Namensvalidierung.
R2: Websuche-Backends (fertig)
- Empfehlung: Wikipedia de+en immer parallel (Grundstock, offiziell, quasi nie down) + Fallback-Kette für Websuche: Keyed-API (falls Key) → ddgs → weiter-ohne-Crash. KEIN Voll-Fan-out (verbrennt Quota ohne Mehrwert).
- Wikipedia: Suche
https://de.wikipedia.org/w/rest.php/v1/search/page?q=…&limit=10; Extrakte via Action-APIprop=extracts&explaintext=1(stabilste Variante, RESTBase-summary unsicher). Pflicht: aussagekräftiger User-Agent mit Kontakt; mit UA 200 req/min, max 3 parallel, Retry-After respektieren. - ddgs ≥9.14 (Paket umbenannt von duckduckgo-search!): heute Metasearch-Aggregator (
backend="auto", Bing/Brave/DDG/Google/Mojeek…),DDGS().text(q, region="de-de", max_results=10). Schwächen: Exceptions untypisiert (#478), auto-Modus kann Ergebnisse verlieren (#427). Grauzone (DDG-ToS) → Best-Effort-Schicht, 1-2 s Pause zwischen Queries. - Brave API: Free-Tier abgeschafft (Feb 2026) → $5 Gratis-Guthaben/Monat ≈ 1000 Queries, Kreditkarte Pflicht, Überschreitung wird berechnet. Optional via BRAVE_API_KEY.
- Tavily: 1000 Credits/Monat OHNE Karte — bevorzugte Optional-Key-Alternative (TAVILY_API_KEY).
- Muster: Timeout ~10 s/Query, 1 Retry, dann nächstes Backend; Circuit Breaker (Backend n× tot → Rest des Laufs überspringen); URL-Dedupe normalisiert (Tracking-Parameter, Trailing Slash); Lauf scheitert nur, wenn ALLE Backends ausfallen, sonst degraded mit Befund.
- SearxNG: nur als optionales Self-Host-Backend (SEARXNG_URL), öffentliche Instanzen unzuverlässig.
R3: creator2 LLM-Disziplin (fertig)
call()-Zustandsmaschine übernehmen: Infra (3 Retries, Backoff 8/16/32 s → LaufPause fail-closed) vs. Inhalt (2 Restarts → None) getrennt; Status ok/parse/cap/infra/error/pause.cap(stop=max_tokens) = KEIN Retry (deterministisch, Aufrufer halbiert Chunk). 1-Element-Dict zählt als Liste (Modelle lassen Klammern weg).- Globale 429-Drossel: ein 429 drosselt ALLE (Cooldown global, Breite halbieren min 4, 20 Erfolge = +1); retry-after respektiert (min 60 s). Start-Breite 28.
- Ledger: events-Schema übernehmen (run_id, ebene, stage, item, template, template_hash, model, role, status, dur_ms, wait_ms, tok_in/out/cache, meta); Log im finally JEDES Versuchs; Hedge-Verlierer als eigene Zeile (hedge/hedge_cancel). Budget: Prüfung VOR und nach jedem Call; creator3: Default AN.
- Panel-Primitive:
einstimmig(volles Panel, None = keine Stimme = kein Entscheid),panelmit Ersatz-Richter bei genau einem Ausfall; Größen: Merge 2 einstimmig, Verify 2, Klärung 3 (Mehrheit). - MiniMax
_text_api: Endpoint api.minimax.io/anthropic/v1/messages, x-api-key + anthropic-version; max_tokens 32k; thinking DISABLED für Judges/Extraktion (sonst 8-32k Denk-Tokens, 44-min-Stalls), thinking AN nur für Writer; temperature 0.2-0.3; leere Antwort → Fehler MIT Tokens (stop_reason durchreichen für cap-Erkennung); httpx Timeout + wait_for; Rollen: judge/guide=MiniMax-M3, quick/fast=M2.7-highspeed (kalt-Route stallte 20 % der Judges → native Route für Judges). - Hedge BEHALTEN (API-Stalls belegt): Zwilling nach max(90 s, timeout/2), erster valider gewinnt, Verlierer verbuchen, HEDGE_NACH_S=0 schaltbar.
- Entfällt ohne CLI: RAM-Gate, Token-Bucket, killpg/_spawn, _opencode/_claude_cli, _batch_sem, caps files/full, Provider-Stacks claude/lokal, „database is locked"-Marker.
- Bleibt: AgentErgebnis, Pause/Abbruch-Flags, _active, Fake-Hook VOR allem außer Pause-Check, Semaphor + getrennte wait_ms-Messung.
- LEKTIONEN-Triage: übernehmen 1-6, 10, 12-37, 40, 42-49, 52-83 + 4 Meta-Regeln; entfällt 7-9, 11, 38-39, 41, 51; Lektion 50 = Gründungsentscheidung.
- Konsequenz API-only: Recherche (caps=full) MUSS als eigener Nicht-LLM-Baustein gebaut werden (Suche+Download im Code, LLM destilliert nur).
R4: creator2 Pipeline/QA (fertig)
- Orchestrierung übernehmen: EBENEN-Kette seriell, Status-Marker nie zurückdrehen,
_fertig_ab-Skip, Resume = 2. Lauf auf fertigem Topic → 0 neue Calls (bewiesen in test_e2e). Runs-Status running/done/stopped/paused/budget/failed. - QA: Note-Formel (10.0 nur bei 0 Befunden; Gewichte kritisch 3.0/mittel 1.5/stil 0.5, Basis = geprüfte Items); Befund-Persistenz mit Schlüssel (art,item,detail[:500]); auto_repair_loop (fertig/stillstand/limit, max 10 Iter).
- KRITISCH für creator3: Gate-Aufrufmuster existiert generisch (pipeline ruft
modul.gate(ctx), RuntimeError blockiert Marker), aber NUR korpus definiert eins. creator3: gate() je Ebene VERPFLICHTEND; bei stillstand/limit mit offenen kritischen Befunden → PAUSE statt Weiterlauf. - Korpus-Themen-Modus komplett übernehmen: Runden bis Sättigung (Runde ohne neuen bestätigten Punkt), Zitat-Gate (finde_zitat gegen Quelle), Konsens: Judge gruppiert nur, CODE zählt ≥2 unabhängige Quellen; URL- und Content-Hash-Dedup; Beleg-Nachsuche gebündelt (1 Call je Quelle statt Punkt×Quelle); geprueft-Negativ-Cache; Snapshots: ftfy NACH Steuerzeichen-Ersatz, sha256[:16]-Dateiname.
- Inventar (ausgereiftester Teil) komplett: 6k-Abschnitte (12k riss 32k-Cap), 2 Reader, Cap → Chunk halbieren (Tiefe 2); Anker 5-Stufen-Matching (exakt→casefold→alnum→fuzzy 8 %→Wort-Alignment) mit Negations-/Eindeutigkeits-Guards; unverankert = start=-1 → erst det. Re-Match, dann LLM-Batch-Fix, verwerfen nur ohne Call-Ausfall; Dedup-Kette: Anker-Overlap ≥0.5 auto → Kandidaten (Embedding cos 0.65 / Jaccard 0.3 ∪ Titel-Kern) mit Negations-Gleichheit → 2er-Panel einstimmig; verwandt-Kanten = nie zweimal Tokens; Lücke: ±6k-Fenster-Nachextraktion, nach 2 Runden Freispruch nur per einstimmigem Panel.
- Struktur: Themen-Schnitt mit exakter Partitions-Validierung + √n-Fallback; Lernziele 2 Runden + Sammelziel; Band 4-8 mit Merge nur im Thema/Split entlang Quellreihenfolge; Ordnung: Median-Anker-Rang als Prior, Judge-Permutation exakt validiert, Fallback = Prior mit Befund.
- Guide: Stages writer→pruefer→fix→done in sections.stage; DELIMITED (===LANG===/===KOMPAKT===, JSON scheiterte an LaTeX-Backslashes 34 %); Fix-Cap 3 mit Freeze nur für Stil; Auftrags-System (2er-Panel, Klärung 3er einmalig, Eskalation); LLM-QA fail-closed mit qa_hash; det-Checks (Marker fehlend/fremd, Länge ×1.25-Band, Vorwärtsverweise, Stil).
- WICHTIG Dopplungs-Ziel: creator2 garantiert nur Marker-Einmaligkeit, KEINEN inhaltlichen Redundanz-Check. Für creator3-Kernziel „Länge minimieren" NEU bauen: Rückwärts-Redundanz-Check (analog Vorwärtsverweis-Detektor) + „Verweis statt Wiederholung"-Regel im Writer-Skill.
- Fake-System komplett übernehmen: Template-Marker
<!-- template:Name -->, Handler-Registry, harter Fehler ohne Handler, Fakes LESEN Prompt (Zitat-Gate mitgetestet), In-Memory-DB, Resume-Test 0 neue Events. - Schema übernehmen minus: artefakte, leitner, lernstand, topics.art, quellen.art='datei', kanten art='braucht'. textkit.py-Funktionsumfang 1:1 nachbauen (norm, finde_zitat 5 Stufen, titel_kern, abschnitte, ueberlappung, negations_menge). prompts.py-Muster (Datei + sha256-Hash) und belege.py (Fenster −300/+1200) übernehmen.
R5: Skill-Datei-Format (fertig)
- Format:
skills/{rolle,aufgabe,stil}/name.md, YAML-Frontmatter minimal: name, art (rolle|aufgabe|stil), beschreibung, optional platzhalter (Validierung vor Call). KEIN version-Feld (Hash ist die Version), kein model/tools. - Komposition statisch erzwungen: genau 1 Rolle + 1 Aufgabe + 0..n Stil; NUR die Aufgabe definiert das Ausgabeformat (eliminiert Konfliktklasse per Konstruktion — Modelle arbitrieren Konflikte nur ~64 % korrekt); Platzhalter
{{name}}nur im Aufgabe-Body, simples str.replace statt format_map (creator2 muss deshalb JSON-Klammern doppeln). - Reihenfolge: Rolle → Stil-Regeln → Aufgabe (Input mittig, Ausgabeformat als LETZTER Block — creator2-bewährt).
- „Curse of Instructions": Erfolg fällt ≈ p^n mit Regelzahl → Budget: zusammengesetzter Prompt ohne Input ≤ ~1000 Tokens, ≤ ~8-10 verifizierbare Regeln. Wichtig für MiniMax-Klasse.
- Rollen-Theater bringt nichts (belegt) — Rolle nur, wenn sie konkrete Verhaltensregeln trägt („nichts erfinden, nur Belegbares").
- Hashing zweistufig: hash_i = sha256(Skill-Rohbytes)[:12] je Datei; skill_hash = sha256(name:hash_i-Liste in Reihenfolge)[:12] → ins Ledger (kompatibel zum template_hash-Muster); Einzelliste zusätzlich ins Log.
- DELIMITED-Ausgabeformat (===ATOM===-Blöcke) statt JSON — robuster für Mittelklasse-Modelle, kein Escaping.
R6: HTML-Extraktion (fertig)
- Stack: trafilatura ≥2.1.0 mit
output_format="markdown", include_tables=True, include_links=False, include_images=False— einziger gewarteter Boilerplate-Entferner mit Markdown-Code-Fences + Tabellen (wichtig für Symfony-Doku); Benchmark-Spitzenklasse unter Python-Tools. Roh-HTML-Bytes IMMER mit archivieren (Re-Extraktion ohne Re-Fetch). - Fetch: httpx mit
follow_redirects=True(NICHT allow_redirects), explizite Timeouts (connect 10/read 30), ehrlicher UAcreator3/0.1 (+mailto:…), Transport-Retries(2) + Backoff für 429/5xx mit Retry-After; Bytes an trafilatura geben, nie response.text (Encoding-Fehlerklasse eliminiert); robots.txt via urllib.robotparser, pro Domain gecacht, Disallow → skip + Befund; ~1 req/s/Domain. - Anker-Stabilität (Architekturregel): Normalisierung GENAU EINMAL bei Ingestion VOR Snapshot-Write: Bytes → trafilatura → ftfy 6.3.1 (NFC, NIE NFKC — verfälscht Zitate) → Snapshot unveränderlich; Zitate nur aus Snapshot; tolerante Normalisierung nur als Lesetransformation beim Vergleich.
- JS-Rendering: YAGNI v1. Detektor: leerer/kurzer Extrakt oder „enable JavaScript" → Befund „braucht Rendering, übersprungen".
- PDF: v1 überspringen mit Befund. Falls später: pypdf (BSD); pymupdf ist AGPL (bewusste Entscheidung nötig).
- Unsicher: Code-Block-Qualität von trafilatura 2.1.0 auf echten Symfony-Seiten → beim ersten Echtlauf prüfen.
R7: Graph-Frontend (fertig)
- Empfehlung: hand-gerolltes Hybrid-Rendering — absolut positionierte HTML-Divs für Knoten (Badges = normales Vue-Templating) + eine SVG-Ebene nur für Kanten (kubische Bezier,
<marker>-Pfeilspitzen), beide im selben transformierten Container. KEINE Graph-Bibliothek (kein Editor, festes Layout, ~20 Knoten). - Layout vorberechnet aus YAML-Stufen: Spalte = Stufe, Zeile = Index (~40 Zeilen computed). Mobil: x/y-Tausch → vertikales Layout per Breakpoint (Orientierung wechseln statt schrumpfen).
- Rückwärts-Kanten (Reparatur/Lücken-Loops): feste Offsets pro Loop-Kante, außen herumgeführt — kontrollierter als Auto-Layout.
- Zoom/Pan: Micro-Lib anvaka/panzoom ODER @panzoom/panzoom (~20 Zeilen Anbindung); initial fitView, Pan als Primärgeste,
touch-action: nonenur auf Container (Page-Scroll nicht kapern). - Live-Zähler: EIN WS-Handler schreibt in zentralen reactive Store
counts[knotenId] = {offen, laufend, fertig, fehler}; Badges lesen daraus (nur Textknoten-Patches); Graphstruktur als shallowRef/statisch; Events auf ~250 ms koalesziert; Initial-Snapshot + Resync nach Reconnect. - Klick/Tap → Seitenpanel mit Task-Liste (lazy geladen) — richtiges Touch-Muster, keine Hover-Tooltips.
- Fallback falls Eigenbau hakt: @vue-flow/core 1.48.2 (51 kB gzip), Positionen trotzdem selbst, draggable/connectable=false. Nicht: mermaid (Full-Re-Render), cytoscape/G6/elkjs (überdimensioniert), dagre-d3 (tot).
R8: Task-Engine (fertig)
- Keine fertige Lib passt (Celery/Dramatiq/Huey/APScheduler/Prefect/LangGraph = Broker/Overkill/falsches Modell). Selbst bauen, ~300-500 Zeilen Engine-Kern. Vorbilder: litequeue-Schema (Task=Zeile, Claim=UPDATE), DBOS/Morling-Checkpoint-Muster, Dagu-YAML, graphlib (stdlib) NUR zur statischen DAG-Validierung beim Config-Laden.
- Scheduling: Hybrid — DB ist die einzige Wahrheit/Queue; Scheduler-Loop blockiert auf asyncio.Event („Wecker", gesetzt bei jedem Task-Abschluss/-Erzeugung) + 2-s-Fallback-Poll. Resume = derselbe Codepfad (Loop starten, DB lesen). Beim Start: Zombie-Reset
laufend → offen(ein Prozess ⇒ kein Lease/Heartbeat nötig). - SQLite: WAL, synchronous=NORMAL, busy_timeout 5000, EINE Writer-Connection (eliminiert locked-Probleme architektonisch); Claim =
UPDATE … WHERE id=? AND status='offen', rowcount==1; LLM-Calls NIE in Transaktion. - Idempotenz: tasks UNIQUE(topic, knoten, item) + INSERT OR IGNORE; Reparatur-RUNDE gehört in den Item-Schlüssel (
r2:atom:14), sonst blockiert UNIQUE die 2. Runde. Atomar in EINER Transaktion: Ergebnis-Zeilen + status='fertig' + Folge-Task-Erzeugung (kein falsch interpretierbarer Zwischenzustand). Datei-Schreiben: Temp + os.replace. - Zyklen: Normal-Kanten = DAG (graphlib-Check); Rück-Kanten (reparatur|ruecklauf) nur von Gates, stufen-abwärts, sind Task-Erzeugungs-Rezepte (kein Laufzeit-Zyklus). Dreifacher Schutz: max_versuche je Task, max_runden je Gate, Stillstand-Fingerprint (SHA-256 über sortierte Befundmenge; 2× identisch → PAUSE).
- v1: Stufen-seriell mit frei fließendem Graph INNERHALB der Stufe (Gates brauchen Quiesce-Punkt; creator2 beweist, dass das reicht). Barriere-Knoten (
barriere: true) für globale Schritte (Dedup, Konsens, Ordnung). - Gate = Knoten-Typ mit deterministischer Registry-Funktion; Trigger: Stufe leer gelaufen → INSERT OR IGNORE Gate-Task
runde:N(einmal pro Runde); rot → in EINER Transaktion Befunde + Fingerprint + Reparatur-Tasks (Runde+1, gecappt); Stillstand/Cap → runs.status='paused' mit Grund (fail-closed), UI bleibt erreichbar. - Task-Schema: tasks(id, run_id, topic, knoten, item, status offen|laufend|fertig|fehler, art, versuch, runde, payload, ergebnis, fehler, erzeugt_von, gestartet, beendet, UNIQUE(topic,knoten,item)) + gate_laeufe(run_id, knoten, runde, fingerprint, befunde, UNIQUE(run_id,knoten,runde)).
- YAML-Schema: global{max_parallel, poll_sekunden}; knoten[{id, typ system|llm|gate, worker, skills[], stufe, max_parallel, max_versuche, barriere}]; kanten[{von, nach, art normal|reparatur|ruecklauf}]. Validierung: Worker in Registry, Skill-Dateien existieren, ein Gate pro Stufe (v1).
- Infra vs. Inhalt getrennt (Lektionen 1/2/68): Infra zählt nicht gegen max_versuche, eigenes Retry-Budget → PAUSE.
R9: MiniMax-API (fertig)
- Endpoint: Anthropic-kompatibel
https://api.minimax.io/anthropic/v1/messages(offiziell empfohlen; x-api-key wie creator2). Plus/anthropic/v1/messages/count_tokens. ACHTUNG:stop_sequences/top_kwerden IGNORIERT → Delimiter im Prompt erzwingen, nicht per stop. - Modelle Mitte 2026: MiniMax-M3 (1M Kontext, thinking
adaptive|disabled— „enabled" gibt 400; einziges Modell mit wirklich abschaltbarem Thinking), M2.7/M2.7-highspeed (Thinking NICHT abschaltbar — erklärt creator2s Denk-Token-Problem!). - Rollen-Empfehlung: Extraktion + Judge = M3 mit thinking disabled, temp 0.1-0.3; Writer = M3 mit thinking adaptive, temp ~0.7, max_tokens 32000. thinking IMMER explizit setzen (Default widersprüchlich dokumentiert).
- Rate Limits: M3 200 RPM / 10M TPM; M2.x 500 RPM / 20M TPM. Rate-Limit-Fehler auch als Statuscode 1002/1041/2045 (nicht nur HTTP 429). Retry-After NICHT verlässlich → eigenes exponentielles Backoff mit Jitter. Nebenläufigkeit: 8-16 Worker sicher.
- Kein response_format/JSON-Mode bei M2.x/M3 (wird still ignoriert) → DELIMITED + eigener Parser bestätigt alternativlos.
- Prompt-Caching: passiv automatisch ab 512 Input-Tokens (Prefix-Match, keine Write-Gebühr) → stabile Prompt-Präfixe (Rolle+Stil vorn) lohnen sich; usage-Felder cache_read/cache_creation wie creator2 parsen.
- Preis M3: konservativ $0.60/$2.40 pro 1M in/out ansetzen (Quellen widersprüchlich, real messen).
- Embeddings: MiniMax embo-01 ist CN-/Legacy, auf api.minimax.io UNSICHER → für Dedup NICHT auf MiniMax-Embeddings bauen; lexikalisch (Jaccard) + optional lokales Modell.
- Deutsch-Qualität: keine belastbare Quellenlage → kleinen eigenen Deutsch-Eval beim ersten Echtlauf einplanen.
R10: Guide-Rendering/Lesbarkeit (fertig)
- Stack-Wechsel ggü. creator2: markdown-it@14.3 (html:false) statt marked (sicher by default, CommonMark, Plugins) + markdown-it-anchor (Heading-IDs) + dompurify@3.4 vor jedem v-html + Shiki 4.3 statt highlight.js (fine-grained: shiki/core + JS-RegExp-Engine ohne WASM, nur benötigte Sprachen php/twig/yaml/bash/sql/json/xml; Dual Themes light/dark per CSS-Variablen; einmal async initialisieren, dann synchron via @shikijs/markdown-it/core).
- Lange Guides: KEINE Virtualisierung nötig (<2 MB); Kapitel-
<section>s mitcontent-visibility: auto+contain-intrinsic-size: auto 1200px(~80 % Render-Ersparnis); HTML einmal rendern und cachen. - CSS-Kern: Prose max-width 68ch zentriert; font-size clamp(1rem…1.125rem), line-height 1.65; Überschriften 1.25, mehr Luft oben;
scroll-margin-top: 5remauf h2/h3 (Sticky-Header!); Tabellen in.table-wrap{overflow-x:auto}+ td max-width 40ch; pre overflow-x, 0.875em; Dark Mode über Custom Properties + data-theme; @media print (nav aus, break-inside avoid auf pre/table). - TOC: markdown-it-anchor-Slugs → Headings (H2-H3) aus Tokens extrahieren → Sidebar-TOC (Desktop sticky, mobil Drawer) + IntersectionObserver-Scroll-Spy (~30 Zeilen). Kein Einklappen (bricht Anker + Suche), kein Lesefortschritts-Balken (Deko).
- Writer-Skill-Regeln (aus Instructional Design): 1 Konzept pro H3; Verweis statt Wiederholung = Ein-Satz-Refresher + Anker-Link („Wie in Kapitel 3: X gezeigt, … — hier für Y"); eindeutige Überschriften (Slug-Kollisionen); H2-Einstieg 2-3 Sätze (was/warum/worauf baut es); Beispiel direkt nach Konzept (max ~5 Sätze Erklärung); H2-Kapitel ~1000-2000 Wörter; Tabellen max 3-4 Spalten; Kapitel-Abschluss 3-5 Punkte ohne fremde Inhalte zu wiederholen.
R11: Dedup + Abdeckung (fertig)
- Grundsatz: deterministisch vor LLM; Ähnlichkeit liefert NUR Kandidaten (Floors niedrig, recall-orientiert), Panel = Präzisions-Gate; Merge nur einstimmig; geprüfte Paare als „verwandt"-Kante persistieren.
- KEIN sentence-transformers in v1. Kandidaten-Kanäle (Union): Anker-Overlap ≥0.5 (einziger Auto-Merge); Titel-Kern exakt + gestemmt; Token-Jaccard (Snowball-German-gestemmt via PyStemmer) auf Definitionen ≥0.3 (gemessener Paraphrasen-Floor); rapidfuzz token_set_ratio auf Titeln ≥85 NUR bei ≥2 Inhaltstoken (Subset-100-Falle; Schwelle kalibrieren); Akronym-Kanal ALL-CAPS ↔ Anfangsbuchstaben (Lektion 28 — in creator2 FEHLEND); Gleicher-Soll-Punkt-Kanal (alle Atompaare am selben Soll-Punkt — deckt Synonym-Lücke billig ab).
- Guards vor Panel: Negationsmengen-Gleichheit, Kandidaten-Cap, ~10 Paare/Call. Backstop falls Dubletten überleben: model2vec potion-multilingual-128M (MIT, 256-dim, kein torch, cos 0.65). MiniMax embo-01 unsicher — nicht drauf bauen.
- Abdeckung = Nugget-Recall-Prinzip (wissenschaftlich bestätigt, TREC RAG): Coverage % = bestätigte Soll-Punkte mit ≥1 Atom UND ≥1 Baustein / alle bestätigten — deterministisch gezählt, NIE LLM-geschätzt. Checklisten-Recall trennschärfer als Likert-Judges (Deckeneffekte).
- Lückensuche: Suchanfragen aus Punkt-Text + Beleg-Zitat GROUNDEN (freie LLM-Query-Expansion halluziniert bei unbekannten Themen), 2-3 Varianten; Runden-Cap 2, dann Panel-Freispruch persistiert.
- Sättigung schärfer als creator2: Nullrunde zählt nur mit NEUEN Query-Winkeln/Lenses; Cap 3-4 Runden.
- Dopplungs-QA im Text (klein, <100 Zeilen): Satz-Split → norm() → Jaccard aller Satzpaare AUS VERSCHIEDENEN KAPITELN ≥~0.6 → Befund → 2er-Panel „redundant vs. didaktisch gewollt" (Recap ist keine Dopplung). Prävention bleibt Hauptinstrument: exakte Soll-Partition + „bekannt"-Kontext im Writer + inhaltsfreie Intros.