1427 lines
50 KiB
HTML
1427 lines
50 KiB
HTML
<!doctype html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Planer-Vorschau: Creator (Teilscan)</title>
|
||
<style>
|
||
:root {
|
||
--bg: #f6f7f9; --karte: #ffffff; --text: #1a202c; --dezent: #64748b;
|
||
--rand: #e2e8f0; --akzent: #2563eb; --kern: #166534; --kern-bg: #dcfce7;
|
||
--rand-flag: #92400e; --rand-bg: #fef3c7; --code-bg: #f1f5f9;
|
||
}
|
||
@media (prefers-color-scheme: dark) {
|
||
:root {
|
||
--bg: #0f1420; --karte: #1a2130; --text: #e2e8f0; --dezent: #94a3b8;
|
||
--rand: #2d3748; --akzent: #60a5fa; --kern: #86efac; --kern-bg: #14532d;
|
||
--rand-flag: #fcd34d; --rand-bg: #451a03; --code-bg: #111827;
|
||
}
|
||
}
|
||
* { box-sizing: border-box; }
|
||
body { margin: 0; padding: 2rem 1rem 4rem; background: var(--bg); color: var(--text);
|
||
font: 15px/1.55 system-ui, sans-serif; }
|
||
main { max-width: 860px; margin: 0 auto; }
|
||
h1 { font-size: 1.4rem; margin: 0 0 .3rem; }
|
||
.untertitel { color: var(--dezent); margin: 0 0 2rem; font-size: .92rem; }
|
||
.bereich { background: var(--karte); border: 1px solid var(--rand); border-radius: 10px;
|
||
padding: 1rem 1.2rem; margin-bottom: 1rem; }
|
||
.bereich > h2 { font-size: 1.08rem; margin: 0; }
|
||
.bereich > p { color: var(--dezent); margin: .25rem 0 .8rem; font-size: .92rem; }
|
||
details { border-top: 1px solid var(--rand); }
|
||
summary { cursor: pointer; padding: .55rem .2rem; list-style: none; display: flex;
|
||
align-items: baseline; gap: .5rem; }
|
||
summary::before { content: "▸"; color: var(--dezent); font-size: .8em; transition: transform .12s; }
|
||
details[open] > summary::before { transform: rotate(90deg); }
|
||
summary:hover { color: var(--akzent); }
|
||
.flag { font-size: .7rem; font-weight: 600; padding: .1rem .45rem; border-radius: 99px;
|
||
text-transform: uppercase; letter-spacing: .04em; }
|
||
.flag.kern { color: var(--kern); background: var(--kern-bg); }
|
||
.flag.rand { color: var(--rand-flag); background: var(--rand-bg); }
|
||
.feature-besch { color: var(--dezent); font-size: .9rem; margin: 0 0 .4rem 1.15rem; }
|
||
.teil { margin: 0 0 .35rem 1.15rem; }
|
||
.teil > summary { font-size: .93rem; padding: .3rem .2rem; }
|
||
.einheit { margin: .3rem 0 .6rem 1.15rem; border: 1px solid var(--rand); border-radius: 8px;
|
||
background: var(--bg); }
|
||
.einheit > summary { font-family: ui-monospace, monospace; font-size: .82rem; padding: .45rem .6rem; }
|
||
.einheit-inhalt { padding: .2rem .8rem .7rem; font-size: .9rem; }
|
||
.einheit-inhalt p { margin: .3rem 0; }
|
||
.fakt { margin: .45rem 0; }
|
||
.beleg { display: block; font-family: ui-monospace, monospace; font-size: .78rem;
|
||
background: var(--code-bg); border-left: 3px solid var(--akzent);
|
||
padding: .25rem .5rem; margin-top: .2rem; border-radius: 0 4px 4px 0;
|
||
overflow-x: auto; white-space: pre; }
|
||
.kanten { color: var(--dezent); font-size: .82rem; margin-top: .5rem; }
|
||
.kanten code { font-size: .78rem; }
|
||
.hinweis { color: var(--dezent); font-size: .85rem; margin-top: 2rem;
|
||
border-top: 1px solid var(--rand); padding-top: 1rem; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<main>
|
||
<h1>Creator — Projektansicht (Phase-0-Vorschau)</h1>
|
||
<p class="untertitel">Teilscan: kanban.py, pipeline.py, config.py · alle Belege mechanisch
|
||
verifiziert · so ähnlich würde die Projektübersicht im Planer aussehen</p>
|
||
<div id="wurzel"></div>
|
||
<p class="hinweis">Wegwerf-Vorschau aus Phase 0 — kein Frontend-Code des Planers.
|
||
Aufklapp-Logik: Bereich → Feature → Teilfeature → Einheiten (Fakten mit Code-Beleg, Kanten).</p>
|
||
</main>
|
||
<script>
|
||
const DATEN = {
|
||
"bereiche": [
|
||
{
|
||
"name": "Kanban-Engine (kartenbasierte Themenverarbeitung)",
|
||
"beschreibung": "Verteilt einen Themen-Run als Kanban-Board über mehrere Stages und zieht Karten worker-gesteuert durch die Verarbeitung.",
|
||
"features": [
|
||
{
|
||
"titel": "Themen-Run starten und überwachen",
|
||
"flag": "kern",
|
||
"beschreibung": "Initialisiert pro Topic den Laufzeit-Zustand, fährt Worker pro Stage hoch und meldet den Live-Fortschritt der Karten im Fluss.",
|
||
"teilfeatures": [
|
||
{
|
||
"titel": "Kann für ein Topic einen Lauf starten, Worker hochfahren und live registrieren",
|
||
"einheiten": [
|
||
"backend/kanban.py::run_flow"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann den Laufzeit-Zustand pro Topic (aktive Worker, Producer, Wachsignal) führen",
|
||
"einheiten": [
|
||
"backend/kanban.py::Flow"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann sekündlich die Gesamtzahl der Karten im Fluss als deutschen Statustext melden",
|
||
"einheiten": [
|
||
"backend/kanban.py::_progress"
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"titel": "Board und Stages modellieren",
|
||
"flag": "kern",
|
||
"beschreibung": "Bildet die Spalten eines Kanban-Boards mit Verarbeitung und Verhaltens-Flags ab und verknüpft Vorgänger-Stages automatisch.",
|
||
"teilfeatures": [
|
||
{
|
||
"titel": "Kann eine Kanban-Spalte mit zugeordneter Verarbeitung und Verhaltens-Flags (Serial, Drain, Barrier, Gate) beschreiben",
|
||
"einheiten": [
|
||
"backend/kanban.py::Stage"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann Vorgänger-Spalten automatisch verketten, sodass Barrier-Worker ihre Quellen kennen",
|
||
"einheiten": [
|
||
"backend/kanban.py::chain_stages"
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"titel": "Karten worker-gesteuert abarbeiten",
|
||
"flag": "kern",
|
||
"beschreibung": "Pullt Karten aus der Queue der eigenen Stage, hält mehrere Pakete gleichzeitig und wartet reaktiv auf neue Arbeit.",
|
||
"teilfeatures": [
|
||
{
|
||
"titel": "Kann Karten pullen, mehrere Pakete gleichzeitig halten und beim Stopp alle laufenden Tasks sauber abbrechen",
|
||
"einheiten": [
|
||
"backend/kanban.py::_worker"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann reaktiv auf neue Arbeit warten, statt dauerhaft zu pollen",
|
||
"einheiten": [
|
||
"backend/kanban.py::_sleep_wake"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann Ruhe über aktive Worker und Queue-Bestand zuverlässig erkennen und zum Beenden auflösen",
|
||
"einheiten": [
|
||
"backend/kanban.py::quiescent"
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"titel": "Fehler im Karten-Lauf behandeln",
|
||
"flag": "kern",
|
||
"beschreibung": "Wiederholt fehlgeschlagene Karten mit exponentiellem Backoff und verschiebt erschöpfte Karten in die Dead-Letter-Stage.",
|
||
"teilfeatures": [
|
||
{
|
||
"titel": "Kann fehlgeschlagene Karten mit Backoff wiederholen oder als Dead-Letter ablegen, ohne bereits weitergerückte Karten zu bestrafen",
|
||
"einheiten": [
|
||
"backend/kanban.py::_fail_package"
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"name": "Pipeline-Steuerung (Guide-Laufzeitverwaltung)",
|
||
"beschreibung": "Orchestriert parallele Agent-Aufrufe für einen Guide, verwaltet Stornierung und Fortschritt und stellt Helfer für Prompt-Bau sowie Fehlerformatierung bereit.",
|
||
"features": [
|
||
{
|
||
"titel": "Guide-Abbruch handhaben",
|
||
"flag": "kern",
|
||
"beschreibung": "Markiert einen Guide als abgebrochen, beendet laufende Agent-Slots und gibt die Sperre für einen Neustart wieder frei.",
|
||
"teilfeatures": [
|
||
{
|
||
"titel": "Kann einen laufenden Guide abbrechen, zugehörige Agent-Slots stoppen und den Datensatz als fehlgeschlagen markieren",
|
||
"einheiten": [
|
||
"backend/pipeline.py::cancel_guide"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann den Stornierungsstatus eines Guides abfragen",
|
||
"einheiten": [
|
||
"backend/pipeline.py::is_guide_cancelled"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann nach einem Abbruch die Sperre lösen, damit der Guide neu gestartet werden kann",
|
||
"einheiten": [
|
||
"backend/pipeline.py::clear_guide_cancelled"
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"titel": "Live-Fortschritt und Statusmeldungen",
|
||
"flag": "kern",
|
||
"beschreibung": "Schreibt Fortschritt, Schrittnummer und Fehler persistiert und meldet nebenläufige Jobs mit Live-Zähler an einen Reporter.",
|
||
"teilfeatures": [
|
||
{
|
||
"titel": "Kann Fortschritt, Schrittnummer und Fehlermeldung im Guide-Datensatz persistieren",
|
||
"einheiten": [
|
||
"backend/pipeline.py::_set_progress",
|
||
"backend/pipeline.py::_set_step",
|
||
"backend/pipeline.py::_fail"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann nebenläufige Jobs mit Live-Zähler an einen Reporter melden und auch bei Ausnahmen weiterzählen",
|
||
"einheiten": [
|
||
"backend/pipeline.py::_gather_progress"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann Info-Meldungen mit Topic-Präfix einheitlich loggen",
|
||
"einheiten": [
|
||
"backend/pipeline.py::_log"
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"titel": "Parallele Agent-Slots orchestrieren",
|
||
"flag": "kern",
|
||
"beschreibung": "Startet mehrere Agent-Slots parallel, sammelt bis zum Quorum gültige Ergebnisse und reagiert auf Stornierung und Infrastrukturfehler.",
|
||
"teilfeatures": [
|
||
{
|
||
"titel": "Kann mehrere Agent-Slots parallel starten und auf eine konfigurierbare Quorum-Anzahl gültiger Ergebnisse warten, mit Hedging, Grace-Periode und Infrastruktur-Retries",
|
||
"einheiten": [
|
||
"backend/pipeline.py::_race"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann Pipeline-Parameter einmal bündeln und durch alle Pipeline-Glieder reichen",
|
||
"einheiten": [
|
||
"backend/pipeline.py::GenContext"
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"titel": "Fehler klassifizieren und formatieren",
|
||
"flag": "rand",
|
||
"beschreibung": "Erkennt Infrastruktur- von inhaltlichen Fehlern und baut kompakte Fehlertexte für Anzeige und Vergleich.",
|
||
"teilfeatures": [
|
||
{
|
||
"titel": "Kann Infrastruktur- von inhaltlichen Fehlern anhand einer Markerliste unterscheiden",
|
||
"einheiten": [
|
||
"backend/pipeline.py::_is_infra"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann Agent-Fehler aus Returncode und Ausgaben zu einer kompakten Meldung formatieren",
|
||
"einheiten": [
|
||
"backend/pipeline.py::_claude_error"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann aus einer Liste von Slot-Ergebnissen den ersten verwertbaren Fehler auswählen und melden",
|
||
"einheiten": [
|
||
"backend/pipeline.py::_gather_error"
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"titel": "Prompt-Bau und Schemavalidierung",
|
||
"flag": "rand",
|
||
"beschreibung": "Stellt Helfer bereit, um Prompts aus Vorlagen aufzubauen, Timeouts zu berechnen und Agent-Antworten zu validieren.",
|
||
"teilfeatures": [
|
||
{
|
||
"titel": "Kann Prompt-Vorlagen aus dem Templates-Verzeichnis laden und mit Argumenten ausfüllen",
|
||
"einheiten": [
|
||
"backend/pipeline.py::_prompt"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann zusätzliche Nutzeranweisungen als klar markierten Block an einen Prompt anhängen",
|
||
"einheiten": [
|
||
"backend/pipeline.py::_extra"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann die Timeout-Dauer eines Schritts als Basis plus skalierten Zuschlag pro Eintrag berechnen",
|
||
"einheiten": [
|
||
"backend/pipeline.py::_timeout"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann die Probleme-Liste einer Agent-Antwort bereinigen oder leer zurückgeben",
|
||
"einheiten": [
|
||
"backend/pipeline.py::_problems_schema"
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"name": "Provider-Konfiguration",
|
||
"beschreibung": "Lädt Umgebung und Provider-Defaults, wählt pro Rolle den effektiven Provider samt Modell und überschreibt Tuning-Werte zur Startzeit.",
|
||
"features": [
|
||
{
|
||
"titel": "Umgebung und Defaults laden",
|
||
"flag": "kern",
|
||
"beschreibung": "Lädt eine .env-Datei und stellt den Default-Provider als Pflicht-Voraussetzung für den Start sicher.",
|
||
"teilfeatures": [
|
||
{
|
||
"titel": "Kann eine .env-Datei laden und damit Umgebungsvariablen setzen, auch über bereits gesetzte Werte hinweg",
|
||
"einheiten": [
|
||
"backend/config.py::_load_env"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann das System ohne konfigurierten Default-Provider gar nicht starten",
|
||
"einheiten": [
|
||
"backend/config.py::DEFAULT_PROVIDER"
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"titel": "Provider-Stacks und Rollen-Routing",
|
||
"flag": "kern",
|
||
"beschreibung": "Stellt mehrere unabhängige Provider-Stacks bereit und routet Agent-Rollen optional auf alternative Provider um.",
|
||
"teilfeatures": [
|
||
{
|
||
"titel": "Kann mehrere unabhängige Provider-Stacks (Cloud-Auth, API-Key, lokal) mit Modellen und Authentifizierung bereitstellen",
|
||
"einheiten": [
|
||
"backend/config.py::PROVIDERS"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann die vier Agent-Rollen prozessweit auf alternative Provider umleiten",
|
||
"einheiten": [
|
||
"backend/config.py::ROLE_ROUTING"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann für eine Rolle das effektive (Provider, Modell)-Paar unter Berücksichtigung von Overrides und \"provider:model\"-Syntax liefern",
|
||
"einheiten": [
|
||
"backend/config.py::resolve_role"
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"titel": "Schritt-Timeouts und Tuning-Overrides",
|
||
"flag": "rand",
|
||
"beschreibung": "Definiert pro Agent-Schritt eine Timeout-Paarung und erlaubt numerische Tuning-Overrides per JSON zur Startzeit.",
|
||
"teilfeatures": [
|
||
{
|
||
"titel": "Kann pro Agent-Schritt ein Timeout-Paar (Basis, Pro-Eintrag) zentral definieren",
|
||
"einheiten": [
|
||
"backend/config.py::TIMEOUTS"
|
||
]
|
||
},
|
||
{
|
||
"titel": "Kann numerische Tuning-Konstanten aus einer JSON-Umgebungsvariablen zur Startzeit überschreiben",
|
||
"einheiten": [
|
||
"backend/config.py::_apply_param_overrides"
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
],
|
||
"einheiten": {
|
||
"backend/kanban.py::Flow": {
|
||
"id": "backend/kanban.py::Flow",
|
||
"beschreibung": "Hält den Laufzeit-Zustand eines Topic-Runs (aktive Worker pro Stage, Producer-Zähler, Wake-Event), damit andere Einheiten Quieszenz und Producer-Lebenszyklus prüfen können.",
|
||
"fakten": [
|
||
{
|
||
"text": "Hält pro Stage einen Zähler aktiver Worker.",
|
||
"beleg": "self.active: dict[str, int] = {}"
|
||
},
|
||
{
|
||
"text": "Zählt zusätzlich die aktuell laufenden Research-Producer.",
|
||
"beleg": "self.producers = 0"
|
||
},
|
||
{
|
||
"text": "research_done ist nur True, wenn kein einziger Producer mehr läuft.",
|
||
"beleg": "return self.producers <= 0"
|
||
},
|
||
{
|
||
"text": "leave klemmt den Active-Zähler beim Dekrementieren bei Null ab.",
|
||
"beleg": "self.active[stage] = max(0, self.active.get(stage, 0) - 1)"
|
||
},
|
||
{
|
||
"text": "active_in meldet True, sobald irgendeine der angegebenen Stages aktive Worker hat.",
|
||
"beleg": "return any(self.active.get(s, 0) > 0 for s in stages)"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::quiescent"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::_sleep_wake"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::_fail_package"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::_worker"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::run_flow"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::_progress"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/kanban.py::Stage": {
|
||
"id": "backend/kanban.py::Stage",
|
||
"beschreibung": "Beschreibt eine Spalte des Kanban-Boards mit Board-Name, Stage-Name, Processor und Verhaltens-Flags (barrier, serial, gate, drain), die der Worker beim Pull-Verhalten auswertet.",
|
||
"fakten": [
|
||
{
|
||
"text": "Repräsentiert genau eine Spalte, identifiziert durch Board- und Stage-Name, mit einem Processor.",
|
||
"beleg": "self.process = process"
|
||
},
|
||
{
|
||
"text": "Setzt serial automatisch mit, sobald drain True ist.",
|
||
"beleg": "self.serial = serial or drain"
|
||
},
|
||
{
|
||
"text": "Hält eine leere upstream-Liste, die chain_stages mit Vorgänger-Stages füllt.",
|
||
"beleg": "self.upstream: list[str] = []"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::chain_stages"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::_worker"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::_fail_package"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::run_flow"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/kanban.py::chain_stages": {
|
||
"id": "backend/kanban.py::chain_stages",
|
||
"beschreibung": "Füllt für jede Stage deren upstream-Liste mit allen Stages, die in der übergebenen Liste davor stehen, damit Barrier-Worker ihre Vorgänger kennen.",
|
||
"fakten": [
|
||
{
|
||
"text": "Setzt das upstream jeder Stage auf die zuvor gesehenen Stages.",
|
||
"beleg": "s.upstream = list(seen)"
|
||
},
|
||
{
|
||
"text": "Gibt die unveränderte Eingabeliste wieder zurück.",
|
||
"beleg": "return stages"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/kanban.py::Stage"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/kanban.py::quiescent": {
|
||
"id": "backend/kanban.py::quiescent",
|
||
"beschreibung": "Liefert True genau dann, wenn kein Worker in den angegebenen Stages aktiv ist UND keine Karte dort in der Queue liegt — die Barrier-/Exit-Bedingung.",
|
||
"fakten": [
|
||
{
|
||
"text": "Eine leere Stages-Liste gilt sofort als quiescent.",
|
||
"beleg": "return True"
|
||
},
|
||
{
|
||
"text": "Sobald irgendeine der Stages aktive Worker hat, wird sofort False zurückgegeben.",
|
||
"beleg": "return False"
|
||
},
|
||
{
|
||
"text": "True nur, wenn die Datenbank für keine der Stages eine queued Card meldet.",
|
||
"beleg": "return await db.kanban_count(flow.topic, list(stages)) == 0"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "ruft-auf",
|
||
"ziel": "backend/kanban.py::Flow"
|
||
},
|
||
{
|
||
"typ": "ruft-auf",
|
||
"ziel": "backend/database.py::kanban_count"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::_worker"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::run_flow"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/kanban.py::_sleep_wake": {
|
||
"id": "backend/kanban.py::_sleep_wake",
|
||
"beschreibung": "Wartet mit Polling-Timeout auf das Wake-Event des Flows und räumt es danach auf, damit der Worker-Loop reaktiv aber nicht busy-waitend bleibt.",
|
||
"fakten": [
|
||
{
|
||
"text": "Wartet auf das Wake-Event des Flows mit einem Polling-Timeout.",
|
||
"beleg": "await asyncio.wait_for(flow.wake.wait(), timeout=_POLL)"
|
||
},
|
||
{
|
||
"text": "Fängt TimeoutError ab, damit die Polling-Schleife weiterläuft.",
|
||
"beleg": "except asyncio.TimeoutError:"
|
||
},
|
||
{
|
||
"text": "Löscht das Wake-Event vor der Rückkehr.",
|
||
"beleg": "flow.wake.clear()"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/kanban.py::Flow"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::_worker"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/kanban.py::_fail_package": {
|
||
"id": "backend/kanban.py::_fail_package",
|
||
"beschreibung": "Behandelt Package-Fehler mit exponentiellem Backoff bzw. Dead-Letter, aber nur für Karten, die der Processor noch nicht weitergerückt hat.",
|
||
"fakten": [
|
||
{
|
||
"text": "Überspringt Karten, die nicht mehr auf der Stage des Processors sitzen (bereits advanced).",
|
||
"beleg": "if cur is None or cur[\"stage\"] != spec.stage:"
|
||
},
|
||
{
|
||
"text": "Ruft db.kanban_fail_card mit MAX_CARD_RETRIES und RETRY_BACKOFF auf.",
|
||
"beleg": "await db.kanban_fail_card(flow.topic, spec.board, c[\"card_id\"], error,"
|
||
},
|
||
{
|
||
"text": "Loggt eine Warnung, wenn eine Karte auf die Dead-Stage verschoben wird.",
|
||
"beleg": "log.warning(\"kanban %s/%s: card %s → dead (%s)\","
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/kanban.py::Flow"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/kanban.py::Stage"
|
||
},
|
||
{
|
||
"typ": "ruft-auf",
|
||
"ziel": "backend/database.py::kanban_get_card"
|
||
},
|
||
{
|
||
"typ": "ruft-auf",
|
||
"ziel": "backend/database.py::kanban_fail_card"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::_worker"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/kanban.py::_worker": {
|
||
"id": "backend/kanban.py::_worker",
|
||
"beschreibung": "Pullt Karten aus der Queue der eigenen Stage, hält bis zu `inflight` Packages gleichzeitig am Laufen und behandelt sowohl Backoff/Dead-Letter als auch Infra-Pausen, bis der gesamte Flow quiescent ist.",
|
||
"fakten": [
|
||
{
|
||
"text": "Bei drain wird die gesamte Queue in einem Package gezogen, sonst KANBAN_BATCH auf einmal.",
|
||
"beleg": "batch = 100_000 if spec.drain else KANBAN_BATCH"
|
||
},
|
||
{
|
||
"text": "Hält ein claimed-Set mit Card-IDs, damit parallele Pulls dieselbe Karte nicht doppelt holen.",
|
||
"beleg": "claimed: set[str] = set()"
|
||
},
|
||
{
|
||
"text": "Bei AgentInfraError wird der ganze Flow pausiert (stop + infra_paused), statt zu dead-lettern.",
|
||
"beleg": "flow.stop = True"
|
||
},
|
||
{
|
||
"text": "Barrier-Worker pullen nur, wenn das Gate offen ist und alle Upstream-Stages quiescent sind.",
|
||
"beleg": "return await quiescent(flow, spec.upstream)"
|
||
},
|
||
{
|
||
"text": "Bricht beim Exit alle noch laufenden Package-Tasks ab und sammelt sie mit Exceptions ein.",
|
||
"beleg": "await asyncio.gather(*tasks, return_exceptions=True)"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/kanban.py::Flow"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/kanban.py::Stage"
|
||
},
|
||
{
|
||
"typ": "ruft-auf",
|
||
"ziel": "backend/kanban.py::quiescent"
|
||
},
|
||
{
|
||
"typ": "ruft-auf",
|
||
"ziel": "backend/kanban.py::_sleep_wake"
|
||
},
|
||
{
|
||
"typ": "ruft-auf",
|
||
"ziel": "backend/kanban.py::_fail_package"
|
||
},
|
||
{
|
||
"typ": "ruft-auf",
|
||
"ziel": "backend/database.py::kanban_pull"
|
||
},
|
||
{
|
||
"typ": "ruft-auf",
|
||
"ziel": "backend/database.py::kanban_count"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/pipeline.py::AgentInfraError"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::run_flow"
|
||
}
|
||
],
|
||
"anker": [
|
||
"backend/kanban.py::_run",
|
||
"backend/kanban.py::_idle_exit",
|
||
"backend/kanban.py::_may_pull"
|
||
]
|
||
},
|
||
"backend/kanban.py::run_flow": {
|
||
"id": "backend/kanban.py::run_flow",
|
||
"beschreibung": "Startet die übergebenen Producer und je einen Worker pro Stage, lässt sie bis zur globalen Quieszenz laufen, respawnt sie bei Bedarf und meldet den Flow live in active_flows an bzw. ab.",
|
||
"fakten": [
|
||
{
|
||
"text": "Trägt den Flow für die Laufzeit in das Modul-Dict active_flows ein.",
|
||
"beleg": "active_flows[flow.topic] = flow"
|
||
},
|
||
{
|
||
"text": "Startet pro Stage einen Worker mit inflight=1 für serial-Stages, sonst WORKER_INFLIGHT.",
|
||
"beleg": "1 if s.serial else WORKER_INFLIGHT,"
|
||
},
|
||
{
|
||
"text": "Startet den Progress-Reporter nur, wenn ein set_p-Callback übergeben wurde.",
|
||
"beleg": ""
|
||
}
|
||
],
|
||
"kanten": [],
|
||
"anker": []
|
||
},
|
||
"backend/kanban.py::_progress": {
|
||
"id": "backend/kanban.py::_progress",
|
||
"beschreibung": "Pollt sekündlich die Stage-Counts aller Boards aus der Datenbank und meldet die Gesamtzahl laufender Karten über das set_p-Callback zurück, bis das Flow-Stop-Flag gesetzt ist.",
|
||
"fakten": [
|
||
{
|
||
"text": "Läuft in einer Schleife, bis das Stop-Flag des Flows gesetzt ist.",
|
||
"beleg": "while not flow.stop:"
|
||
},
|
||
{
|
||
"text": "Holt pro Board die aktuellen Stage-Counts aus der Datenbank.",
|
||
"beleg": "counts = await db.kanban_stage_counts(flow.topic)"
|
||
},
|
||
{
|
||
"text": "Summiert alle Karten über alle Boards und Stages zu einer Gesamtzahl.",
|
||
"beleg": "total = sum(n for stages in counts.values() for n in stages.values())"
|
||
},
|
||
{
|
||
"text": "Reicht die Gesamtzahl als formatierten deutschen String an set_p weiter.",
|
||
"beleg": "set_p(f\"Kanban: {total} Karten im Fluss\")"
|
||
},
|
||
{
|
||
"text": "Schläft eine Sekunde zwischen den Iterationen.",
|
||
"beleg": "await asyncio.sleep(1.0)"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/kanban.py::Flow"
|
||
},
|
||
{
|
||
"typ": "ruft-auf",
|
||
"ziel": "backend/database.py::kanban_stage_counts"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/kanban.py::run_flow"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/pipeline.py::cancel_guide": {
|
||
"id": "backend/pipeline.py::cancel_guide",
|
||
"beschreibung": "Verwaltet den Cancel-Zustand eines Guides (Markieren, Abfragen, Zurücksetzen) und markiert den Guide-Datensatz als fehlgeschlagen unter Erhalt des Fortschritts.",
|
||
"fakten": [
|
||
{
|
||
"text": "Beim Markieren wird der Guide zur internen Cancel-Menge hinzugefügt, der Agent-Scope gelöscht und laufende Subprozesse beendet.",
|
||
"beleg": "_cancelled.add(guide_id)"
|
||
},
|
||
{
|
||
"text": "Nach dem Abbruch wird der Guide-Datensatz auf status=\"error\" gesetzt, der Fortschritt bleibt erhalten und es wird eine UTC-Zeitmarke geschrieben.",
|
||
"beleg": "await update_guide(guide_id, status=\"error\", progress=None, error_msg=\"Cancelled — progress is preserved\""
|
||
},
|
||
{
|
||
"text": "`is_guide_cancelled` meldet ausschließlich über die Modul-interne Menge, nicht aus dem Datensatz.",
|
||
"beleg": "return guide_id in _cancelled"
|
||
},
|
||
{
|
||
"text": "`clear_guide_cancelled` entfernt den Guide aus der Cancel-Menge und räumt den zugehörigen Agent-Scope auf, sodass ein Neustart nicht blockiert wird.",
|
||
"beleg": "clear_scope(f\"{guide_id}-\") # clear scope → restart not blocked"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "ruft-auf",
|
||
"ziel": "backend/pipeline.py::is_guide_cancelled"
|
||
},
|
||
{
|
||
"typ": "ruft-auf",
|
||
"ziel": "backend/pipeline.py::clear_guide_cancelled"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/database.py::update_guide"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/agents.py::cancel_scope"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/agents.py::kill_process"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/agents.py::clear_scope"
|
||
}
|
||
],
|
||
"anker": [
|
||
"backend/pipeline.py::is_guide_cancelled",
|
||
"backend/pipeline.py::clear_guide_cancelled"
|
||
]
|
||
},
|
||
"backend/pipeline.py::_set_progress": {
|
||
"id": "backend/pipeline.py::_set_progress",
|
||
"beschreibung": "Schreibt Fortschritts- und Schrittinformationen sowie Fehlermeldungen eines Guides persistiert in den Datensatz.",
|
||
"fakten": [
|
||
{
|
||
"text": "`_set_progress` aktualisiert ausschließlich das Fortschrittsfeld zusammen mit einer UTC-Zeitmarke.",
|
||
"beleg": "await update_guide(guide_id, progress=progress, updated_at=now)"
|
||
},
|
||
{
|
||
"text": "`_set_step` schreibt zusätzlich den numerischen Schritt zusammen mit dem Fortschritt und einem Zeitstempel.",
|
||
"beleg": "await update_guide(guide_id, step=step, progress=progress, updated_at=now)"
|
||
},
|
||
{
|
||
"text": "`_fail` setzt den Guide auf den Fehlerstatus, leert den Fortschritt und hinterlegt die übergebene Fehlermeldung.",
|
||
"beleg": "await update_guide(guide_id, status=\"error\", progress=None, error_msg=msg, updated_at=now)"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/database.py::update_guide"
|
||
}
|
||
],
|
||
"anker": [
|
||
"backend/pipeline.py::_set_step",
|
||
"backend/pipeline.py::_fail"
|
||
]
|
||
},
|
||
"backend/pipeline.py::_prompt": {
|
||
"id": "backend/pipeline.py::_prompt",
|
||
"beschreibung": "Stellt Hilfsfunktionen für Prompt-Bau, Logging, Fehlerklassifikation, Timeout-Berechnung und JSON-Schemavalidierung bereit.",
|
||
"fakten": [
|
||
{
|
||
"text": "`_prompt` lädt ein Prompt-Template aus `TEMPLATES_DIR/Prompt/<name>.md` und formatiert es mit den übergebenen Schlüsselwortargumenten.",
|
||
"beleg": "template = (TEMPLATES_DIR / \"Prompt\" / f\"{name}.md\").read_text(encoding=\"utf-8\")"
|
||
},
|
||
{
|
||
"text": "`_extra` hängt zusätzliche Nutzeranweisungen in einem klar markierten Block an, sofern welche vorhanden sind.",
|
||
"beleg": "return f\"\\\\n\\\\nADDITIONAL INSTRUCTIONS FROM THE USER:\\\\n{instructions}\\\\n\" if instructions else \"\""
|
||
},
|
||
{
|
||
"text": "`_is_infra` erkennt Transport- bzw. Infrastrukturfehler anhand einer festen Markerliste, um sie von inhaltlichen Fehlern zu unterscheiden.",
|
||
"beleg": "return any(m in (err or \"\") for m in _INFRA_MARKERS)"
|
||
},
|
||
{
|
||
"text": "`_claude_error` formatiert eine Fehlermeldung aus Returncode, stdout und stderr, mit Fallback auf das Ende der stdout-Ausgabe.",
|
||
"beleg": "return f\"{label} (exit {returncode}, no output)\""
|
||
},
|
||
{
|
||
"text": "`_timeout` berechnet die Timeout-Dauer eines Schritts als Basis plus skalierten Zuschlag pro Eintrag.",
|
||
"beleg": "return base + per * n"
|
||
},
|
||
{
|
||
"text": "`_problems_schema` liefert eine leere Liste bei `{\"ok\": True}`, eine bereinigte Problemliste bei vorhandenen Problemen, sonst `None`.",
|
||
"beleg": "if data.get(\"ok\") is True:"
|
||
},
|
||
{
|
||
"text": "`_runde_schema` liefert im Finalmodus nur dann ein Ergebnis, wenn keine Restfragen offen sind.",
|
||
"beleg": "if include is None or rest is None or (final and rest):"
|
||
},
|
||
{
|
||
"text": "`_enum_map_schema` liefert eine Parser-Factory, die bei ungültigen IDs oder Werten strikt `None` zurückgibt.",
|
||
"beleg": "if value not in allowed:"
|
||
},
|
||
{
|
||
"text": "`_yesno_schema` ist die konkrete Ausprägung der Enum-Map-Factory für das Triage-Feld \"relevant\" mit den Werten \"ja\"/\"nein\".",
|
||
"beleg": "_yesno_schema = _enum_map_schema(\"relevant\", _YESNO) # triage gate ∈ ja/nein"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/config.py::TEMPLATES_DIR"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/config.py::TIMEOUTS"
|
||
}
|
||
],
|
||
"anker": [
|
||
"backend/pipeline.py::_extra",
|
||
"backend/pipeline.py::_log",
|
||
"backend/pipeline.py::_claude_error",
|
||
"backend/pipeline.py::_is_infra",
|
||
"backend/pipeline.py::_gather_error",
|
||
"backend/pipeline.py::_timeout",
|
||
"backend/pipeline.py::_problems_schema",
|
||
"backend/pipeline.py::_str_list",
|
||
"backend/pipeline.py::_runde_schema",
|
||
"backend/pipeline.py::_enum_map_schema",
|
||
"backend/pipeline.py::_yesno_schema"
|
||
]
|
||
},
|
||
"backend/pipeline.py::_race": {
|
||
"id": "backend/pipeline.py::_race",
|
||
"beschreibung": "Startet parallele Agent-Slots, sammelt eine konfigurierbare Quorum-Anzahl gültiger Ergebnisse und behandelt dabei Timeouts, Infrastrukturfehler, Hedging, Grace-Periode und Stornierung.",
|
||
"fakten": [
|
||
{
|
||
"text": "Slots, die länger als `hedge_s` ohne Ergebnis laufen, bekommen genau einen parallelen Zwilling mit dem Suffix `-h`.",
|
||
"beleg": "hedged.add(i)"
|
||
},
|
||
{
|
||
"text": "Sobald das Quorum steht und die Grace-Frist abgelaufen ist, werden laufende Agents beendet und die bis dahin gesammelten Ergebnisse zurückgegeben.",
|
||
"beleg": "if deadline is not None and len(results) >= quorum and loop.time() >= deadline:"
|
||
},
|
||
{
|
||
"text": "Infrastrukturfehler werden mit eigenem Zähler und wachsendem Backoff bis zu `_INFRA_MAX_RETRIES` wiederholt; bei Erschöpfung wird `AgentInfraError` ausgelöst.",
|
||
"beleg": "raise AgentInfraError("
|
||
},
|
||
{
|
||
"text": "Eine aktive Stornierung führt sofort zum Abbruch ohne Neustart und Rückgabe von `None`.",
|
||
"beleg": "if cancelled and cancelled():"
|
||
},
|
||
{
|
||
"text": "Im `finally`-Block werden alle noch laufenden Tasks storniert und ihre Subprozesse beendet, auch beim vorzeitigen Abbruch.",
|
||
"beleg": "for task, i in tasks.items():"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/agents.py::run_agent"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/agents.py::kill_process"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/pipeline.py::_is_infra"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/pipeline.py::_claude_error"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/pipeline.py::_log"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/pipeline.py::_timeout"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/config.py::MAX_CONCURRENT_GENERATIONS"
|
||
}
|
||
],
|
||
"anker": [
|
||
"backend/pipeline.py::AgentInfraError"
|
||
]
|
||
},
|
||
"backend/pipeline.py::GenContext": {
|
||
"id": "backend/pipeline.py::GenContext",
|
||
"beschreibung": "Bündelt Pipeline-Parameter (Topic, Provider, Stornierungsprüfung, Guide-ID) zu einem Wert, der durch die Pipeline-Glieder weitergereicht wird.",
|
||
"fakten": [
|
||
{
|
||
"text": "`GenContext` kapselt die langen Argumentlisten der Pipeline-Aufrufe in einem Wert.",
|
||
"beleg": "\"\"\"Pipeline parameters passed through — saves long argument signatures.\"\"\""
|
||
},
|
||
{
|
||
"text": "`is_cancelled` ist eine parameterlose Funktion, die den aktuellen Stornierungszustand abfragt.",
|
||
"beleg": "is_cancelled: Callable[[], bool]"
|
||
},
|
||
{
|
||
"text": "`run_single_slot` führt genau einen Agent-Aufruf als Rennen mit Quorum 1 aus und übersetzt das Ergebnis in einen `(status, wert)`-Tripel.",
|
||
"beleg": "res = await _race(ctx.topic, label, slots, 1, timeout, ctx.provider, cancelled=ctx.is_cancelled)"
|
||
},
|
||
{
|
||
"text": "Bei gesetztem Quorum und Stornierung gibt `run_single_slot` `(CANCELLED, None)` zurück; bei verfehltem Quorum `(FAILED, None)`.",
|
||
"beleg": "if res is None:"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "ruft-auf",
|
||
"ziel": "backend/pipeline.py::_race"
|
||
}
|
||
],
|
||
"anker": [
|
||
"backend/pipeline.py::run_single_slot"
|
||
]
|
||
},
|
||
"backend/pipeline.py::_gather_progress": {
|
||
"id": "backend/pipeline.py::_gather_progress",
|
||
"beschreibung": "Führt Coroutinen nebenläufig aus, meldet nach jeder Vervollständigung den Live-Fortschritt und liefert die Ergebnisse in Eingabereihenfolge samt Ausnahmen.",
|
||
"fakten": [
|
||
{
|
||
"text": "Vor dem Start der Coroutinen wird der Initialwert `done` an den Reporter übergeben.",
|
||
"beleg": "await report(done, total)"
|
||
},
|
||
{
|
||
"text": "Jeder abgeschlossene Job ruft den Reporter auch dann auf, wenn die Coroutine eine Ausnahme wirft, dank `try/finally`.",
|
||
"beleg": "finally:"
|
||
},
|
||
{
|
||
"text": "`asyncio.gather` wird mit `return_exceptions=True` aufgerufen, damit ein Fehler in einer Coroutine die übrigen nicht abbricht.",
|
||
"beleg": "return await asyncio.gather(*[wrap(c) for c in coros], return_exceptions=True)"
|
||
}
|
||
],
|
||
"kanten": [],
|
||
"anker": []
|
||
},
|
||
"backend/pipeline.py::is_guide_cancelled": {
|
||
"id": "backend/pipeline.py::is_guide_cancelled",
|
||
"beschreibung": "Beantwortet die Frage, ob ein Guide abgesagt wurde, ausschließlich anhand der Modul-internen Cancel-Menge.",
|
||
"fakten": [
|
||
{
|
||
"text": "Die Prüfung liest ausschließlich die Modul-interne Cancel-Menge und nicht den Datensatz.",
|
||
"beleg": "return guide_id in _cancelled"
|
||
}
|
||
],
|
||
"kanten": [],
|
||
"anker": []
|
||
},
|
||
"backend/pipeline.py::clear_guide_cancelled": {
|
||
"id": "backend/pipeline.py::clear_guide_cancelled",
|
||
"beschreibung": "Entfernt einen Guide aus der internen Cancel-Menge und räumt den zugehörigen Agent-Scope auf, damit ein Neustart nicht blockiert wird.",
|
||
"fakten": [
|
||
{
|
||
"text": "Entfernt die Guide-ID aus der Modul-internen Cancel-Menge.",
|
||
"beleg": "_cancelled.discard(guide_id)"
|
||
},
|
||
{
|
||
"text": "Löscht den Agent-Scope unter dem Prefix \"{guide_id}-\" mit Hinweis auf Restart-Freigabe.",
|
||
"beleg": "clear_scope(f\"{guide_id}-\") # clear scope → restart not blocked"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/agents.py::clear_scope"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/pipeline.py::_set_step": {
|
||
"id": "backend/pipeline.py::_set_step",
|
||
"beschreibung": "Schreibt zusätzlich zum Fortschritt den numerischen Schritt eines Guides in den Datensatz und stempelt die Aktualisierungszeit.",
|
||
"fakten": [
|
||
{
|
||
"text": "Persistiert step, progress und updated_at in einem update_guide-Aufruf.",
|
||
"beleg": "await update_guide(guide_id, step=step, progress=progress, updated_at=now)"
|
||
},
|
||
{
|
||
"text": "Setzt den Zeitstempel als UTC-ISO-String.",
|
||
"beleg": "now = datetime.now(timezone.utc).isoformat()"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/database.py::update_guide"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/pipeline.py::_fail": {
|
||
"id": "backend/pipeline.py::_fail",
|
||
"beschreibung": "Setzt den Guide-Datensatz konsistent auf den Fehlerstatus mit übergebener Meldung und löscht den Fortschritt.",
|
||
"fakten": [
|
||
{
|
||
"text": "Schreibt status=\"error\", leert progress und hinterlegt die übergebene Fehlermeldung.",
|
||
"beleg": "await update_guide(guide_id, status=\"error\", progress=None, error_msg=msg, updated_at=now)"
|
||
},
|
||
{
|
||
"text": "Stempelt den Datensatz mit einer UTC-Zeitmarke.",
|
||
"beleg": "now = datetime.now(timezone.utc).isoformat()"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/database.py::update_guide"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/pipeline.py::_extra": {
|
||
"id": "backend/pipeline.py::_extra",
|
||
"beschreibung": "Erzeugt einen klar markierten Block mit zusätzlichen Nutzeranweisungen für einen Prompt oder liefert einen leeren String, falls keine Anweisungen vorliegen.",
|
||
"fakten": [
|
||
{
|
||
"text": "Mit Anweisungen wird ein Block mit fester Überschrift eingefügt.",
|
||
"beleg": "return f\"\\\\n\\\\nADDITIONAL INSTRUCTIONS FROM THE USER:\\\\n{instructions}\\\\n\" if instructions else \"\""
|
||
},
|
||
{
|
||
"text": "Ohne Anweisungen ist das Ergebnis der leere String.",
|
||
"beleg": "if instructions else \"\""
|
||
}
|
||
],
|
||
"kanten": [],
|
||
"anker": []
|
||
},
|
||
"backend/pipeline.py::_log": {
|
||
"id": "backend/pipeline.py::_log",
|
||
"beschreibung": "Schreibt eine einheitlich formatierte Info-Meldung mit Topic-Präfix über den Pipeline-Logger.",
|
||
"fakten": [
|
||
{
|
||
"text": "Nutzt den Modul-Logger mit dem Namen \"creator.pipeline\".",
|
||
"beleg": "log = logging.getLogger(\"creator.pipeline\")"
|
||
},
|
||
{
|
||
"text": "Setzt Topic in eckige Klammern vor die eigentliche Meldung.",
|
||
"beleg": "log.info(\"[%s] %s\", topic, msg)"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/pipeline.py::_race"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/pipeline.py::_claude_error": {
|
||
"id": "backend/pipeline.py::_claude_error",
|
||
"beschreibung": "Baut eine kompakte Fehlermeldung aus Returncode, stdout und stderr eines Agent-Aufrufs mit abgestufter Fallback-Strategie.",
|
||
"fakten": [
|
||
{
|
||
"text": "Zieht zuerst den gestrippten stderr heran.",
|
||
"beleg": "stderr = (stderr or \"\").strip()"
|
||
},
|
||
{
|
||
"text": "Bevorzugt stderr bis 1000 Zeichen als Fehlermeldung.",
|
||
"beleg": "return f\"{label}: {stderr[:1000]}\""
|
||
},
|
||
{
|
||
"text": "Bei leerem stderr wird das Ende der stdout als Fallback genutzt.",
|
||
"beleg": "tail = (stdout or \"\").strip()[-500:]"
|
||
},
|
||
{
|
||
"text": "Bei komplett fehlender Ausgabe erscheint nur Label und Exitcode mit \"no output\".",
|
||
"beleg": "return f\"{label} (exit {returncode}, no output)\""
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/pipeline.py::_race"
|
||
},
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/pipeline.py::_gather_error"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/pipeline.py::_is_infra": {
|
||
"id": "backend/pipeline.py::_is_infra",
|
||
"beschreibung": "Klassifiziert eine Fehlermeldung als Transport- bzw. Infrastrukturfehler anhand einer festen Markerliste.",
|
||
"fakten": [
|
||
{
|
||
"text": "Die Markerliste enthält HTTP 429, HTTP 5xx, rate_limit, Timeout after und diverse Netzfehler.",
|
||
"beleg": "_INFRA_MARKERS = (\"HTTP 429\", \"HTTP 5\", \"rate_limit\", \"Timeout after\","
|
||
},
|
||
{
|
||
"text": "Ein Marker-Treffer genügt, um die Meldung als Infra-Fehler zu werten.",
|
||
"beleg": "return any(m in (err or \"\") for m in _INFRA_MARKERS)"
|
||
},
|
||
{
|
||
"text": "Der Docstring grenzt Infra-Fehler von inhaltlichem Fehlschlag ab.",
|
||
"beleg": "\"\"\"Transport-/Infra-Fehler (retry + pause) statt inhaltlichem Fehlschlag.\"\"\""
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/pipeline.py::_race"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/pipeline.py::_gather_error": {
|
||
"id": "backend/pipeline.py::_gather_error",
|
||
"beschreibung": "Wählt aus einer Liste von Slot-Ergebnissen den ersten Fehler aus und formatiert ihn als kompakten Fehlertext.",
|
||
"fakten": [
|
||
{
|
||
"text": "Iteriert die Ergebnisliste in Reihenfolge und meldet den ersten Treffer.",
|
||
"beleg": "for r in results:"
|
||
},
|
||
{
|
||
"text": "Ausnahmen werden als Klassenname plus Meldung ausgegeben.",
|
||
"beleg": "return f\"{label}: {type(r).__name__}: {r}\""
|
||
},
|
||
{
|
||
"text": "Nicht-null-Returncodes werden an `_claude_error` zur Formatierung delegiert.",
|
||
"beleg": "return _claude_error(label, returncode, stdout, stderr)"
|
||
},
|
||
{
|
||
"text": "Ohne verwertbares Ergebnis wird ein pauschaler Fehlertext geliefert.",
|
||
"beleg": "return f\"{label}: no usable result\""
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/pipeline.py::_claude_error"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/pipeline.py::_timeout": {
|
||
"id": "backend/pipeline.py::_timeout",
|
||
"beschreibung": "Berechnet die Timeout-Dauer eines Pipeline-Schritts als Basis plus skalierten Zuschlag pro Eintrag aus der zentralen TIMEOUTS-Tabelle.",
|
||
"fakten": [
|
||
{
|
||
"text": "Liest Basis und Zuschlag pro Eintrag aus TIMEOUTS für den angegebenen Schritt.",
|
||
"beleg": "base, per = TIMEOUTS[step]"
|
||
},
|
||
{
|
||
"text": "Liefert die Summe aus Basis und Zuschlag multipliziert mit n.",
|
||
"beleg": "return base + per * n"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/config.py::TIMEOUTS"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/pipeline.py::_problems_schema": {
|
||
"id": "backend/pipeline.py::_problems_schema",
|
||
"beschreibung": "Validiert Agent-Antworten für das Probleme-Feld und liefert je nach Form eine leere Liste, die bereinigte",
|
||
"fakten": [],
|
||
"kanten": [],
|
||
"anker": []
|
||
},
|
||
"backend/config.py::_load_env": {
|
||
"id": "backend/config.py::_load_env",
|
||
"beschreibung": "Lädt KEY=VALUE-Zeilen aus einer .env-Datei in os.environ, wobei die Datei bereits gesetzte Umgebungsvariablen überschreibt.",
|
||
"fakten": [
|
||
{
|
||
"text": "Liest die .env-Datei UTF-8-codiert ein.",
|
||
"beleg": "text = path.read_text(encoding=\"utf-8\")"
|
||
},
|
||
{
|
||
"text": "Bricht den Ladevorgang still ab, wenn die Datei nicht lesbar ist.",
|
||
"beleg": "except OSError:"
|
||
},
|
||
{
|
||
"text": "Überspringt Leerzeilen, Kommentarzeilen und Zeilen ohne Gleichheitszeichen.",
|
||
"beleg": "if not line or line.startswith(\"#\") or \"=\" not in line:"
|
||
},
|
||
{
|
||
"text": "Entfernt umgebende Anführungszeichen aus den Werten.",
|
||
"beleg": "value = value.strip().strip('\"').strip(\"'\")"
|
||
},
|
||
{
|
||
"text": "Schreibt jeden gültigen Schlüssel in os.environ (Datei gewinnt gegen vererbte Env).",
|
||
"beleg": "os.environ[key] = value"
|
||
}
|
||
],
|
||
"kanten": [],
|
||
"anker": []
|
||
},
|
||
"backend/config.py::DEFAULT_PROVIDER": {
|
||
"id": "backend/config.py::DEFAULT_PROVIDER",
|
||
"beschreibung": "Stellt sicher, dass DEFAULT_PROVIDER aus der Umgebung gesetzt ist, und bricht den Start sonst mit einer RuntimeError ab.",
|
||
"fakten": [
|
||
{
|
||
"text": "Liest DEFAULT_PROVIDER aus der Umgebung mit leerem Default.",
|
||
"beleg": "DEFAULT_PROVIDER = os.getenv(\"DEFAULT_PROVIDER\", \"\")"
|
||
},
|
||
{
|
||
"text": "Bricht den Start ab, wenn DEFAULT_PROVIDER nicht gesetzt ist.",
|
||
"beleg": "if not DEFAULT_PROVIDER:"
|
||
},
|
||
{
|
||
"text": "Die Fehlermeldung nennt `minimax` als konkreten Beispielwert.",
|
||
"beleg": "DEFAULT_PROVIDER=minimax)\""
|
||
}
|
||
],
|
||
"kanten": [],
|
||
"anker": []
|
||
},
|
||
"backend/config.py::PROVIDERS": {
|
||
"id": "backend/config.py::PROVIDERS",
|
||
"beschreibung": "Stellt die unabhängigen Provider-Stacks (claude, minimax, lokal) mit Rollen-Modellen, CLI-Aufrufen und Authentifizierungs-Umgebungsvariablen bereit.",
|
||
"fakten": [
|
||
{
|
||
"text": "Mehrere voneinander unabhängige Provider-Stacks sind in einem Dict zusammengefasst.",
|
||
"beleg": "PROVIDERS = {"
|
||
},
|
||
{
|
||
"text": "Der `lokal`-Stack prüft Ollama-Erreichbarkeit über eine HTTP-URL.",
|
||
"beleg": "\"check_url\": \"http://localhost:11434/api/tags\","
|
||
},
|
||
{
|
||
"text": "Der `minimax`-Stack nutzt den ENV-Key `MINIMAX_API_KEY` zur Authentifizierung.",
|
||
"beleg": "\"env_key\": \"MINIMAX_API_KEY\","
|
||
},
|
||
{
|
||
"text": "Der `claude`-Stack nutzt OAuth-Authentifizierung statt eines API-Keys.",
|
||
"beleg": "\"env_key\": None, # auth via CLAUDE_CODE_OAUTH_TOKEN"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/config.py::resolve_role"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/config.py::ROLE_ROUTING": {
|
||
"id": "backend/config.py::ROLE_ROUTING",
|
||
"beschreibung": "Bildet die vier Agent-Rollen quick/judge/guide/fast auf optionale prozessweite Override-Provider aus Umgebungsvariablen ab.",
|
||
"fakten": [
|
||
{
|
||
"text": "Vier Agent-Rollen können prozessweit auf einen anderen Provider-Stack umgeleitet werden.",
|
||
"beleg": "\"quick\": os.getenv(\"ROLE_QUICK\", \"\"),"
|
||
},
|
||
{
|
||
"text": "Fehlende Env-Variablen ergeben einen leeren Override-String.",
|
||
"beleg": "os.getenv(\"ROLE_QUICK\", \"\"),"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/config.py::resolve_role"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/config.py::resolve_role": {
|
||
"id": "backend/config.py::resolve_role",
|
||
"beschreibung": "Liefert für eine Rolle das konkrete (provider, model)-Paar, wobei ein per Env gesetzter Rollen-Override Vorrang hat und sonst der Lauf-Provider genutzt wird.",
|
||
"fakten": [
|
||
{
|
||
"text": "Ein gesetzter Rollen-Override aus ROLE_ROUTING hat Vorrang vor dem Lauf-Provider.",
|
||
"beleg": "target = ROLE_ROUTING.get(role, \"\") or run_provider"
|
||
},
|
||
{
|
||
"text": "Die Syntax \"provider:model\" erlaubt es, ein bestimmtes Modell zu pinnen.",
|
||
"beleg": "provider, _, model = target.partition(\":\")"
|
||
},
|
||
{
|
||
"text": "Unbekannte Provider fallen auf den Lauf-Provider zurück.",
|
||
"beleg": "if provider not in PROVIDERS:"
|
||
},
|
||
{
|
||
"text": "Fehlt eine explizite Modellangabe, wird das Rollen-Default des Stacks genutzt.",
|
||
"beleg": "model = PROVIDERS.get(provider, {}).get(role, \"\")"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/config.py::ROLE_ROUTING"
|
||
},
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/config.py::PROVIDERS"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/config.py::TIMEOUTS": {
|
||
"id": "backend/config.py::TIMEOUTS",
|
||
"beschreibung": "Definiert pro Agent-Schritt ein (Basis, pro-Block)-Timeout-Paar, das unabhängig vom Provider gilt.",
|
||
"fakten": [
|
||
{
|
||
"text": "Jeder Agent-Schritt hat individuelle Timeout-Werte als (Basis, pro-Block)-Tupel.",
|
||
"beleg": "\"research_mapping\": (600, 3), # n = pre-merged entries"
|
||
},
|
||
{
|
||
"text": "Die Judge-Caps wurden am 2026-07-04 reduziert.",
|
||
"beleg": "# Judge caps tightened 2026-07-04: judge p50 is 6–72 s;"
|
||
},
|
||
{
|
||
"text": "Der `writer` skaliert mit 60 s pro Sektion.",
|
||
"beleg": "\"writer\": (450, 60), # per section"
|
||
},
|
||
{
|
||
"text": "QA/Repair-Wellen (`qa_judge`) skalieren nicht mit n.",
|
||
"beleg": "\"qa_judge\": (600, 0),"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "wird-genutzt-von",
|
||
"ziel": "backend/config.py::_apply_param_overrides"
|
||
}
|
||
],
|
||
"anker": []
|
||
},
|
||
"backend/config.py::_apply_param_overrides": {
|
||
"id": "backend/config.py::_apply_param_overrides",
|
||
"beschreibung": "Liest ein JSON-Override-Dict aus der Umgebungsvariable CREATOR_PARAMS und überschreibt damit numerische Tuning-Konstanten dieses Moduls zur Prozess-Startzeit.",
|
||
"fakten": [
|
||
{
|
||
"text": "Bricht die Override-Anwendung still ab, wenn CREATOR_PARAMS nicht gesetzt ist.",
|
||
"beleg": "if not raw:"
|
||
},
|
||
{
|
||
"text": "Weist ungültiges JSON in CREATOR_PARAMS mit SystemExit zurück.",
|
||
"beleg": "except ValueError:"
|
||
},
|
||
{
|
||
"text": "Timeout-Overrides werden über das Präfix `TIMEOUT_` in TIMEOUTS eingespielt.",
|
||
"beleg": "if key.startswith(\"TIMEOUT_\"):"
|
||
},
|
||
{
|
||
"text": "Unbekannte Schlüssel oder boolesche Werte werden mit SystemExit abgewiesen.",
|
||
"beleg": "isinstance(g[key], bool):"
|
||
},
|
||
{
|
||
"text": "Numerische Overrides behalten den ursprünglichen Typ des Ziels.",
|
||
"beleg": "g[key] = type(g[key])(val)"
|
||
}
|
||
],
|
||
"kanten": [
|
||
{
|
||
"typ": "nutzt",
|
||
"ziel": "backend/config.py::TIMEOUTS"
|
||
}
|
||
],
|
||
"anker": []
|
||
}
|
||
}
|
||
};
|
||
const wurzel = document.getElementById("wurzel");
|
||
const eht = id => DATEN.einheiten[id];
|
||
const esc = s => s.replace(/[&<>"]/g, c => ({"&":"&","<":"<",">":">",'"':"""}[c]));
|
||
|
||
for (const b of DATEN.bereiche) {
|
||
const bx = document.createElement("section");
|
||
bx.className = "bereich";
|
||
bx.innerHTML = `<h2>${esc(b.name)}</h2><p>${esc(b.beschreibung)}</p>`;
|
||
for (const f of b.features) {
|
||
const fd = document.createElement("details");
|
||
fd.innerHTML = `<summary><strong>${esc(f.titel)}</strong>
|
||
<span class="flag ${f.flag}">${f.flag}</span></summary>
|
||
<p class="feature-besch">${esc(f.beschreibung)}</p>`;
|
||
for (const t of f.teilfeatures) {
|
||
const td = document.createElement("details");
|
||
td.className = "teil";
|
||
td.innerHTML = `<summary>${esc(t.titel)}</summary>`;
|
||
for (const id of t.einheiten) {
|
||
const e = eht(id);
|
||
const ed = document.createElement("details");
|
||
ed.className = "einheit";
|
||
if (!e) { ed.innerHTML = `<summary>${esc(id)} (nicht im Teilscan)</summary>`; td.appendChild(ed); continue; }
|
||
const fakten = e.fakten.map(fa => `<div class="fakt">${esc(fa.text)}
|
||
${fa.beleg ? `<code class="beleg">${esc(fa.beleg)}</code>` : ""}</div>`).join("");
|
||
const kanten = e.kanten.length
|
||
? `<div class="kanten">Kanten: ${e.kanten.map(k => `${k.typ} → <code>${esc(k.ziel)}</code>`).join(" · ")}</div>` : "";
|
||
ed.innerHTML = `<summary>${esc(id)}</summary>
|
||
<div class="einheit-inhalt"><p><em>${esc(e.beschreibung)}</em></p>${fakten}${kanten}</div>`;
|
||
td.appendChild(ed);
|
||
}
|
||
fd.appendChild(td);
|
||
}
|
||
bx.appendChild(fd);
|
||
}
|
||
wurzel.appendChild(bx);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|