# Einheiten: backend/kanban.py stand: UNBEKANNT ## backend/kanban.py::Flow beschreibung: Verwaltet den Laufzeit-Kontext eines Topics: aktive Zähler, Producer-Status und Koordinations-Events. fakten: - Zählt aktive Producer und meldet done, wenn alle fertig sind beleg: "return self.producers <= 0" - Notifybar, wenn sich Producer-Zähler oder aktive Karten ändern beleg: "self.wake.set()" - Führt Topic-spezifischen Zustand (z.B. infra_paused) beleg: "self.state: dict = {}" - Registriert laufende Flows global für Cancel/Attach beleg: "active_flows: dict[str, \"Flow\"] = {}" - Verfolgt Karten-IDs, die gerade in einem Processor laufen beleg: "self.active_cards: set[str] = set()" kanten: - nutzt: backend/database.py::kanban_get_card - wird-genutzt-von: backend/kanban.py::_worker - wird-genutzt-von: backend/kanban.py::run_flow ## backend/kanban.py::Stage beschreibung: Kapselt eine Spalte des Kanban-Boards: Board/Stage-Name, Processor und Barrier/Serial/Gate/Drain-Parameter. fakten: - Prozessor wird pro Stage nur einmal gespeichert, von allen Rows gemeinsam genutzt beleg: "self.process = process" - Serial erzwingt sequentielle Abarbeitung (auch bei drain) beleg: "self.serial = serial or drain" - Barrier-Wert wird unverändert übernommen beleg: "self.barrier = barrier" - Upstream wird von chain_stages befüllt, nicht im Konstruktor beleg: "self.upstream: list[str] = []" kanten: - nutzt: backend/kanban.py::chain_stages - wird-genutzt-von: backend/kanban.py::_worker - wird-genutzt-von: backend/kanban.py::run_flow ## backend/kanban.py::chain_stages beschreibung: Baut die upstream-Kette aller Stages — jede Stage sieht alle vorherigen Stages als upstream. fakten: - Befüllt upstream jeder Stage mit allen davor liegenden Stage-Namen beleg: "s.upstream = list(seen)" - Gibt die Eingabeliste zurück (Method-Chaining-fähig) beleg: "return stages" - producers gelten implizit als upstream von allem beleg: "Producers are upstream of everything implicitly" kanten: - wird-genutzt-von: backend/kanban.py::Stage ## backend/kanban.py::quiescent beschreibung: Prüft, ob alle gegebenen Stages ruhen (keine aktiven Worker UND keine Cards in der DB). fakten: - Leere Stage-Liste gilt als quiescent beleg: "if not stages: return True" - Zählt nur Cards in der DB, nicht in-flight im Speicher beleg: "return await db.kanban_count(flow.topic, list(stages)) == 0" - Berücksichtigt auch Producer-Status via flow.active_in beleg: "if flow.active_in(stages): return False" kanten: - nutzt: backend/database.py::kanban_count - nutzt: backend/kanban.py::Flow - wird-genutzt-von: backend/kanban.py::_worker ## backend/kanban.py::_sleep_wake beschreibung: Blockiert bis Wake-Event oder Poll-Timeout, dann cleart das Event. fakten: - Wartet maximal _POLL Sekunden beleg: "await asyncio.wait_for(flow.wake.wait(), timeout=_POLL)" - Timeout bedeutet kein Wake innerhalb von _POLL beleg: "except asyncio.TimeoutError: pass" - Event wird nach Return gecleart beleg: "flow.wake.clear()" kanten: - nutzt: backend/kanban.py::Flow ## backend/kanban.py::_fail_package beschreibung: Setzt nicht-advanced Cards auf Backoff oder Dead-Letter nach Processor-Fehler. fakten: - Überspringt Cards, die bereits weitergingen (ihre Stage hat sich geändert) beleg: "if cur is None or cur[\"stage\"] != spec.stage: continue" - Nutzt db.kanban_fail_card für Backoff und potentielles Dead-Lettering beleg: "dead = await db.kanban_fail_card(flow.topic, spec.board, c[\"card_id\"" - Loggt wann eine Card nach dead geht beleg: "log.warning(\"kanban %s/%s: card %s → dead (%s)\"" kanten: - nutzt: backend/database.py::kanban_get_card - nutzt: backend/database.py::kanban_fail_card - wird-genutzt-von: backend/kanban.py::_worker ## backend/kanban.py::_worker beschreibung: Dauerläufer pro Stage: zieht Cards, dispatcht bis INFLIGHT concurrent, handhabt Errors und Exit. fakten: - Zieht max KANBAN_BATCH Cards pro Pull (bzw. 100_000 bei drain) beleg: "batch = 100_000 if spec.drain else KANBAN_BATCH" - Hält Claims für alle in-flight Cards um Doppel-Pull zu verhindern beleg: "claimed: set[str] = set()" - AgentInfraError (429/Timeout) pausiert den Flow komplett beleg: "flow.state[\"infra_paused\"] = True" - Any Exception wird nicht-propagiert — nur _fail_package aufgerufen beleg: "except Exception as e: log.info(\"kanban %s/%s: %s: %s\"" - Prüft Gate (falls gesetzt) VOR dem Pull beleg: "if spec.gate is not None and not spec.gate(): return False" - Barrier-Worker wartet auf upstream-Quieszenz vor dem Pull beleg: "return await quiescent(flow, spec.upstream)" - Exit erst wenn research_done UND alle Stages quiescent beleg: "return (flow.research_done and not flow.active_in(all_stages)" kanten: - ruft-auf: backend/kanban.py::quiescent - ruft-auf: backend/kanban.py::_fail_package - ruft-auf: backend/kanban.py::_sleep_wake - nutzt: backend/database.py::kanban_pull - nutzt: backend/kanban.py::Flow - nutzt: backend/kanban.py::Stage - wird-genutzt-von: backend/kanban.py::run_flow ## backend/kanban.py::run_flow beschreibung: Startet Producer + einen Worker pro Stage, läuft bis globaler Quieszenz oder stop-Flag. fakten: - Registriert Flow global für Cancel/Attach beleg: "active_flows[flow.topic] = flow" - Ein Worker pro Stage (serial=1, sonst WORKER_INFLIGHT) beleg: "1 if s.serial else WORKER_INFLIGHT" - Restart-Schleife fängt Producer an, die genau beim Exit dazukamen beleg: "workers = _spawn_workers()" - Stoppt Flows bei infra_paused oder bei globaler Quieszenz beleg: "if flow.stop or (flow.research_done and await quiescent(flow, names))" - Räumt active_flows im finally-Block auf beleg: "active_flows.pop(flow.topic, None)" kanten: - ruft-auf: backend/kanban.py::_worker - ruft-auf: backend/kanban.py::_progress - ruft-auf: backend/kanban.py::quiescent - nutzt: backend/kanban.py::Flow - nutzt: backend/kanban.py::Stage - nutzt: backend/database.py::kanban_stage_counts - wird-genutzt-von: backend/kanban.py::Flow (spawn_research callback) ## backend/kanban.py::_progress beschreibung: Reportet periodisch die Gesamtzahl aller Karten im Flow an den Callback. fakten: - Zählt Cards pro Stage via db.kanban_stage_counts beleg: "counts = await db.kanban_stage_counts(flow.topic)" - Summiert über alle Stages und Boards beleg: "total = sum(n for stages in counts.values() for n in stages.values())" - Ruft set_p alle 1.0 Sekunden auf beleg: "await asyncio.sleep(1.0)" kanten: - nutzt: backend/database.py::kanban_stage_counts - nutzt: backend/kanban.py::Flow - wird-ausgelöst-von: backend/kanban.py::run_flow