paperless-ai lief 3.335-mal ins Leere
paperless-ai klassifizierte wochenlang nichts: Der Restrict-Modus unterdrückte still das Tag-Vokabular — 3.335 Aufrufe ohne Wirkung.
Container läuft, Logs sauber, alle 30 Minuten ein LLM-Aufruf — und trotzdem änderte sich über Wochen nichts an unseren Dokumenten. Unser paperless-ai-Dienst war seit dem 05.08.2026 in Betrieb, verarbeitete aber nur Luft: 3.335 LLM-Aufrufe ohne jede Wirkung. Die Ursache war ein stiller Fehler, kein lauter Absturz — zwei Konfigurations- und Code-Probleme, die das System scheinbar gesund erscheinen ließen, während es tatsächlich nichts klassifizierte. Die Geschichte zeigt: Ein grüner Status ist kein Beweis für Wirkung.
Was wir gebaut haben
Der Dienst (clusterzx/paperless-ai, Image vom 17.06.2026) läuft an DeepSeek-V4-Flash, einem lokal betriebenen Modell im Nicht-Denkmodus, und sollte unsere Papierflut automatisch verschlagworten. Das Setup sah solide aus: ein eigener Systemprompt mit klaren Regeln, die Beschränkung auf ein kuratiertes Vokabular von 191 Tags, 812 Korrespondenten und 53 Dokumenttypen. Der Prompt war bewusst defensiv formuliert — „Lieber nichts als ungefähr" als Grundsatz, höchstens drei Tags pro Dokument, ausdrücklich auch keine. Wir hatten uns gegen den verbreitetsten Fehlermodus abgesichert: Bei 107 Tags wählen Modelle 15–20, weil die Menge sich sicherer anfühlt.
Doch genau diese Absicherung wurde unterlaufen. Der Restrict-Modus (RESTRICT_TO_EXISTING_TAGS=yes) war aktiv — und damit unser eigentliches Problem.
Was passierte: zwei Fehler, eine Wirkung
Fehler 1: Der Restrict-Modus unterdrückte das Vokabular stillschweigend. In der customService.js wird das Tag-Vokabular nur dann automatisch in den Prompt injiziert, wenn useExistingData === 'yes' und gleichzeitig restrictToExistingTags === 'no' gilt. Bei uns stand aber RESTRICT_TO_EXISTING_TAGS=yes — also griff der else-Zweig: Der Systemprompt enthielt die Tags nicht. Der einzige Weg, dem Modell die erlaubten Tags zu zeigen, sind die Platzhalter %RESTRICTED_TAGS% und %RESTRICTED_CORRESPONDENTS% im eigenen Prompt.
Fehler 2: Ein Bug lieferte die Tag-Liste leer an. Die Funktion _formatTagsList filterte auf tag.name — aber der Cron-Scan und der „Scan now"-Knopf übergeben vorher existingTags.map(tag => tag.name), also bereits Strings. Ergebnis: Der Filter warf alles weg, %RESTRICTED_TAGS% blieb leer, das Modell erfand Tags, die beim Schreiben verworfen wurden. Nur die Weboberfläche (processQueue) übergab Objekte und funktionierte — ein gespiegeltes Gegenstück zu Issue #600, wo [object Object] statt der Namen ankam.
Die Kombination war fatal: Das Modell sah keine erlaubten Tags, erfand eigene, und die Schreib-Beschränkung (paperlessService.js) verwarf diese stillschweigend. Kein Fehler, kein Log-Eintrag — nur 3.335 Aufrufe ins Leere.
Was wir gelernt haben
Container-Status ist kein Wirkungsnachweis. „Läuft" bedeutet nur: Der Prozess ist aktiv. Ob er das Richtige tut, zeigt erst die Messung der Ergebnisse. Wir hatten die Prozesse beobachtet (Logs, Status, Aufrufzahlen), aber nicht die Wirkung (Klassifikationen, geänderte Dokumente). Erst als wir die tatsächlichen Änderungen am Dokumentenbestand prüften, wurde der Fehler sichtbar.
Stille Fehler sind teurer als laute. Ein Absturz ist sofort sichtbar, ein Fehlschlag in der Logik nicht. Unsere 3.335 nutzlosen LLM-Aufrufe kosteten Rechenzeit auf unserer eigenen Hardware — und hätten unbegrenzt weiterlaufen können, hätte nicht jemand die Wirkung geprüft.
Die Messung muss die Funktion prüfen, nicht den Prozess. Wir haben daraus eine Betriebsregel gemacht: Vor der Scharfschaltung testen wir gegen bekannte Referenzfälle. Der Trockenlauf gegen 13 handgeprüfte Dokumente zeigte: 11 von 13 mit Tags ausschließlich aus dem Vokabular, nie mehr als drei Tags, Übereinstimmung in allen sechs Fällen, die als fehlklassifiziert markiert waren.
Der Weg zur Scharfschaltung
Nach der Korrektur der zwei Patches (als Mount, nicht im Image) und dem injizierten Vokabular (191 Tags, 812 Korrespondenten, 53 Dokumenttypen ≈ 5,4k Token je Dokument) wurde der Dienst am 05.08.2026 scharfgeschaltet:
ACTIVATE_TAGGING=yes ACTIVATE_TITLE=no
ACTIVATE_CORRESPONDENTS=yes ACTIVATE_CUSTOM_FIELDS=no
ACTIVATE_DOCUMENT_TYPE=yes
Der erste scharfe Lauf über drei Dokumente mit bekannter Wahrheit traf drei von drei. Der Vollauf über den Bestand (3.330 Markierungen) wurde als Einmallauf um 02:00 eingerichtet — und dann in der Nacht zum 06.08. nach 452 Dokumenten von Sebastian gestoppt: Der Lauf erwies sich als Tageslauf (rund 14 Stunden), nicht als Nachtlauf. Die Qualität der 444 auswertbaren Änderungen war gut: 0 ohne Dokumenttyp, 15 von 15 Datumsprüfungen korrekt, wo der Titel ein Datum trug.
Fazit
Die Fehlkonfiguration wurde erst entdeckt, als wir Wirkung statt Prozesse maßen. Ein System, das regelmäßig läuft und nichts tut, ist kein System — es ist eine Kostenstelle. Wir messen seitdem nicht mehr „läuft der Dienst?", sondern „verändert der Dienst die Dokumente korrekt?" — und prüfen vor jedem Vollauf die Messbedingungen, statt blind auf Container-Status zu vertrauen.
Dieselbe Lehre von der anderen Seite: Ein einzelner erfolgreicher Testlauf beweist noch weniger als ein grüner Container-Status — warum wir seitdem viermal hintereinander messen, bevor wir Vollzug melden.