Die letzten 20 % eines mit KI gebauten SaaS-Produkts

10 Min. Lesezeit

Die Oberflächen funktionieren. Das System hat Fragen.

17.10.2026, Von Stephan Schwab

Stephan Schwab

Eine mit KI gebaute Anwendung kann fast fertig aussehen, während die schwierigen Fragen gerade erst auftauchen. Bei der Entwicklung unseres SaaS-Produkts Nilo mit KI-Unterstützung wird diese Lücke immer wieder greifbar. Die Oberflächen funktionieren. Dann verstehen zwei Versionen dasselbe Ereignis unterschiedlich, eine Abo-Verlängerung vergisst den gewählten Tarif oder der Ausfall einer optionalen Datenbank legt eine nützliche Seite lahm. Die restliche Arbeit steckt in Wechselwirkungen, die keine Demo durchspielt. Sie zu erkennen und plausible Lösungen zu hinterfragen, verlangt mehr als die passende Aufforderung für die nächste Funktion.

Eine Prüflampe beleuchtet getrennte blaue und grüne Module unter einer fertigen Anwendungsoberfläche, neben einem runden Mechanismus und einem verschlossenen Datenfach.

In Wenn Vibe Coding auf einen echten Entwickler trifft beschrieb ich Anwendungen, die Menschen mit KI gebaut hatten, bevor sie einen erfahrenen Entwickler hinzuzogen. Sie waren beeindruckend weit gekommen. Ihre Anwendungen enthielten allerdings auch Probleme, die sie gar nicht als solche erkennen konnten.

Nilo zeigt mir die andere Seite. Auch wir nutzen KI, um es zu entwickeln. Wir haben 2026. Einen Entwickler alles mühsam tippen zu lassen, würde vor allem eine Vorliebe fürs Tippen beweisen.

Die interessanten Belege stecken in meinen Gesprächen mit Codex: in den Fragen nach der Implementierung, den Einwänden nach der Erklärung und den scheinbar kleinen Änderungen, die quer durch das Produkt reichen.

Die „letzten 20 %“ sind eine Kurzform, keine Messung von Nilos Fortschritt. Gemeint ist die Arbeit, die Menschen entdecken, nachdem die Anwendung bereits überzeugend aussieht. Niemand verspricht, dass sie nur 20 % des Aufwands kostet. Prozentzahlen beruhigen ungemein, solange niemand den Nenner gemessen hat.

Blue/Green, bis die beiden Farben miteinander reden

Eine gesunde neue Version kann die alte bereits stören, bevor sie auch nur eine einzige Kundenanfrage erhält.

Am 3. Oktober tauchten nach einem Deployment mehr Fehler auf. Ich bat Codex, die zentralen Logs zu untersuchen, in denen die Meldungen der laufenden Anwendungsinstanzen zusammenkommen.

Wir hatten bereits Blue/Green-Deployment. Die übliche Erklärung: Eine Version bedient die Kunden, während eine zweite daneben startet. Den Kandidaten prüfen, den Verkehr umschalten, die alte Version abschalten. Sehr ordentlich. Zwei Farben, ein Pfeil, eine hervorragende Folie.

Die Untersuchung ergab, dass die neue Nilo-Version eine neue Art interner Ereignisse veröffentlichte: tasks.board. Die alte Version kannte diesen Ereignistyp nicht. Beide Versionen nutzten dieselbe Ereignisinfrastruktur. Deshalb empfingen die alten Instanzen die neuen Ereignisse und wiesen sie zurück.

Beide Versionen konnten weiterhin ihre Healthchecks bestehen. Die neue war erfolgreich gestartet. Die alte beantwortete weiterhin Anfragen. Diese Tatsachen belegten nicht, dass beide sicher nebeneinander laufen konnten.

Die Fehler hörten auf, als die alten Instanzen herunterfuhren. Codex stellte das zunächst als beruhigend dar: Die Fehlerwelle sei vorbei, die aktuellen Instanzen funktionierten.

Ich fragte:

So soll Blue/Green-Deployment doch nicht funktionieren, oder?

Diese Frage änderte die Schlussfolgerung. Dass die Fehler verschwanden, bewies das Ende der Überlappung. Es bewies keinen korrekten Übergang.

Unsere Implementierung kontrollierte den eingehenden Webverkehr. Sie kontrollierte die Hintergrundaktivität zwischen den beiden Versionen nicht ausreichend. Mit dem Start des Kandidaten begann auch Arbeit, die das laufende System beeinflussen konnte.

Wer nur die Website betrachtet, sieht das womöglich nie. Auch ein Entwickler, der sich auf das Umschalten des Verkehrs konzentriert, kann es übersehen. Der Fehler überschritt eine Grenze zwischen Deployment, Hintergrundverarbeitung und Anwendungsereignissen.

„Fertig“ muss auch die unsichtbare Arbeit einschließen

Das Umschalten des Webverkehrs erledigt keine bereits laufende Arbeit. Ein unterbrochener E-Mail-Vorgang kann trotzdem eine Nachricht senden, die die Anwendung noch nicht erfasst hat.

Ich fragte dann, wie wir den Übergang verbessern könnten, ohne häufige Deployments und schnelle Entwicklung mühsam zu machen.

Damit kamen wir zum Draining: keine neue Arbeit beginnen, bereits laufende Arbeit zu einem bekannten Ergebnis bringen und dann den Prozess beenden.

Bei einer einfachen Website kommt man mit dem Zählen aktiver Anfragen vielleicht schon recht weit. Nilo hat außerdem zeitgesteuerte Aufgaben, Postfachaktivität, asynchrone Vorgänge und dauerhafte Verbindungen. Eine Anfrage kann abgeschlossen sein, während die von ihr angestoßene Arbeit anderswo weiterläuft.

Nehmen wir einen Vorgang, der eine E-Mail sendet. Der externe Maildienst kann sie kurz vor dem Stopp der Anwendung annehmen, während die Anwendung das Ergebnis noch nicht gespeichert hat. Nach dem Neustart könnte ein erneuter Versuch die Nachricht doppelt senden. Den Thread zu beenden sagt nichts darüber aus, ob der Empfänger die Nachricht bekommt.

Der beobachtete Fehler war das inkompatible Ereignis. Die E-Mail beschreibt ein Fehlerszenario, das der Übergang abdecken muss: Vor einem erneuten Versuch feststellen, ob ein unterbrochener Vorgang erfolgreich war. Das Routing des Verkehrs allein kann das nicht beantworten.

Der Kandidat muss starten können, ohne vorzeitig produktive Arbeit zu erledigen. Der alte Prozess muss aufhören, neue Arbeit anzunehmen, und die bereits angenommene abschließen. Ereignisempfänger und dauerhafte Verbindungen brauchen ein geordnetes Ende. Zeitüberschreitungen brauchen eine ausdrücklich festgelegte Konsequenz.

Eine feste Wartezeit belegt all das nicht. Eine Minute zu warten beweist, dass eine Minute vergangen ist. Die Anwendung kann nach zwei Sekunden fertig gewesen sein oder noch immer auf einen externen Dienst warten.

Das Etikett Blue/Green macht inkompatible Versionen nicht sicher nebeneinander betreibbar. Während der Überlappung braucht es kompatible Ereignisse oder einen kontrollierten Übergang, der verhindert, dass beide Versionen sie gleichzeitig verarbeiten. Eine kurze Schreibpause kann der ehrliche Preis dafür sein, interne Schnittstellen leicht ändern zu können.

Das sind Produkt- und Betriebsentscheidungen, versteckt in etwas, das wie ein Deployment-Skript aussah.

Auch die Lösung kann zu clever sein

Die erste plausible Lösung verdient dieselbe kritische Prüfung wie die ursprüngliche Implementierung.

Codex schlug ausdrückliche Release-Zustände, Freigaben für die Arbeitsausführung und einen Datensatz in Mongo zur Kennzeichnung der Deployment-Generation vor. Die Erklärung war schlüssig. Sie verlagerte aber auch die Deployment-Orchestrierung in die Anwendung.

Ich fragte:

Die Anwendung weiß also über den Deployment-Prozess Bescheid? Das klingt nicht richtig.

Dann:

Das ist doch nichts Neues. Ich würde annehmen, dass es dafür im Spring-Ökosystem schon eine Lösung gibt.

Die Untersuchung wandte sich den vorhandenen Lifecycle-Funktionen von Spring zu, dem Framework, das Nilo nutzt. Die sinnvolle Grenze wurde deutlicher: Die Deployment-Infrastruktur koordiniert Releases und Routing; die Anwendungskomponenten beteiligen sich am geordneten Starten und Herunterfahren.

Das erfordert weiterhin Integration. Ein automatisch startender Hintergrund-Listener muss am Lifecycle teilnehmen. Vorhandene Framework-Unterstützung bindet sich nicht von selbst in jede eigene Komponente ein. Sie zuerst zu prüfen verändert aber, wie viel Mechanik wir neu erfinden müssen und wohin sie gehört.

Am 3. Oktober war die Arbeit noch im Gang. Die bessere Richtung brauchte weiterhin eine Implementierung und den Nachweis im Produktionsbetrieb.

Dieser Austausch ist relevant, weil die KI durchgehend nützlich war. Sie verfolgte den Fehler und entwickelte konkrete Vorschläge. Sie lieferte aber auch eine zunächst beruhigende Deutung und danach eine Lösung, deren architektonische Folgen Widerspruch verlangten.

Zur Arbeit gehörte, die Erklärung zu bewerten, statt nur den Patch zu akzeptieren.

Ein Abo ist eine Zusage, die auch nächsten Monat gilt

Eine erfolgreiche Zahlung beweist, dass Geld geflossen ist. Sie beweist nicht, dass die nächste Verlängerung den vom Kunden gewählten Tarif beibehält.

Ein früheres Gespräch begann damit, bestimmte Credits nicht mehr verfallen zu lassen. Ich nannte das sogar „wahrscheinlich eine sehr kleine Änderung“. Berühmte letzte Worte.

Nilo hat bezahlte Credits, vom Betreiber gewährte Guthaben und wiederkehrende Dienste, die Credits verbrauchen. Ein Testkunde kann eine Gutschrift erhalten und später zahlender Kunde werden. Der angezeigte Saldo allein erklärt nicht, was verfallen soll, was ein Betreiber widerrufen darf oder was eine Verlängerung bereitstellen muss.

Wir mussten deshalb die Quellen unterscheiden sowie Gutschriften und deren Verwendung erfassen. Dann kam eine weitere Frage:

Gibt es etwas, das verhindert, dass uns die Credits völlig ausgehen? Wir haben wiederkehrende Abbuchungen.

Und die Geschäftsregel:

Solange Produkte mit wiederkehrenden Abbuchungen aktiv sind, darf es nicht möglich sein, ein zu kleines Abo zu wählen.

Einen Button zu deaktivieren hilft der Person auf der Abrechnungsseite. Die Regel muss aber auch im Dienst gelten, der das Abo ändert, bevor er beim Zahlungsanbieter irgendetwas verändert.

Die Untersuchung legte ein verwandtes Problem bei der Verlängerung offen: Der gewählte Tarif musste gespeichert und bei der Verlängerung verwendet werden, statt auf den kleinsten Tarif zurückzufallen.

Eine Vorführung der Anmeldung spielt die Verlängerung im nächsten Monat nicht durch. Eine Zahlung kann erfolgreich sein, während das Produkt die Zusage nicht einhält, die der Kunde gekauft zu haben glaubt.

Die Preisseite war sichtbar. Die Zusage verteilte sich auf das Credit-Modell, Abo-Änderungen, Zahlungsbenachrichtigungen und die Verarbeitung der Verlängerung. Dort steckte die restliche Arbeit.

Einen Fehler verschwinden zu lassen kann die falsche Lösung sein

Unabhängige Funktionen müssen weiterarbeiten, wenn optionale Daten ausfallen. Datensätze umzuschreiben oder Zugriffsgrenzen aufzuweichen beseitigt das eigentliche Problem nicht.

Ein Problem auf der Kontaktseite entstand durch nicht verfügbaren persönlichen Speicher. Nilo hält persönliche kontaktbezogene Daten getrennt von Unternehmenskontakten. Die Unternehmenskontakte konnten weiterhin verfügbar sein, obwohl die persönliche Datenbank es nicht war.

Während der Untersuchung schlug Codex einen Reparaturweg ein, den ich ablehnte. Die Entwicklungsdatenbank enthielt gemischte Daten und inkonsistente Referenzen. Gefordert war, den Ausfall der Abhängigkeit zu begrenzen, statt andere gespeicherte Datensätze umzuschreiben, damit die Ausnahme verschwindet.

Meine Korrektur enthielt:

Aber NIEMALS die Mongo-Datenbank verändern.

Aus Kundensicht war das gewünschte Verhalten einfach: Unternehmenskontakte anzeigen, erklären, dass persönliche Informationen nicht verfügbar sind, und unabhängige Funktionen weiterarbeiten lassen.

Dafür mussten wir die Speichergrenze erhalten, den konkreten Fehler dort behandeln, wo die Seite optionale Daten abruft, und auch die Import- und Detailseiten prüfen. Eine sichtbare Seite zu reparieren deckte nicht alle Stellen ab, die diese Daten nutzten.

Eine fehlende Mitgliedschaft darf kein Vorwand sein, einen Zugang zu einer anderen Datenbank zu erfinden. Ebenso wenig sollten nicht verfügbare optionale Daten den Kunden daran hindern, unabhängige Unternehmensdaten zu nutzen.

Der Drang, Dinge zu „reparieren“, kann aktiv schaden, wenn das System einen Zugriff zu Recht verweigert. Erfahrung hilft, zwischen einer kaputten Referenz, einer nicht verfügbaren Abhängigkeit und einer Grenze zu unterscheiden, die erhalten bleiben muss. Alles kann mit derselben roten Fehlermeldung anfangen.

Ein Besuch verrät nicht, wer zu Besuch war

Ein erfasster Besuch belegt, dass jemand oder etwas den Link geöffnet hat. Ihn als menschliches Interesse zu verbuchen lässt das CRM mehr behaupten, als die Belege hergeben.

Kampagnen-Tracking brachte eine weitere scheinbar einfache Funktion: einen personalisierten Link senden und Aktivität im Kontaktverlauf anzeigen.

Ich fragte, was passiert, wenn ein E-Mail-Sicherheitsscanner dem Link folgt, um die Seite auf Schadsoftware zu prüfen. Wenn wir das als Lesen durch den menschlichen Empfänger erfassen, erzählt unser CRM eine Geschichte, die die Belege nicht hergeben.

Erst nach einer bewussten Aktion etwas zu erfassen würde einen Teil dieser Mehrdeutigkeit vermeiden, aber den Zweck der Besuchsbeobachtung vereiteln. Auch dagegen hatte ich einen Einwand.

Wir änderten das Modell, um getrennte Beobachtungen zu speichern: einen Besuch und danach gemeldetes Scrollen, Klicken oder andere Aktivität. Spätere Signale können Kontext ergänzen, ohne die erste Beobachtung nachträglich zu einer stärkeren Behauptung umzuschreiben.

Es bleiben Beobachtungen. Selbst ein gemeldeter Klick gibt uns keine magische Gewissheit über die Absichten eines Menschen.

Dann blieb der Kontaktverlauf leer. Ich bat Codex, nachzuverfolgen, was hätte ankommen sollen.

Die Untersuchung verglich Produktionslogs, die dokumentierte API und den Code, der tatsächlich auf der externen Seite lief. Dieser Aufrufer verwendete einen anderen Endpunkt und ließ Teile der von Nilo geforderten Anfrage und Authentifizierung weg. Nilo konnte eine funktionierende API haben und das andere System Tracking-Code, während die Integration dazwischen unvollständig blieb.

Dass eine Seite behauptet, Ereignisse zu senden, ist ein schwacher Beleg. Ein Logeintrag mit der Kampagnendomain ebenso; er kann die ausgehende E-Mail beschreiben. Der aussagekräftige Nachweis ist eine Anfrage, die den richtigen Dienst erreicht, die vorgesehenen Prüfungen besteht, ein gespeichertes Ereignis erzeugt und beim richtigen Kontakt erscheint.

„Beide Seiten haben es implementiert“ lässt ziemlich viel Platz zwischen den Seiten.

Erfahrung zeigt sich in der zweiten Frage

Ein großer Teil der restlichen Arbeit beginnt mit der Frage, was ein erfolgreiches Ergebnis tatsächlich beweist.

Keines dieser Beispiele verlangte, auf KI zu verzichten. Wir nutzten sie, um das System zu untersuchen und zu verändern. Der Geschwindigkeitsgewinn ist real.

Die Notwendigkeit, das Ergebnis zu hinterfragen, ebenfalls. Das Deployment ist abgeschlossen; haben inkompatible Versionen gemeinsam gearbeitet? Die Zahlung war erfolgreich; erhält die Verlängerung den gewählten Tarif? Die Seite lädt; hat die Lösung stillschweigend Daten verändert, die sie nichts angingen? Ein Ereignis erscheint; was belegt es über einen Menschen?

Wer kein Entwickler ist, hat wenig Anlass, diese Fragen vorauszusehen. Auch ein fähiger Entwickler, dessen Erfahrung sich auf eine Schicht konzentriert, kann sie übersehen. Jahre an Benutzeroberflächen vermitteln nicht automatisch die Abläufe rund um Zahlungen. Wer Container-Deployments kennt, versteht deshalb noch nicht die asynchrone Arbeit innerhalb des Containers.

Breite Erfahrung zählt, weil die Fehler wandern. Ein vertrautes Warnsignal zu erkennen liefert einen Ansatzpunkt für die Untersuchung. Es erspart nicht den Beweis der Diagnose.

Aus den Fragen können ausführbare Prüfungen werden: Der gewählte Tarif übersteht die Verlängerung; ein ungeeignetes Abo löst keine Änderung beim Zahlungsanbieter aus; nicht verfügbarer persönlicher Speicher lässt Unternehmenskontakte unberührt; ein Release-Kandidat erledigt keine produktive Arbeit, bevor der Übergang es erlaubt.

Damit hat die nächste KI-gestützte Änderung etwas Konkretes zu erfüllen. Tests geben diesen Entscheidungen ein Gedächtnis, das läuft. Deployment-Prüfungen und Belege aus dem Produktionsbetrieb decken ab, was isolierte Tests nicht erfassen können.

So sehen die „letzten 20 %“ aus. Ein Teil davon ist fehlender Code. Vieles besteht darin, eine Zusage zu finden, die die sichtbare Funktion noch nicht einhält, und ihr dann durch das gesamte System zu folgen.

Die Oberfläche ändert sich womöglich kaum. Der Kunde bekommt einen Dienst, der auch bei Verlängerungen, Ausfällen und nach dem nächsten Release weiter funktioniert. Ziemlich viel Produkt für etwas, das die Demo nicht zeigen konnte.

Die Lage durchsprechen

Schildern Sie, was passiert. Ich höre zu, stelle ein paar praktische Fragen und spiegele zurück, was ich sehe: wo das Risiko liegen könnte, was Delivery blockiert und was als Nächstes prüfenswert aussieht. Kein Pitch, keine Verpflichtung. Vertraulich und direkt.

Gespräch beginnen

Newsletter: Kein Methoden-Theater. Kein Fluff.
Einblicke in echte Software-Auslieferung und Führung.

×