# Einheiten: backend/kanban.py stand: UNBEKANNT ## 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: - Hält pro Stage einen Zähler aktiver Worker. beleg: "self.active: dict[str, int] = {}" - Zählt zusätzlich die aktuell laufenden Research-Producer. beleg: "self.producers = 0" - research_done ist nur True, wenn kein einziger Producer mehr läuft. beleg: "return self.producers <= 0" - leave klemmt den Active-Zähler beim Dekrementieren bei Null ab. beleg: "self.active[stage] = max(0, self.active.get(stage, 0) - 1)" - 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: - wird-genutzt-von: backend/kanban.py::quiescent - wird-genutzt-von: backend/kanban.py::_sleep_wake - wird-genutzt-von: backend/kanban.py::_fail_package - wird-genutzt-von: backend/kanban.py::_worker - wird-genutzt-von: backend/kanban.py::run_flow - wird-genutzt-von: backend/kanban.py::_progress ## 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: - Repräsentiert genau eine Spalte, identifiziert durch Board- und Stage-Name, mit einem Processor. beleg: "self.process = process" - Setzt serial automatisch mit, sobald drain True ist. beleg: "self.serial = serial or drain" - Hält eine leere upstream-Liste, die chain_stages mit Vorgänger-Stages füllt. beleg: "self.upstream: list[str] = []" kanten: - wird-genutzt-von: backend/kanban.py::chain_stages - wird-genutzt-von: backend/kanban.py::_worker - wird-genutzt-von: backend/kanban.py::_fail_package - wird-genutzt-von: backend/kanban.py::run_flow ## 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: - Setzt das upstream jeder Stage auf die zuvor gesehenen Stages. beleg: "s.upstream = list(seen)" - Gibt die unveränderte Eingabeliste wieder zurück. beleg: "return stages" kanten: - nutzt: backend/kanban.py::Stage ## 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: - Eine leere Stages-Liste gilt sofort als quiescent. beleg: "return True" - Sobald irgendeine der Stages aktive Worker hat, wird sofort False zurückgegeben. beleg: "return False" - 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: - ruft-auf: backend/kanban.py::Flow - ruft-auf: backend/database.py::kanban_count - wird-genutzt-von: backend/kanban.py::_worker - wird-genutzt-von: backend/kanban.py::run_flow ## 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: - Wartet auf das Wake-Event des Flows mit einem Polling-Timeout. beleg: "await asyncio.wait_for(flow.wake.wait(), timeout=_POLL)" - Fängt TimeoutError ab, damit die Polling-Schleife weiterläuft. beleg: "except asyncio.TimeoutError:" - Löscht das Wake-Event vor der Rückkehr. beleg: "flow.wake.clear()" kanten: - nutzt: backend/kanban.py::Flow - wird-genutzt-von: backend/kanban.py::_worker ## 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: - Überspringt Karten, die nicht mehr auf der Stage des Processors sitzen (bereits advanced). beleg: "if cur is None or cur["stage"] != spec.stage:" - 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," - Loggt eine Warnung, wenn eine Karte auf die Dead-Stage verschoben wird. beleg: "log.warning("kanban %s/%s: card %s → dead (%s)"," kanten: - nutzt: backend/kanban.py::Flow - nutzt: backend/kanban.py::Stage - ruft-auf: backend/database.py::kanban_get_card - ruft-auf: backend/database.py::kanban_fail_card - wird-genutzt-von: backend/kanban.py::_worker ## backend/kanban.py::_worker anker: backend/kanban.py::_run anker: backend/kanban.py::_idle_exit anker: backend/kanban.py::_may_pull 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: - 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" - Hält ein claimed-Set mit Card-IDs, damit parallele Pulls dieselbe Karte nicht doppelt holen. beleg: "claimed: set[str] = set()" - Bei AgentInfraError wird der ganze Flow pausiert (stop + infra_paused), statt zu dead-lettern. beleg: "flow.stop = True" - Barrier-Worker pullen nur, wenn das Gate offen ist und alle Upstream-Stages quiescent sind. beleg: "return await quiescent(flow, spec.upstream)" - Bricht beim Exit alle noch laufenden Package-Tasks ab und sammelt sie mit Exceptions ein. beleg: "await asyncio.gather(*tasks, return_exceptions=True)" kanten: - nutzt: backend/kanban.py::Flow - nutzt: backend/kanban.py::Stage - ruft-auf: backend/kanban.py::quiescent - ruft-auf: backend/kanban.py::_sleep_wake - ruft-auf: backend/kanban.py::_fail_package - ruft-auf: backend/database.py::kanban_pull - ruft-auf: backend/database.py::kanban_count - nutzt: backend/pipeline.py::AgentInfraError - wird-genutzt-von: backend/kanban.py::run_flow ## 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: - Trägt den Flow für die Laufzeit in das Modul-Dict active_flows ein. beleg: "active_flows[flow.topic] = flow" - Startet pro Stage einen Worker mit inflight=1 für serial-Stages, sonst WORKER_INFLIGHT. beleg: "1 if s.serial else WORKER_INFLIGHT," - Startet den Progress-Reporter nur, wenn ein set_p-Callback übergeben wurde. beleg: "progress = asyncio.create_task(_progress(flow, set_p ## 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: - Läuft in einer Schleife, bis das Stop-Flag des Flows gesetzt ist. beleg: "while not flow.stop:" - Holt pro Board die aktuellen Stage-Counts aus der Datenbank. beleg: "counts = await db.kanban_stage_counts(flow.topic)" - 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())" - Reicht die Gesamtzahl als formatierten deutschen String an set_p weiter. beleg: "set_p(f\"Kanban: {total} Karten im Fluss\")" - Schläft eine Sekunde zwischen den Iterationen. beleg: "await asyncio.sleep(1.0)" kanten: - nutzt: backend/kanban.py::Flow - ruft-auf: backend/database.py::kanban_stage_counts - wird-genutzt-von: backend/kanban.py::run_flow