diff --git a/backend/board_inventory.py b/backend/board_inventory.py
index 1303a1d..f8902e5 100644
--- a/backend/board_inventory.py
+++ b/backend/board_inventory.py
@@ -1714,6 +1714,11 @@ async def run_boards(ctx: GenContext, set_p, files: dict, q: dict, folder, instr
watcher.cancel()
if ctx.is_cancelled():
return False
+ if flow.state.get("infra_paused"):
+ from blocks import _blocks_errors # lazy: Import-Zyklus vermeiden
+ _blocks_errors[topic] = flow.state.get("infra_error") \
+ or "Pausiert: API-Ratelimit (429) — «Fortsetzen», sobald Kontingent zurück"
+ return True # kein Fehler: Karten warten resümierbar in ihrer Spalte
if flow.state.get("qa_paused"):
return True # kein Fehler: Flow hielt am QA-Gate, Karten warten in generate
await _write_final(topic, files)
diff --git a/backend/config.py b/backend/config.py
index b627c06..c18346f 100644
--- a/backend/config.py
+++ b/backend/config.py
@@ -193,6 +193,8 @@ KANBAN_BATCH = 5 # cards a worker pulls per micro-batch
MAX_CARD_RETRIES = 3 # failures per card → dead-letter
RETRY_BACKOFF = 30.0 # base seconds; backoff = base · 2^(retries-1)
MAX_RESTARTS = 2 # agent restart cap per race slot
+INFRA_MAX_RETRIES = 3 # 429/Timeout/Netz: Retries pro Slot, dann Lauf-Pause (kein fail-open)
+INFRA_BACKOFF_BASE = 8.0 # Pause = base · 2^(n-1) → 8/16/32 s
# Stall-Hedge: läuft ein Race-Slot so lange ohne Ergebnis, startet parallel ein Zwilling
# (key -h), der erste valide gewinnt. Gemessen (kanban-smoke): 4 Panel-Stalls à 160–230 s
# verlängerten den kritischen Pfad um ~5 min. UNTERGRENZE: effektiv gilt
diff --git a/backend/kanban.py b/backend/kanban.py
index bebcd0e..8b78586 100644
--- a/backend/kanban.py
+++ b/backend/kanban.py
@@ -19,6 +19,7 @@ import logging
import database as db
from config import KANBAN_BATCH, MAX_CARD_RETRIES, MAX_CONCURRENT_AGENTS_PER_TOPIC, RETRY_BACKOFF
+from pipeline import AgentInfraError
log = logging.getLogger("creator.kanban")
@@ -165,6 +166,12 @@ async def _worker(flow: Flow, spec: Stage, inflight: int, all_stages: list[str])
flow.active_cards.update(f"{spec.board}:{i}" for i in ids)
try:
await spec.process(cards)
+ except AgentInfraError as e: # 429/Timeout erschöpft → Lauf pausieren, NICHT dead-letter
+ log.warning("kanban %s/%s: infra-pause: %s", topic, spec.stage, e)
+ flow.state["infra_paused"] = True
+ flow.state["infra_error"] = str(e)
+ flow.stop = True
+ flow.wake.set()
except Exception as e: # one bad package must not kill the worker → backoff/dead-letter
log.info("kanban %s/%s: %s: %s", topic, spec.stage, type(e).__name__, e)
try:
diff --git a/backend/pipeline.py b/backend/pipeline.py
index e99856e..8fc0739 100644
--- a/backend/pipeline.py
+++ b/backend/pipeline.py
@@ -77,6 +77,15 @@ def _claude_error(label: str, returncode: int, stdout: str, stderr: str) -> str:
return f"{label} (exit {returncode}, no output)"
+_INFRA_MARKERS = ("HTTP 429", "HTTP 5", "rate_limit", "Timeout after",
+ "ConnectError", "ConnectTimeout", "ReadError", "RemoteProtocolError")
+
+
+def _is_infra(err: str) -> bool:
+ """Transport-/Infra-Fehler (retry + pause) statt inhaltlichem Fehlschlag."""
+ return any(m in (err or "") for m in _INFRA_MARKERS)
+
+
def _gather_error(label: str, results: list) -> str:
for r in results:
if isinstance(r, BaseException):
@@ -158,7 +167,8 @@ def _enum_map_schema(key: str, allowed):
_yesno_schema = _enum_map_schema("relevant", _YESNO) # triage gate ∈ ja/nein
-from config import MAX_RESTARTS as _MAX_RESTARTS, HEDGE_NACH_S as _HEDGE_NACH_S # noqa: E402 — zentral tunebar
+from config import (MAX_RESTARTS as _MAX_RESTARTS, HEDGE_NACH_S as _HEDGE_NACH_S, # noqa: E402 — zentral tunebar
+ INFRA_MAX_RETRIES as _INFRA_MAX_RETRIES, INFRA_BACKOFF_BASE as _INFRA_BACKOFF)
async def _race(topic: str, label: str, slots: list[dict], quorum: int, timeout: int, provider: str, cancelled=None, *, grace: int | None = None) -> list | None:
@@ -176,6 +186,8 @@ async def _race(topic: str, label: str, slots: list[dict], quorum: int, timeout:
keeps running until it stands. Returns: `quorum` to `len(slots)` results.
"""
attempts = {i: 0 for i in range(len(slots))}
+ infra_attempts = {i: 0 for i in range(len(slots))}
+ infra_erschoepft: set[int] = set()
tasks: dict[asyncio.Task, int] = {}
keys: dict[asyncio.Task, str] = {}
born: dict[asyncio.Task, float] = {}
@@ -267,17 +279,32 @@ async def _race(topic: str, label: str, slots: list[dict], quorum: int, timeout:
return results
continue
- _log(topic, f"{label} {i + 1} (attempt {attempts[i] + 1}): {err}")
+ infra = _is_infra(err)
+ _log(topic, f"{label} {i + 1} (attempt {attempts[i] + 1}{' infra' if infra else ''}): {err}")
attempts[i] += 1
# If the minimum already stands, restarts are pointless — the restart
# would be killed at the grace end anyway. A still-running twin IS the retry.
enough = grace is not None and len(results) >= quorum
zwilling = any(i2 == i for i2 in tasks.values())
- if attempts[i] <= _MAX_RESTARTS and not enough and not zwilling and not (cancelled and cancelled()):
+ if enough or zwilling or (cancelled and cancelled()):
+ continue
+ if infra:
+ # 429/Timeout/Netz: eigener Zähler + wachsende Pause, dann Eskalation
+ infra_attempts[i] += 1
+ if infra_attempts[i] > _INFRA_MAX_RETRIES:
+ infra_erschoepft.add(i) # kein Respawn → raise am Quorum-Miss
+ continue
+ await asyncio.sleep(_INFRA_BACKOFF * 2 ** (infra_attempts[i] - 1))
+ spawn(i)
+ elif attempts[i] <= _MAX_RESTARTS:
spawn(i)
if len(results) >= quorum: # all slots done, minimum stands (only reachable with grace)
return results
_log(topic, f"{label}: quorum {quorum} not reached ({len(results)} valid)")
+ if infra_erschoepft:
+ raise AgentInfraError(
+ f"{label}: {len(infra_erschoepft)} Slot(s) nach {_INFRA_MAX_RETRIES} "
+ "Infra-Retries erschöpft (429/Timeout)")
return None
finally:
for task, i in tasks.items():
@@ -300,6 +327,11 @@ class GenContext:
OK, CANCELLED, FAILED = "ok", "cancelled", "failed"
+class AgentInfraError(Exception):
+ """Slot nach Infra-Retries erschöpft (429/Timeout). KEIN inhaltliches Urteil —
+ der Aufrufer pausiert den Lauf, nie fail-open."""
+
+
async def run_single_slot(
ctx: GenContext, label: str, *,
key: str, prompt: str, role: str, capabilities: str, payload, timeout: int, on_line=None,
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index ee0cf2e..9cdd144 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -500,6 +500,7 @@ onMounted(async () => {
:progress="blocks.progress"
:ready="blocks.ready"
:partial="blocks.partial"
+ :error="blocks.error"
:guideFormat="guideBoardFormat"
@close="mainView = 'blocks'"
@resetStage="handleResetStage"
diff --git a/frontend/src/components/GenerationView.vue b/frontend/src/components/GenerationView.vue
index 7b263de..03ffc7a 100644
--- a/frontend/src/components/GenerationView.vue
+++ b/frontend/src/components/GenerationView.vue
@@ -14,6 +14,7 @@ const props = defineProps({
progress: { type: String, default: null },
ready: { type: Boolean, default: false },
partial: { type: Boolean, default: false },
+ error: { type: String, default: null },
guideFormat: { type: String, default: 'Guide' },
})
const emit = defineEmits(['close', 'resetStage', 'restartAll', 'continueAll', 'addResearch',
@@ -203,9 +204,10 @@ async function repairClick() {
diff --git a/uni/aak/alle_serien_2026.pdf b/uni/aak/alle_serien_2026.pdf
new file mode 100644
index 0000000..b511569
--- /dev/null
+++ b/uni/aak/alle_serien_2026.pdf
@@ -0,0 +1,10030 @@
+%PDF-1.3
+%
+1 0 obj
+<< /Metadata 3 0 R /Pages 4 0 R /Type /Catalog >>
+endobj
+2 0 obj
+<< /Author () /CreationDate (D:20260702123537+00'00') /Creator (LaTeX with hyperref) /ModDate (D:20260702123537+00'00') /Producer (pdfTeX-1.40.27) >>
+endobj
+3 0 obj
+<< /Subtype /XML /Type /Metadata /Length 809 >>
+stream
+
+
+
+
+
+
+
+
+endstream
+endobj
+4 0 obj
+<< /Count 23 /Kids [ 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 23 0 R 24 0 R 25 0 R 26 0 R 27 0 R ] /Type /Pages >>
+endobj
+5 0 obj
+<< /Annots 28 0 R /Contents 29 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 30 0 R /ExtGState 31 0 R /Font << /F59 32 0 R /F88 33 0 R /F89 34 0 R /F90 35 0 R /F91 36 0 R /F95 37 0 R /F96 38 0 R /F98 39 0 R /F99 40 0 R >> /Pattern 41 0 R /ProcSet [ /PDF /Text /ImageC /ImageI ] /XObject << /Im1 42 0 R /Im2 43 0 R >> >> /Type /Page >>
+endobj
+6 0 obj
+<< /Annots 44 0 R /Contents 45 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 46 0 R /ExtGState 47 0 R /Font << /F107 48 0 R /F59 49 0 R /F88 50 0 R /F89 51 0 R /F90 52 0 R /F91 53 0 R /F95 54 0 R /F96 55 0 R /F98 56 0 R /F99 57 0 R >> /Pattern 58 0 R /ProcSet [ /PDF /Text /ImageC /ImageI ] /XObject << /Im1 59 0 R >> >> /Type /Page >>
+endobj
+7 0 obj
+<< /Annots 60 0 R /Contents 61 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 62 0 R /ExtGState 63 0 R /Font << /F133 64 0 R /F134 65 0 R /F135 66 0 R /F136 67 0 R /F139 68 0 R /F140 69 0 R /F142 70 0 R /F144 71 0 R /F145 72 0 R /F146 73 0 R /F59 74 0 R >> /Pattern 75 0 R /ProcSet [ /PDF /Text /ImageC /ImageI ] /XObject << /Im1 76 0 R >> >> /Type /Page >>
+endobj
+8 0 obj
+<< /Annots 77 0 R /Contents 78 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 79 0 R /ExtGState 80 0 R /Font << /F102 81 0 R /F85 82 0 R /F87 83 0 R /F88 84 0 R /F89 85 0 R /F90 86 0 R /F94 87 0 R /F96 88 0 R /F98 89 0 R >> /Pattern 90 0 R /ProcSet [ /PDF /Text /ImageC /ImageI ] /XObject << /Im1 91 0 R >> >> /Type /Page >>
+endobj
+9 0 obj
+<< /Contents 92 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 93 0 R /ExtGState 94 0 R /Font << /F103 95 0 R /F109 96 0 R /F112 97 0 R /F59 98 0 R /F88 99 0 R /F89 100 0 R /F90 101 0 R /F91 102 0 R /F95 103 0 R /F96 104 0 R >> /Pattern 105 0 R /ProcSet [ /PDF /Text /ImageC /ImageI ] /XObject << /Im1 106 0 R >> >> /Type /Page >>
+endobj
+10 0 obj
+<< /Contents 107 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 108 0 R /ExtGState 109 0 R /Font << /F59 110 0 R /F87 111 0 R /F88 112 0 R /F89 113 0 R /F90 114 0 R /F94 115 0 R /F95 116 0 R /F96 117 0 R /F97 118 0 R /F98 119 0 R >> /Pattern 120 0 R /ProcSet [ /PDF /Text /ImageC /ImageI ] /XObject << /Im1 121 0 R >> >> /Type /Page >>
+endobj
+11 0 obj
+<< /Contents 122 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 123 0 R /ExtGState 124 0 R /Font << /F101 125 0 R /F59 126 0 R /F88 127 0 R /F89 128 0 R /F90 129 0 R /F91 130 0 R /F95 131 0 R /F96 132 0 R /F97 133 0 R /F98 134 0 R >> /Pattern 135 0 R /ProcSet [ /PDF /Text /ImageC /ImageI ] /XObject << /Im1 136 0 R >> >> /Type /Page >>
+endobj
+12 0 obj
+<< /Contents 137 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 138 0 R /ExtGState 139 0 R /Font << /F59 140 0 R /F83 141 0 R /F87 142 0 R /F88 143 0 R /F89 144 0 R /F90 145 0 R /F94 146 0 R /F95 147 0 R /F96 148 0 R /F97 149 0 R /F98 150 0 R >> /Pattern 151 0 R /ProcSet [ /PDF /Text /ImageC /ImageI ] /XObject << /Im1 152 0 R >> >> /Type /Page >>
+endobj
+13 0 obj
+<< /Contents 153 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 138 0 R /ExtGState 139 0 R /Font << /F89 144 0 R /F94 146 0 R /F95 147 0 R /F96 148 0 R /F97 149 0 R /F98 150 0 R >> /Pattern 151 0 R /ProcSet [ /PDF /Text ] >> /Type /Page >>
+endobj
+14 0 obj
+<< /Contents 154 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 155 0 R /ExtGState 156 0 R /Font << /F100 157 0 R /F101 158 0 R /F102 159 0 R /F106 160 0 R /F110 161 0 R /F111 162 0 R /F112 163 0 R /F99 164 0 R >> /Pattern 165 0 R /ProcSet [ /PDF /Text /ImageC /ImageI ] /XObject << /Im1 166 0 R >> >> /Type /Page >>
+endobj
+15 0 obj
+<< /Contents 167 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 168 0 R /ExtGState 169 0 R /Font << /F103 170 0 R /F109 171 0 R /F112 172 0 R /F59 173 0 R /F88 174 0 R /F89 175 0 R /F90 176 0 R /F91 177 0 R /F95 178 0 R /F96 179 0 R >> /Pattern 180 0 R /ProcSet [ /PDF /Text /ImageC /ImageI ] /XObject << /Im1 181 0 R >> >> /Type /Page >>
+endobj
+16 0 obj
+<< /Contents 182 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 168 0 R /ExtGState 169 0 R /Font << /F103 170 0 R /F109 171 0 R /F112 172 0 R /F90 176 0 R /F91 177 0 R /F95 178 0 R /F96 179 0 R >> /Pattern 180 0 R /ProcSet [ /PDF /Text ] >> /Type /Page >>
+endobj
+17 0 obj
+<< /Contents 183 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 184 0 R /ExtGState 185 0 R /Font << /F100 186 0 R /F59 187 0 R /F89 188 0 R /F90 189 0 R /F91 190 0 R /F92 191 0 R /F96 192 0 R /F97 193 0 R /F99 194 0 R >> /Pattern 195 0 R /ProcSet [ /PDF /Text /ImageC /ImageI ] /XObject << /Im1 196 0 R >> >> /Type /Page >>
+endobj
+18 0 obj
+<< /Contents 197 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 184 0 R /ExtGState 185 0 R /Font << /F100 186 0 R /F59 187 0 R /F91 190 0 R /F92 191 0 R /F96 192 0 R /F97 193 0 R /F98 198 0 R /F99 194 0 R >> /Pattern 195 0 R /ProcSet [ /PDF /Text ] >> /Type /Page >>
+endobj
+19 0 obj
+<< /Contents 199 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 184 0 R /ExtGState 185 0 R /Font << /F100 186 0 R /F91 190 0 R /F97 193 0 R /F99 194 0 R >> /Pattern 195 0 R /ProcSet [ /PDF /Text ] >> /Type /Page >>
+endobj
+20 0 obj
+<< /Contents 200 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 201 0 R /ExtGState 202 0 R /Font << /F101 203 0 R /F59 204 0 R /F88 205 0 R /F89 206 0 R /F90 207 0 R /F91 208 0 R /F95 209 0 R /F96 210 0 R /F97 211 0 R >> /Pattern 212 0 R /ProcSet [ /PDF /Text /ImageC /ImageI ] /XObject << /Im1 213 0 R >> >> /Type /Page >>
+endobj
+21 0 obj
+<< /Contents 214 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 201 0 R /ExtGState 202 0 R /Font << /F101 203 0 R /F59 204 0 R /F90 207 0 R /F91 208 0 R /F95 209 0 R /F96 210 0 R /F97 211 0 R /F98 215 0 R >> /Pattern 212 0 R /ProcSet [ /PDF /Text ] >> /Type /Page >>
+endobj
+22 0 obj
+<< /Contents 216 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 217 0 R /ExtGState 218 0 R /Font << /F83 219 0 R /F87 220 0 R /F88 221 0 R /F89 222 0 R /F90 223 0 R /F94 224 0 R /F95 225 0 R /F97 226 0 R /F98 227 0 R >> /Pattern 228 0 R /ProcSet [ /PDF /Text /ImageC /ImageI ] /XObject << /Im1 229 0 R >> >> /Type /Page >>
+endobj
+23 0 obj
+<< /Contents 230 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 217 0 R /ExtGState 218 0 R /Font << /F111 231 0 R /F59 232 0 R /F89 222 0 R /F90 223 0 R /F94 224 0 R /F95 225 0 R /F96 233 0 R /F97 226 0 R /F98 227 0 R >> /Pattern 228 0 R /ProcSet [ /PDF /Text ] >> /Type /Page >>
+endobj
+24 0 obj
+<< /Contents 234 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 217 0 R /ExtGState 218 0 R /Font << /F89 222 0 R /F94 224 0 R /F95 225 0 R /F96 233 0 R /F97 226 0 R /F98 227 0 R >> /Pattern 228 0 R /ProcSet [ /PDF /Text ] >> /Type /Page >>
+endobj
+25 0 obj
+<< /Contents 235 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 236 0 R /ExtGState 237 0 R /Font << /F100 238 0 R /F101 239 0 R /F102 240 0 R /F103 241 0 R /F107 242 0 R /F111 243 0 R /F112 244 0 R /F113 245 0 R >> /Pattern 246 0 R /ProcSet [ /PDF /Text /ImageC /ImageI ] /XObject << /Im1 247 0 R >> >> /Type /Page >>
+endobj
+26 0 obj
+<< /Contents 248 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 236 0 R /ExtGState 237 0 R /Font << /F102 240 0 R /F107 242 0 R /F111 243 0 R /F112 244 0 R /F113 245 0 R >> /Pattern 246 0 R /ProcSet [ /PDF /Text ] >> /Type /Page >>
+endobj
+27 0 obj
+<< /Contents 249 0 R /MediaBox [ 0 0 595.276 841.89 ] /Parent 4 0 R /Resources << /ColorSpace 236 0 R /ExtGState 237 0 R /Font << /F102 240 0 R /F107 242 0 R /F112 244 0 R /F113 245 0 R >> /Pattern 246 0 R /ProcSet [ /PDF /Text ] >> /Type /Page >>
+endobj
+28 0 obj
+[ 250 0 R 251 0 R 252 0 R ]
+endobj
+29 0 obj
+<< /Filter /FlateDecode /Length 2155 >>
+stream
+xY[s۶~#4 3A%4V2D}-XX\J(Q2XpBYDW'dK#*Β$J9Hɯ'HLi┚.jɢ:tf;vO'?\fYD8<˲2i\Di_
GoNo.^o]}RA&&gx3e#Gѐ1ϬjݔͦodB&"Mj[y%ˢ)_ؖQ}f=0JIyzҮm1[ٺ/uek7 \>-4Z6$
N+DىIIy[|s
#}ҼU?8%EUV}ћF]QU~nA)KX