fix: Retry bei stillem <!--END-->-Drop + unterscheidbarer Exit-Code bei Totalverlust (#280, #281) - #297
Merged
Merged
Conversation
…rscheidbarer Exit-Code bei Totalverlust (#280, #281) #280: Wenn der Extractor ein Konzept im Textfenster nicht ausreichend belegt sieht, antwortet er prompt-konform nur mit <!--END--> (kein <!--NOTE-->-Block) -- diese None-Rueckgabe wurde bisher ohne jeden zweiten Versuch verbucht, im Gegensatz zum bestehenden Trunkierungs- Retry (B2). Traf Kernkonzepte in 3 von 6 Testlaeufen (u.a. "Amotivation", [high]-priorisiert bei Deci & Ryan). Fix: derselbe Retry-Gedanke fuer den Empty-Fall -- ein zweiter Call-Versuch mit verstaerktem Hinweis, bevor endgueltig None zurueckgegeben wird. Bleibt auf den Retry selbst beschraenkt; die im Issue als Fix-Richtung (b) separat aufgefuehrte Root-Cause-Untersuchung der Planner-Konzept-zu-Chunk-Zuordnung ist nicht Teil dieses Fixes. #281: Ein Lauf mit Totalverlust (0 Notes trotz >=1 versuchtem Konzept) endete bisher mit "Fertig." und Exit-Code 0 -- ununterscheidbar von einem legitimen Konzeptmangel-Lauf. Luecke: extractor_failure_exit_code() wertet nur harte Exceptions (ctx.extractor_failures), nicht den stillen #280-Drop. Fix: total_loss_exit_code() hebt den Exit-Code auf einen neuen, unterscheidbaren Wert (_EXIT_TOTAL_LOSS=4) wenn 0 finale Notes trotz >=1 versuchtem Konzept vorliegen, plus eine prominente stdout-Warnzeile ("TOTALVERLUST") zusaetzlich zu den bisherigen stderr-Details. Ein legitimer 0-Konzepte-Lauf (nichts versucht) bleibt unveraendert bei Exit 0. Tests: test_extractor_empty_end_retry.py (Retry-Pfad, Retry-Guard im Self-Refine-Loop), test_orchestrator_total_loss_exit_code.py (reine Helper-Faelle + main()-Integration ueber --load-drafts). Volle Suite (generative + lib/decision_engine/tests + shared/tests): 5945 passed, 3 skipped, 8 deselected.
TillQuandel
added a commit
that referenced
this pull request
Jul 17, 2026
…#335) Root-Cause (#280/#297): der Planner weist Konzepten Chunks zu, die die Belegstelle nicht ausreichend enthalten; das 400-Wort-Fenster (concept_text_window) traf sie dann auch nach dem #280-Retry nicht -- der #297-Retry lief auf demselben Fenster und verdoppelte nur die Token-Kosten des Fehlversuchs, ohne die Ursache zu beheben. Fix: bleibt ein Konzept nach Erst-Call UND #280-Retry (extractor. run_per_concept) weiterhin leer, folgt auf Orchestrator-Ebene genau EIN Rescue-Versuch mit deutlich groesserem Fenster (400 -> 1200 Woerter, _RESCUE_WINDOW_WORDS). Neuer Parameter retry_empty=False an run_per_concept unterdrueckt dabei den internen #280-Retry, damit der Rescue max. 1 statt bis zu 2 Zusatz-Calls kostet. Erfolg -> Draft zaehlt normal, kein dropped-Event; Fehlschlag -> weiterhin dropped/ empty_extraction. Kein Rescue-Call, wenn das expandierte Fenster identisch zum urspruenglichen waere (z.B. sehr kurze Dokumente). Log-Signaturen: [extractor-window-rescue] / [extractor-window-rescue-failed]. Co-authored-by: TillQuandel <tillq@live.de>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Zusammenfassung
Kombinierter PR fuer zwei verwandte, aus derselben Testlauf-Serie (2026-07-14) stammende Befunde: ein Konzept faellt still aus dem Extractor-Output (#280), und dieser Verlust ist im Exit-Code/Summary nicht sichtbar (#281). Beide Fixes liegen in derselben Region der Extraction-Pipeline, sind aber unabhaengig voneinander lauffaehig -- daher ein PR, zwei klar getrennte Aenderungsbloecke (extractor.py fuer #280, orchestrator.py fuer #281).
#280 -- Stiller Konzeptverlust via
<!--END-->ohne RetryProblem: Wenn der Extractor ein zugewiesenes Konzept im Textfenster nicht ausreichend belegt sieht, antwortet er prompt-konform nur mit
<!--END-->(kein<!--NOTE-->-Block). DieseNone-Rueckgabe wurde ohne jeden Retry alsdropped/empty_extractionverbucht -- im Gegensatz zum bestehenden Retry-Mechanismus fuer abgeschnittene (truncated) Bodies (B2). Traf Kernkonzepte in 3 von 6 Testlaeufen, u.a. "Amotivation" ([high]-priorisiert bei Deci & Ryan, in keiner der finalen Notes abgedeckt).Ursache:
generative/agents/extractor.py:417-424gab bei leeremitemssofortNonezurueck, ohne den bereits fuer Truncation existierenden Retry-Pfad (extractor.py:428-450) analog zu nutzen.Fix: Ein zweiter Call-Versuch mit verstaerktem Hinweis ("pruefe das Fenster noch einmal sorgfaeltig, auch indirekte Belegstellen zaehlen"), bevor endgueltig
Nonezurueckgegeben wird. Guardrevision_hint is not None -> kein Retry(dieselbe Bedingung wie beim Trunkierungs-Retry) verhindert eine Retry-Kaskade im Self-Refine/Critic-Loop.Bewusst ausgeklammert: Die im Issue als Fix-Richtung (b) separat genannte Root-Cause-Untersuchung der Planner-Konzept-zu-Chunk-Zuordnung (
concept_text_window()) ist NICHT Teil dieses Fixes -- das Issue nennt sie als optionale/investigative Ergaenzung ("ggf."), nicht als Pflichtteil. Der Retry laeuft weiterhin auf demselben Textfenster, das dem Extractor schon beim ersten Versuch vorlag; er behebt den "kein Retry"-Bug, nicht zwangslaeufig Faelle, in denen das Fenster die Belegstelle tatsaechlich nie enthielt.Testnachweis:
test_silent_end_drop_triggers_retry_and_recoversschlug fehl (assert 1 == 2, kein zweiter Call).generative/tests/test_extractor_empty_end_retry.pygruen (Retry-Erfolgsfall, Retry-auch-leer-Fall, Self-Refine-Guard).#281 -- Totalverlust endet mit "Fertig."/Exit 0
Problem: Ein Lauf mit Totalverlust (0 Notes trotz >=1 geplantem/versuchtem Konzept) endete bisher mit der normalen Erfolgsmeldung "Fertig." und Exit-Code 0 -- ununterscheidbar von einem legitimen 0-Konzepte-Lauf (Konzeptmangel in der Quelle). Historischer Beleg: Lauf 1 verlor sein einziges geplantes Konzept durch 2x claude-CLI-Timeout, das einzige Fehlersignal stand nur in stderr.
Live-Luecke im aktuellen Code:
extractor_failure_exit_code()(Issue #210) wertet ausschliesslichctx.extractor_failures-- also nur harte Exceptions (Timeout/CLI-Fehler nach Retries). Der stille<!--END-->-Drop aus #280 (run_per_conceptliefertNoneOHNE Exception) zaehlt zwar indropped_totalmit, faellt aber durch dieses Sieb und liefert weiterhin Exit-Code 0.Fix: Neue Funktion⚠️ TOTALVERLUST: ...") an beiden fruehen Return-Punkten der Extraction-Stage (kein Konzept extrahiert / alle Drafts als Artefakte verworfen), nicht nur stderr-Details wie bisher. Ein legitimer 0-Konzepte-Lauf (
total_loss_exit_code()ingenerative/orchestrator.pyhebt den Exit-Code auf einen neuen, unterscheidbaren Wert_EXIT_TOTAL_LOSS = 4, wenn 0 finale Notes trotz >=1 versuchtem Konzept vorliegen -- unabhaengig davon, ob die Ursache eine Exception (bisher Code 3) oder ein stiller Drop (bisher Code 0) war. Zusaetzlich eine prominente stdout-Warnzeile ("n_attempted == 0) bleibt unveraendert bei Exit 0.Bewusst begrenzter Scope: Die Pruefung greift an den beiden extraction-stufigen frühen Return-Punkten (vor Dedup/Stage-6), NICHT am finalen Vault-Writer-Return. Grund: dort koennte
written == 0auch durch legitimes Dedup entstehen (alle extrahierten Konzepte sind reine Duplikate bereits existierender Vault-Notes) -- das waere ein False-Positive-Totalverlust, kein echter Ausfall.Exit-Code-Kompatibilitaet geprueft:
generative/gui/runner.py/app.py/run_history.pyreichenrcunveraendert durch (kein Enum/Whitelist). Das Frontend (app.js) hat Spezialbehandlung nur fuerrc===0undrc===3; ein neuer Wert (4) faellt in den generischen Fehler-Zweig (markStageError+Fehlercode ${rc}) -- korrektes Verhalten fuer einen Totalverlust, keine Test-Regression (volle Suite inkl.generative/gui/testsgruen).Testnachweis:
test_orchestrator_total_loss_exit_code.pyschlugen mitAttributeErrorfehl (Funktion/Konstante existierten noch nicht); die Gegenprobe (legitimer 0-Konzepte-Lauf) war bereits vorher gruen (Referenzverhalten unveraendert).main()-Integration ueber--load-drafts).Suite + Lint
generative lib/decision_engine/tests shared/tests: 5945 passed, 3 skipped, 8 deselected (keine Fehlschlaege).ruff check+ruff format --checkauf allen 4 geaenderten Dateien: sauber.Geaenderte Dateien
generative/agents/extractor.py-- Retry-Pfad fuer stillen<!--END-->-Drop ([MITTEL] Stiller Konzeptverlust via <!--END--> ohne Retry — traf Kernkonzepte in 3 von 6 Läufen #280)generative/orchestrator.py--total_loss_exit_code()/total_loss_warning_line()+ Wiring an den beiden fruehen Extraction-Return-Punkten ([MITTEL] Lauf mit Totalverlust endet mit "Fertig."/Exit 0 — Fehlersignal nur in stderr #281)generative/tests/test_extractor_empty_end_retry.py-- neugenerative/tests/test_orchestrator_total_loss_exit_code.py-- neuFixes #280
Fixes #281