---
title: Bekannte Einschränkungen
icon: phosphor-duotone:warning
summary: Die ehrlichen Lücken - was gegen eine echte laufende App belegt ist und was nicht.
description: Die ehrlichen Lücken - was gegen eine echte laufende App belegt ist und was nicht.
tags: [reference, limitations]
---

# Bekannte Einschränkungen

BX Agents befindet sich in aktiver Entwicklung. Diese Seite hält die ehrlichen Lücken fest - was gegen eine echte laufende App getestet ist, was noch nur gegen bx-ais eingebauten `"mock"`-Provider läuft, und echte vorgelagerte Eigenheiten, auf die dieses Projekt gestoßen ist.

## Tests laufen nur gegen den `mock`-Provider

Jede Fast-Lane-Spec (Build-Pipeline, Generatoren, CLI-Verben) und die [ColdBox-Integrationssuite](#real-coldbox-integration-testing) nutzen bx-ais eingebauten `"mock"`-Provider - nie einen echten Netzwerkaufruf an ein LLM. Das ist bewusst so (schnell, kostenlos, deterministische CI), bedeutet aber, dass derzeit kein automatisierter Test belegt, dass ein echter Provider (OpenAI, Anthropic usw.) tatsächlich Ende-zu-Ende korrekt funktioniert. Vor einer Nutzung in Produktion mindestens einen manuellen `chat`-/`serve`-Lauf gegen einen echten Provider und einen Wegwerf-API-Schlüssel durchführen.

## Real ColdBox integration testing

`./gradlew testColdBoxIntegration` startet einen **echten** `boxlang-miniserver`-Prozess gegen das eigene `Application.bx`/Bootstrap einer generierten App und stellt einen echten HTTP-Request über eine mit `toAi()` registrierte Route. Das ist der stärkste Beweispunkt in der Suite - er hat während der Entwicklung drei echte Fehler in `ColdBoxAppGenerator.bx` aufgedeckt (ein fehlendes `extends` beim WireBox-`Binder`, eine falsche Reihenfolge der `Bootstrap`-Konstruktorargumente und ein nackter `getInstance()`-Aufruf, der auf `Binder` gar nicht existiert).

### Die `toAi()`-Race-Condition beim ersten Request

Der **allererste** HTTP-Request an die `toAi()`-Route einer frisch gestarteten App kann vorübergehend mit "Function [getInstance] not found" fehlschlagen - eine echte ColdBox-/WireBox-Race-Condition bei der Lazy-Injection des eigenen `getInstance`-Delegates des Routers, kein BX-Agents-Fehler. Bei jedem Request danach gelingt es zuverlässig, sobald irgendetwas anderes WireBox bereits einmal gezwungen hat, das `GeneratedAgent`-Singleton zu bauen (ein Health-Check, eine `chat`-Sitzung, eine andere Route). **Vor Produktionslast einen Warm-up-Request senden**, bevor auf eine frisch deployte `toAi()`-Route vertraut wird.

## Drei Integrationsprüfungen - über einen anderen Weg geschlossen als ursprünglich geplant

`tests/specs/integration/RuntimeStartupSmokeSpec.bx` hat weiterhin drei `xit()`s, da diese Datei über den CLI-Runner läuft (`runTests.bxs`/`testBx`), und BoxLangs CLI-Modus nie einen `cgi`-Scope liefert - den ColdBoxs RoutingService aber selbst zum Laden des Routers beim Start braucht. Das ist eine strukturelle Tatsache über den CLI-Modus, keine Lücke: Alle drei Prüfungen sind inzwischen anderswo real belegt, in `tests/specs/integration/coldbox/ColdBoxRuntimeSpec.bx` (das innerhalb eines echten, von einem echten `boxlang-miniserver`-Prozess bedienten HTTP-Requests läuft):

- Ein echter, ColdBox-gerouteter HTTP-Request, der den generierten Agenten Ende-zu-Ende erreicht - belegt durch `ColdBoxRuntimeSpec.bx` plus die eigene `POST /api/chat/invoke`-Assertion von `runColdBoxIntegrationTests.bxs`.
- `schedules/*`, das sich tatsächlich bei einem **echten** ColdBox-`Scheduler` registriert - belegt über `SchedulerService.getSchedulers()["appScheduler@coldbox"].hasTask(...)` gegen einen echten Boot, ohne auf ein tatsächliches Cron-Feuern zu warten (die gröbste unterstützte Granularität ist 1 Minute, was jeden CI-Lauf für zusätzlichen Beweis von marginalem Wert belasten würde, sobald bestätigt ist, dass der Task live und registriert ist).
- `chat` und eine über `serve` bereitgestellte HTTP-Route, die nie auseinanderlaufen - korrigiert von der ursprünglichen Formulierung (`chat` startet bewusst nie WireBox überhaupt, kann also per Design nie WireBoxs Singleton-*Objekt* teilen) hin zu dem, was tatsächlich zählt: Das Instanziieren von `GeneratedAgentFactory` außerhalb von WireBox erzeugt einen sich verhaltensgleich verhaltenden Agenten zu WireBoxs eigenem Singleton.

## Echtes OS-Prozess-CLI-Testing - und ein echter Fehler, den es fand

`ModuleCliProcessTest.java` startet echte `java -jar <boxlang-jar> module:bxagents <verb> ...`-Kindprozesse gegen eine Kopie der tatsächlichen installierbaren Modulstruktur (`build/modules/bxagents`), gerichtet auf ein echtes `modulesDirectory` - genau so, wie eine echte BoxLang-Installation dieses Modul lädt. Jede andere CLI-Spec ruft stattdessen `ModuleConfig.main()`/die `run()`-Methode jedes Verbs In-Prozess auf, was schneller ist, aber nie belegt, dass das Modul nach einer tatsächlichen Installation korrekt aufgelöst wird.

Diese Lücke war real: Dieser Test hat aufgedeckt, dass jedes CLI-Verb sofort mit "class not located" fehlschlug, sobald es durch einen tatsächlich installierten Modulprozess lief, weil interne Querverweise einen bloßen dotted-path `bxagents.models....` verwendeten, der nur dank der eigenen Dev-/Test-`boxlang.json` dieses Repos (eine handdeklarierte `/bxagents`-Zuordnung) auflösbar war - ein wirklich installiertes Modul erhält diese Zuordnung nie. Behoben, indem die internen Referenzen jeder verschachtelten Klasse auf die modul-relative Suffixform `Class@bxagents` umgestellt wurden (`ModuleConfig.bx` selbst, das an der Modulwurzel sitzt, löst einfache relative Pfade problemlos auf und brauchte keine Änderung) - siehe den Docblock von `BuildPipeline.bx`s `init()` für die vollständige Erklärung. Die Ausgabe der Modulstruktur von `build.gradle` wurde von `build/module` nach `build/modules/bxagents` verschoben (der Ordnername muss dem Modulnamen entsprechen, damit `modulesDirectory` es überhaupt findet), und die Dev-/Test-`boxlang.json` lädt es jetzt ebenfalls als echtes Modul, sodass die gesamte bestehende Suite denselben Auflösungspfad durchläuft wie die Produktion, nicht nur die Komfort-Zuordnung.

## Ein echter, grundlegender Fehler, gefunden beim Bau des Testing-Frameworks: Agenten erhielten nie ihre eigenen Tools

Der Bau des `toHaveCalledTool`-Matchers von `BaseAgentSpec` (M15) deckte einen ernsten, zuvor unentdeckten Fehler auf: `ColdBoxAppGenerator`s generierter `aiAgent()`-Aufruf übergab überhaupt nie ein `tools:`-Argument - bestätigt gegen den echten bx-ai-Quellcode (`AiAgent.bx` referenziert `aiToolRegistry()` intern nie). Die `tools/` eines Projekts wurden in den Build kopiert und für die MCP-Verdrahtung namentlich auflösbar gemacht, aber **kein von BX Agents jemals gebauter Agent - in keinem Kontext: eine echte bereitgestellte App, `chat`, oder eine Test-Spec - erhielt tatsächlich seine eigenen deklarierten Tools.** Das war unentdeckt geblieben, weil kein bestehender Test je einen echten Tool-Aufruf prüfte, sondern nur eine nicht-leere Antwort.

Behoben in `ColdBoxAppGenerator.renderAgentFactory()`: Jedes generierte `GeneratedAgentFactory.bx` lädt jetzt sein eigenes `tools/`-Verzeichnis über eine neue `ToolRegistryLoader.bx` (unter Verwendung eines zur Generierungszeit eingebetteten ABSOLUTEN Pfads, nicht eines relativen, der von einer wie auch immer im Ladekontext gerade geltenden "/"-Zuordnung abhängt - bestätigt, dass die relative Pfadauflösung von `aiToolRegistry().scan("tools")` stillschweigend fehlschlägt, wenn sie aus einem per `DynamicClassLoader` geladenen Kontext wie `chat` aufgerufen wird), und übergibt dann `tools: aiToolRegistry().getAll()` an jeden `aiAgent()`-Aufruf.

Eine dritte Falle auf Top-Level-Skript-Ebene, hart in CI gefunden: **`var` kann nicht auf oberster Ebene eines `.bxs`-Skripts verwendet werden.** `var` deklariert in den `local`-Scope, der nur innerhalb einer Funktion existiert, sodass ein `var x = ...` auf oberster Ebene zur Laufzeit `Scope [local] is not available in this context` wirft - nicht beim Parsen, sodass es das Review übersteht und erst auf dem Codepfad zündet, der es erreicht. Es kostete einen CI-Zyklus in `runColdBoxIntegrationTests.bxs`, wo die betreffende Zeile in einem Failure-Diagnostics-Zweig saß und daher genau dann abstürzte, wenn bereits etwas anderes schiefgelaufen war, und den echten Fehler durch ihren eigenen ersetzte. Zwei Regeln folgen daraus: nie `var` außerhalb einer Funktion in einem `.bxs` verwenden, und Diagnoseausgaben in ein eigenes try/catch einwickeln, damit sie nie den Fehler verdecken können, den sie beschreiben.

**Die Application-Einstellung für eine Standard-Datenquelle ist `this.datasource`, NICHT `this.defaultDatasource`.** Der Registrierungsschlüssel im Plural lautet tatsächlich `this.datasources[ "name" ] = { ... }`, sodass `this.defaultDatasource` der Name ist, zu dem jeder greift - und BoxLang akzeptiert ihn stillschweigend und tut nichts damit. Direkt gegen die Laufzeit verifiziert statt nur vermutet: Mit gesetztem `this.defaultDatasource = "testds"` schlägt ein unqualifiziertes `queryExecute()` weiterhin fehl mit `No default datasource defined in the application or globally or in the query options. Registered datasources are: [testds]`; die einzelne Zeile in `this.datasource = "testds"` zu ändern löst die Datenquelle auf, und der Aufruf gelangt bis zu einem echten Treiber-Problem. Es gibt keine Warnung und keinen Hinweis im Fehler darauf, dass die geschriebene Einstellung ignoriert wurde - die Meldung nennt die registrierte Datenquelle, die man auszuwählen versucht, was liest, als sei der Auswahlmechanismus kaputt statt falsch geschrieben.

**qbs `moduleSettings.qb.defaultOptions` erreichte `QueryBuilder` in einem echten ColdBox-Boot nicht.** Das generierte `config/ColdBox.bx` setzt `moduleSettings.qb.defaultOptions = { datasource : "<name>" }`, und qbs eigene `ModuleConfig.cfc` mappt `QueryBuilder@qb` mit `.initArg( name = "defaultOptions", value = settings.defaultOptions )` innerhalb von `onLoad()` - was korrekt liest und der Grund ist, warum das generierte `ChatDb.query()` ursprünglich keine Datenquelle benannte. Es funktionierte nicht: Jede Query schlug fehl mit `No default datasource defined in the application or globally or in the query options. Registered datasources are: [<name>]`, d. h. die Datenquelle war registriert, und der Builder hatte trotzdem leere Optionen. Warum die Modul-Einstellung nicht ankam, wurde nicht bis zum Grund verfolgt - es hörte auf zu zählen, weil die Datenquelle explizit zu benennen ohnehin besser ist, als sich auf diese Verdrahtung zu verlassen. `ChatDb.query()` ruft jetzt `.mergeDefaultOptions( { datasource : static.DATASOURCE } )` auf jedem Builder auf, ganz wie `schemaOptions()` es bereits für `SchemaBuilder` tun musste (dem qb nie `defaultOptions` übergibt). Wer einen neuen qb-Aufrufpfad zu generiertem Code hinzufügt, sollte die Datenquelle darauf explizit benennen und sich nicht auf die Modul-Einstellung verlassen.

**Eine generierte App deklariert jetzt `this.defaultDatasource`, nicht nur `this.datasources`.** Bei einer einzigen Datenquelle sollte alles, was eine Query ohne Namensangabe ausführt - ColdBox selbst, ein anderes Modul, eigener Projekt-Code - diese erreichen, statt fehlzuschlagen. Zuvor wurde nichts über den emittierten Datenquellen-Block geprüft, weshalb er ohne Standard ausgeliefert wurde; `ColdBoxAppGeneratorSpec` deckt jetzt sowohl den Web-UI-Fall als auch den Fall ohne Web-UI ab (wo keine der beiden Zeilen erscheinen sollte).

**Das generierte `models/ChatDb.bx` kompilierte nicht, und keine Unit-Spec bemerkte es.** `WebUiGenerator` baut BoxLang-Quellcode aus BoxLang-String-Literalen auf, wobei ein literales doppeltes Anführungszeichen als verdoppeltes Anführungszeichen geschrieben wird - ein Leerstring-Literal braucht also VIER Anführungszeichen, und die naheliegende Schreibweise mit zwei erzeugt ein verirrtes Anführungszeichen, das einen String öffnet und den Rest der Zeile verschluckt. Zwei Vorlagenzeilen mit einem Elvis-zu-Leerstring (`?: ""`) erzeugten `?: "`, sodass jedes generierte Web-UI-Projekt ein `ChatDb.bx` auslieferte, das nicht geparst werden konnte. Jeder `WebUiGeneratorSpec`-Fall prüfte Teilstrings des generierten Textes, die alle weiterhin passten; nur ein echter ColdBox-Boot versuchte je, ihn tatsächlich zu *kompilieren*, und dieser Pfad war selbst mehrere Zyklen lang durch die unten beschriebenen Harness-Fehler kaputt, sodass der echte Fehler dahinter verborgen blieb. Es gibt jetzt einen `WebUiGeneratorSpec`-Fall, der prüft, dass jede Zeile des generierten Quellcodes eine gerade Anzahl doppelter Anführungszeichen hat - eine Invariante, die die ganze Fehlerklasse abfängt statt nur die zwei zufällig betroffenen Zeilen. Die weitergehende Lehre: Eine Generator-Spec, die nur die eigene Ausgabe grept, testet nicht, dass die Ausgabe gültiger Code ist.

**`GET /chat/api/health` beweist weit weniger, als es scheint.** Die generierte `health()`-Action rendert ein literales `{ status: "ok", success: true }` und rührt sonst nichts an - ein 200 belegt also, dass ColdBox gebootet hat und das Routing den generierten `ChatUi`-Handler erreicht, und sagt nichts über WireBox, `ChatDb` oder SQLite aus. Der Kommentar in `runColdBoxIntegrationTests.bxs` behauptete früher, er prüfe "ColdBox routing -> ChatUi -> WireBox -> ChatDb", was falsch war und echte falsche Zuversicht gab: Ein CI-Lauf lieferte eine grüne Probe, während `models/ChatDb.bx` überhaupt nicht kompilieren konnte. An Ort und Stelle korrigiert; die Integrations-Specs, nicht die Probe, sind als Abdeckung des Stores zu behandeln.

**TestBoxs `JSONReporter` kann nicht über Specs berichten, die live Framework-Singletons berühren.** Die ColdBox-Integrations-Specs lösen echte `controller`-/WireBox-/qb-Objekte auf, und diese Objektreferenzen landen im TestBox-Ergebnis-Memento. `JSONReporter` serialisiert das Memento vollständig, sodass BoxLang über die lebenden Objekte reflektiert (`StructUtil.objectToStruct` -> `DynamicInteropService.getMethodNames`) und ihren zyklischen Referenzen folgt, bis die JVM `StackOverflowError` wirft. Die Specs selbst waren bereits grün gelaufen, als das auftrat, was genau das Verwirrende daran war: eine bestandene Suite wurde als fehlgeschlagener Lauf gemeldet. `tests/runner-coldbox.bxm` stellt deshalb seinen eigenen Bericht zusammen und zwingt jeden Wert durch einen `toScalar()`-Helper, sodass kein Objekt den Serializer erreichen kann, egal was eine Spec hinterlässt, und zeichnet die Pass-/Fail-Zähler in den Fortschrittsmarkierungen auf, *bevor* der Bericht erstellt wird - sodass ein Serialisierungsfehler nie wieder als Testfehler missverstanden werden kann.

**In einer BoxLang-Zeitmaske ist `nn` NANOSEKUNDEN, nicht Minuten - `mm` verwenden.** `dateTimeFormat( now(), "HH:nn:ss" )` erzeugte stillschweigend Zeitstempel wie `02:777491298:32`, was als beschädigte Ausgabe liest statt als falsche Maske. (Man beachte die umgekehrte Falle in einer Datumsmaske, wo `mm` der Monat ist: `yyyy-mm-dd` liefert die Minute, nicht den Tag.)

**Eine Identität serverseitig abzuleiten ist nicht dasselbe wie sich dagegen zu autorisieren.** Der generierte `handlers/ChatUi.bx` achtete darauf, `userId` bei jeder Action aus der Session abzuleiten und nie aus dem Request-Body, und trotzdem hatten zwei Actions eine Autorisierungslücke, weil sie nach `threadId` adressiert wurden statt nach dem Aufrufer. `/pending` und `/resume` luden einen Checkpoint per ID und agierten darauf; `/resume` leitete `decidedBy` sogar aus der Session ab, was wie eine Scoping-Prüfung liest, aber nur regelt, *was auf dem Entscheidungslabel steht*, nicht *wessen Lauf entschieden wird*. Ein Besucher mit der `threadId` einer anderen Person konnte deren ausstehende Tool-Aufrufe lesen und deren Genehmigungen beantworten. Beide vergleichen jetzt den Aufrufer mit der `userId`, die der Agent im Lauf-Options-Checkpoint hinterlegt hat. Die allgemeine Regel, die man sich merken sollte: Wenn eine Route über eine undurchsichtige ID adressiert wird statt über den Aufrufer, kauft eine abgeleitete Identität Zuschreibung, nicht Zugriffskontrolle - beide brauchen getrenntes Nachdenken, und ein serverseitig abgeleiteter Wert in derselben Funktion kann leicht mit beidem verwechselt werden.

**`ProjectValidator`s Pfadprüfungen müssen `..` berücksichtigen, nicht nur ein führendes Trennzeichen.** `database.path` wird in einen `expandPath()`-Aufruf im generierten `Application.bx` eingesetzt, wurde also darauf geprüft, absolut zu sein - und `../../var/lib/chat.db` entkommt dem App-Verzeichnis genauso wirksam, während es diese Prüfung sauber besteht. Jetzt auf beiden Trennzeichen abgelehnt, wobei ganze Pfadsegmente abgeglichen werden, damit legitime Namen wie `..hidden` oder `a..b` weiterhin durchgehen. Jeder zukünftige Validator, der einen generierten Pfad absichert, braucht beide Hälften dieser Prüfung.

**Der Testsuite-Lauf lokal braucht bx-ai aus dem Quellcode überlagert, genau wie CI es tut.** `./gradlew downloadModules` holt den *veröffentlichten* bx-ai-Snapshot, der dem eigenen Entwicklungszweig hinterherhinkt - er überschreibt die vendorte Kopie unter `src/test/resources/modules/bxai`, und die Suite schlägt dann bei ~40 Specs mit `Method 'isRunning' not found` und `The method aiGatewayRegistry does not exist` fehl, beides APIs, die vorgelagert existieren, aber nicht im veröffentlichten Build. Das ist keine Regression, und es lässt sich leicht als eine solche missverstehen. Vor dem Ausführen von `testBx` neu bauen und überlagern: `( cd <bx-ai checkout> && ./gradlew createModuleStructure )`, dann `cp -R <bx-ai>/build/module/. src/test/resources/modules/bxai/`. `tests/coldbox/`, `tests/testbox/` und `tests/qb/` stammen aus `box install` (in `tests/` ausführen), und alle drei sind gitignored.

**`chr()` existiert in BoxLang nicht - der BIF heißt `char()`.** Das ist nicht kontextabhängig, trotz eines Kommentars in `ModuleConfig.bx`, der früher behauptete, es sei nur "in diesem Modul-CLI-Ausführungskontext" nicht verfügbar (jetzt korrigiert). Direkt gegen die Laufzeit verifiziert: `char( 10 )` liefert den Zeilenumbruch, `chr( 10 )` wirft `Function [chr] not found`, sowohl in einem einfachen CLI-Skript als auch in einem bereitgestellten Template. Wert, es zu wissen, weil der Fehler ein *Laufzeit*fehler ist - `chr()` parst fine und übersteht das Review, dann wirft es auf welchem Codepfad auch immer es zuerst erreicht wird. Es kostete hier mehrere CI-Zyklen: `tests/runner-coldbox.bxm` rief `chr( 10 )` in seiner allerersten Fortschrittsmarkierung auf, sodass jeder Request beim Eintritt mit 500 fehlschlug, und die unten beschriebene Retry-Schleife verwandelte diesen einfachen einzeiligen Fehler in ein undurchsichtiges "HTTP 408, nie geantwortet".

Eine vierte Lehre aus demselben CI-Harness, und die, die die meisten Zyklen kostete: **Instrumentierung, die ein Retry überschreiben kann, zeichnet nichts auf.** `tests/runner-coldbox.bxm` begann damit, die eigene Fortschrittsdatei zu leeren (`fileWrite( progressFile, "" )`), und `runColdBoxIntegrationTests.bxs` wiederholte den Runner-Request einmal pro Sekunde bis zu einer Deadline. Also löschte jeder Retry die Markierungen, die der hängende erste Versuch geschrieben hatte, und die Fehlerdiagnostik druckte treu eine leere Datei aus - was liest wie "die Seite kam nirgendwo hin", während die Wahrheit war "der Beweis wurde gelöscht". Schlimmer noch, die Retries waren unabhängig von der Diagnostik aktiv schädlich: Die Runner-Seite ist weder billig noch idempotent (sie bootet TestBox und führt jede Integrations-Spec aus), sodass wiederholtes Aufrufen gleichzeitige vollständige Testläufe auf einem MiniServer-Worker-Pool von nur einer Handvoll Threads stapelte - ein Weg, um ein Hängen zu verursachen, nicht um sich davon zu erholen. Beides ist behoben: Der Orchestrator leert die Markierungsdatei einmal und stellt genau einen Request (die Health-Probe davor hat bereits bewiesen, dass der Server läuft, es gibt also nichts, worauf ein Retry warten müsste), und die Seite hängt nur noch an, wobei jede Zeile mit einer Pro-Request-UUID markiert wird, damit sich überlappende Versuche unterscheiden lassen. Die allgemeine Regel: Ein Fortschrittslog muss nur-anhängend sein und länger leben als das, was es instrumentiert, und alles Teure oder Zustandsbehaftete sollte nie hinter einer Retry-Schleife stecken.

Eine verwandte, separate BoxLang-Falle, gefunden bei der Diagnose davon: **`request` ist ein reservierter eingebauter Scope-Name.** Eine lokale Variable oder Schleifenvariable namens `request` kann ihn stillschweigend überschatten - `for ( var request in someArray ) { request.someKey }` iterierte die korrekte Anzahl von Malen, aber jeder `request.someKey`-Zugriff darin las stillschweigend den leeren eingebauten Scope statt der Schleifenvariable, ganz ohne Fehler. Behoben, indem in den Matchern von `BaseAgentSpec.bx` in `recordedRequest` umbenannt wurde - wert, sich für jeden zukünftigen BoxLang-Code in diesem Projekt zu merken, der über irgendetwas sinnvollerweise `request` genanntes iteriert.

## `serve`s Miniserver-Suche ist PATH-only

`serve` sucht `boxlang-miniserver` nur im `PATH` (`MiniServerLauncher.findExecutable()`). Es gibt kein Fallback auf einen konfigurierten Pfad oder eine mitgelieferte Binärdatei - ist sie nicht installiert und im `PATH`, schlägt `serve` mit einem klaren, handlungsleitenden Fehler fehl, aber es gibt keinen alternativen Weg, sie anzugeben. `invoke --server` nutzt intern `serve` wieder, erbt also dieselbe PATH-only-Suche - der eigene `InvokeSpec.bx`-Test für den echten HTTP-Round-Trip prüft zuerst `MiniServerLauncher.findExecutable()` und überspringt (statt fehlzuschlagen), wenn keine echte Binärdatei im `PATH` ist, dasselbe Idiom, das `RuntimeStartupSmokeSpec.bx` bereits für seine eigene Jar-Präsenzprüfung nutzt. Vor einer Nutzung des echten HTTP-Pfads in Produktion mindestens einen manuellen Lauf von `bxAgents invoke --message=... --server` auf einer Maschine mit installiertem `boxlang-miniserver` durchführen - dieselbe ehrliche Einordnung, die auch an anderer Stelle auf dieser Seite für Lücken verwendet wird, die diese Suite nicht in jeder Umgebung selbst schließen kann.

## Das begrenzte BoxLang-Runtime-Home erreicht `serve`/`invoke --server` bedingungslos; die In-Prozess-Verben nur, falls die eigene BoxLang-Installation `.env` lädt

`serve` begrenzt das eigene BoxLang-Runtime-Home des Miniservers über `serverHome` auf `.build/runtime` (ein echtes, bestätigtes `MiniServerConfig`-Feld - der eigene CLI-Hilfetext von `boxlang-web`s `MiniServer`: `-s, --serverHome <PATH>  BoxLang server home directory (default: ~/.boxlang)`), sodass der Compiled-Class-Cache und etwaige Konfigurations-Overrides jedes Projekts pro Projekt isoliert sind statt global unter `~/.boxlang` geteilt zu werden. `invoke --server` erbt das, da es intern `serve` wiederverwendet. Dieser Teil ist bedingungslos - die Startkonfiguration für diesen Prozess wird selbst geschrieben.

`chat`, `build`, `test` und der standardmäßige (In-Prozess-) `invoke` sind anders: Sie laufen innerhalb des bereits gestarteten `bxAgents`-Prozesses, dessen eigene BoxLang-Runtime - und damit ihr Home - aufgelöst wurde, bevor irgendein eigener BoxLang-Code, einschließlich `ModuleConfig.bx`s eigenem `main()`, überhaupt die Chance hatte zu laufen (die Engine muss existieren, um diesen Code überhaupt zu interpretieren). `BoxRuntime` ist ein JVM-weites Singleton; sein Home wird bei der ersten Initialisierung fixiert, sodass nichts, was eine Verb-Klasse aus diesem bereits laufenden Prozess heraus tut, es nachträglich ändern kann.

Es GIBT hierfür einen echten Hebel: `BoxRunner` (BoxLangs eigener Core-CLI-Einstiegspunkt, nicht nur der Miniserver) liest eine echte `BOXLANG_HOME`-Umgebungsvariable, bevor `BoxRuntime` initialisiert - direkt gegen das echte Runtime-Jar bestätigt (nicht nur gegen die Dokumentation): Es mit gesetztem `BOXLANG_HOME=<path>` als echter OS-Umgebungsvariable auszuführen (relative Pfade werden gegen das CWD aufgelöst, genau wie ein absoluter Pfad) befüllt jedes Mal die vollständige Home-Struktur an diesem Pfad statt unter `~/.boxlang`. `new` erzeugt eine `.env`, die `BOXLANG_HOME=.build/runtime` deklariert - denselben Pfad, den auch `serve` nutzt - genau aus diesem Grund (angelehnt an die eigene `.env`/`BOXLANG_HOME`-Konvention von `ortus-boxlang/bx-ai-intro`), im **Projekt-Root** - also dem Verzeichnis, aus dem eine Person `bxAgents <verb>` für die In-Prozess-Verben tatsächlich ausführt, was der richtige Ort für einen CWD-basierten `.env`-Loader ist, sie zu finden.

**Was bestätigt wurde, und was nicht.** `boxlang-miniserver` (was `serve` startet) hat tatsächlich echtes, eingebautes `.env`-Auto-Loading - bestätigt durch Dekompilieren und dann tatsächliches Ausführen von `ortus.boxlang.web.MiniServer` direkt: Ohne konfigurierte `envFile` löst es `.env` relativ zum **webRoot** des Servers auf (nicht zum Projekt-Root) und lädt sie, falls gefunden, als Java-`Properties`, wobei jeder Schlüssel via `System.setProperty()` angewendet wird - so geladene echte Werte SIND über `getSystemSetting()` für BoxLang-Code sichtbar. Das rohe Core-Runtime-Jar (`BoxRunner`, verwendet von `chat`/`build`/`test`/standardmäßigem `invoke`) hat nirgendwo darin eine entsprechende Logik (bestätigt durch Grep über jede Klasse im Jar nach `.env`) - diese Verben übernehmen `.env` also nur, wenn die tatsächlich installierte `boxlang`-CLI (ein von der BVM bereitgestellter nativer Launcher, nicht dieses rohe Jar) ihr eigenes `.env`-Laden durchführt, bevor die JVM startet, so wie `ortus-boxlang/bx-ai-intro` sich darauf verlässt. Das ist plausibel und mit der realen Nutzung dieses Projekts konsistent, ist aber nichts, was diese Sandbox mit der echten Binärdatei direkt verifizieren kann.

Eine spezifische, bestätigte Tücke selbst dort, wo `.env`-Laden stattfindet: **`BOXLANG_HOME` selbst wirkt nur, wenn es eine echte OS-Umgebungsvariable ist** - keine JVM-System-Property und kein `-D`-Flag. Zweimal direkt gegen die echten Jars verifiziert: (1) `boxlang-miniserver` gegen ein webRoot auszuführen, dessen `.env` `BOXLANG_HOME=customhome` deklarierte, lud die Datei (bestätigt über die eigene Log-Zeile "Loaded environment variables from:" und über `getSystemSetting()`, das andere `.env`-Werte korrekt zurücklieferte), doch der Server loggte weiterhin `Logs Directory: /root/.boxlang/logs` - das Standard-Home, nicht `customhome`; (2) `BoxRunner` direkt mit `-DBOXLANG_HOME=<path>` zu starten (eine JVM-System-Property, keine `.env` beteiligt) hatte ebenfalls keinen Effekt auf das aufgelöste Home, obwohl `getSystemSetting( "BOXLANG_HOME" )` bereitwillig den Wert des Flags aus BoxLang-Code zurückgab. `BOXLANG_HOME`-Auflösung liest also speziell nur die echte OS-Umgebungsvariable - anders als bei den meisten Einstellungen bedeutet ein von `getSystemSetting()` zurückgegebener Wert nicht, dass sich das Runtime-Home tatsächlich verschoben hat. Wenn die eigene `boxlang`-CLI ihren `.env`-Loader intern genauso funktioniert wie MiniServer (`System.setProperty` nachdem die JVM bereits gestartet ist) statt vor dem Start der JVM eine echte Umgebungsvariable zu exportieren, erreicht `BOXLANG_HOME` in `.env` das Runtime-Home nicht, obwohl `.env` selbst erfolgreich geladen wurde - alles andere darin funktioniert trotzdem. Falls die eigene Installation das gar nicht bietet, sollte man es selbst vor dem Ausführen von Befehlen sourcen (z. B. `set -a; source .env; set +a`), um dieselbe Isolation zu bekommen, die `serve` bereits bedingungslos erhält.

## `new`s `box install`-Komfortschritt wird nicht von einem automatisierten Test geprüft

`new` führt standardmäßig `box install` im erzeugten `tests/`-Ordner aus, sodass `bxAgents test` sofort funktioniert (siehe [CLI-Referenz](cli-reference.md)). Das ist eine echte Netzwerkoperation (CommandBox löst `testbox` gegen ForgeBox auf) - bestätigt, dass sie in dieser Entwicklungs-Sandbox speziell etwa 25 Sekunden dauert und mit einem Zertifikatsfehler fehlschlägt, dieselbe ForgeBox-unerreichbar-Einschränkung, die an anderer Stelle im eigenen Tooling dieses Projekts bereits vermerkt ist. `NewSpec.bx` prüft nur den schnellen, deterministischen `--skipInstall`-Pfad (und auch die OS-Prozess-`ModuleCliProcessTest.java`/In-Prozess-`ModuleConfigCliSpec.bx`-Aufrufe von `new` übergeben aus demselben Grund `--skipInstall`) - der Standard-Installationsversuchspfad selbst ist nur durch manuelles Testen verifiziert, nicht durch CI.

## `chat` braucht ein echtes TTY

`chat` nutzt BoxLangs eigene `MiniConsole`, die `stty` aufruft, um den Raw-Terminal-Modus einzurichten - es kann nur gegen ein echtes interaktives Terminal laufen. Es funktioniert nicht per Pipe, umgeleitet oder aus einem nicht-interaktiven Prozess (einem CI-Job, einem Skript). Es gibt keinen nicht-interaktiven Fallback-Modus.

## Behoben: `chat` und der standardmäßige (nicht-`--server`) `invoke` schlugen früher bei einer klassenbasierten `Agent.bx` fehl

Früher warfen sowohl `chat` als auch der standardmäßige `invoke` `The requested class [agent.classes.agentClass] has not been located in any class resolver.`, bevor sie überhaupt den Agenten erreichten. Grundursache: Beide Verben laden das generierte `GeneratedAgentFactory.bx` In-Prozess über `DynamicClassLoader.instantiate()` (ein roher `RunnableLoader`-Aufruf gegen einen absoluten Pfad, ohne beteiligten ColdBox-Container), und die generierte Factory instanziierte eine klassenbasierte `Agent.bx` früher über eine **relative** Dotted-Path-Lookup, `new "agent.classes.agentClass"()` - was nur auflöst, sobald etwas eine Zuordnung registriert hat, die das App-Root auflösbar macht, und nichts tat das außerhalb eines echten ColdBox-Boots.

Eine Zuordnung mitten im Skript zu registrieren (`Configuration.registerMapping( "/", appDir )` unmittelbar vor `DynamicClassLoader.instantiate()`) behebt es ebenfalls **nicht** - empirisch bestätigt durch handgebautes Nachstellen genau derselben Sequenz in einem eigenständigen `.bxs`-Skript. Dieselbe Art von Einschränkung, wie bereits oben für die TestBox-Discovery von `TestRunnerLauncher` dokumentiert: Eine über `Configuration.registerMapping()` mitten im Skript registrierte Zuordnung propagiert nicht zuverlässig zu den relativen Pfad-Lookups einer Klasse innerhalb desselben Prozesses.

**Fix:** `ColdBoxAppGenerator.copyAgentClass()` gibt jetzt den eigenen absoluten Dateipfad der kopierten Klasse zurück (statt eines Dotted-Component-Pfads), und `renderClassBasedAgentStatement()` instanziiert sie über `DynamicClassLoader.instantiate( absolutePath, context )` - genau dieselbe Primitive, die `chat`/`invoke` bereits zum Laden von `GeneratedAgentFactory.bx` selbst verwenden - statt eines relativen `new "..."()`. Das umgeht die Zuordnungsauflösung vollständig, sodass es jetzt identisch funktioniert, egal ob ein echter ColdBox-Container gebootet ist oder nicht. Gegen `examples/class-based-agent/` bestätigt: `chat`, standardmäßiger `invoke`, `invoke --server` und `serve` bauen und betreiben jetzt alle korrekt denselben Agenten.

## Kein box.json-`executable`-Installations-Smoketest

`box.json` deklariert `"boxlang": { "executable": "bxAgents" }`, sodass eine echte Modul-Installation einen nativen `bxAgents`-Befehl erzeugt (siehe [Installation](getting-started/installation.md)). Diese Verdrahtung selbst wird nicht von einem automatisierten Test geprüft - sie verlässt sich auf das eigene dokumentierte Verhalten des BoxLang-Modul-Installers für die Erzeugung von Executable-Wrappern, verifiziert durch Lesen des Quellcodes, nicht durch einen Installations-und-Ausführungs-Test in der eigenen CI dieses Repos.

## `schedules/Scheduler.bx` wird zur Build-Zeit nicht validiert

Da es echter, handgeschriebener ColdBox-Code ist, der unverändert durchgereicht wird (siehe [schedules/](conventions/schedules.md)), kann `build` ihn nicht sinnvoll prüfen, wie es früher bei der `{ cron, action }`-Konfiguration möglich war - ein Syntaxfehler, ein Tippfehler in einem `getInstance( "..." )`-Aufruf oder der Verweis auf einen nicht existierenden Agentennamen passieren `build` alle sauber und tauchen erst auf, wenn die generierte App tatsächlich bootet (`serve`), genau wie bei jeder anderen echten BoxLang-Klasse, deren Inhalt dieses Projekt nicht besitzt. `build` fängt einen engeren, angrenzenden Fehler ab: zwei Agenten (Root oder Subagent, beliebige Tiefe), die denselben `name` deklarieren - das ist eine `config/WireBox.bx`-Bindungskollision, die dieses Projekt selbst generiert, wird also zur Validierungszeit geprüft wie alles andere, das es generiert.

## Ein echter Fehler, gefunden beim Bau des `test`-Verbs: die Auflösung "des aktuellen BoxLang-Jars" ist mehrdeutig, sobald `boxlang-miniserver` auf dem Klassenpfad liegt

`TestRunnerService.bx` startet einen frischen Kindprozess, um die `tests/specs` eines Projekts auszuführen, und muss wissen, mit welchem Jar er gestartet werden soll. Die erste Implementierung nutzte den Standard-Trick "aus welchem Jar wurde diese Klasse geladen" (`BoxRuntime.class.getProtectionDomain().getCodeSource().getLocation()`) - das funktionierte in isoliertem manuellen Testen, schlug aber unvorhersehbar fehl, sobald es als Teil der eigenen vollständigen `testBx`-Suite dieses Projekts lief, die außerdem `boxlang-miniserver-*.jar` auf dem Klassenpfad braucht (für `serve`-/`MiniServerLauncher`-bezogene Specs). Durch direkte Inspektion bestätigt: **`boxlang-miniserver-*.jar` ist ein Fat-Jar, das seine eigene Kopie von `ortus.boxlang.runtime.BoxRuntime` bündelt** - mit beiden Jars auf dem Klassenpfad kann der Classloader `BoxRuntime.class` statt auf das echte Runtime-Jar auf das Miniserver-Jar auflösen, wodurch stillschweigend `MiniServer.main` gestartet wird (das `--bx-config` ablehnt und sofort mit Exit-Code 1 beendet, bevor je ein BoxLang-Skript oder Testbericht erzeugt wird) statt `BoxRunner.main`. Das äußerte sich darin, dass die Fälle mit echtem Prozess in `tests/specs/cli/TestSpec.bx` mit `exitCode=1` und leerem Bericht fehlschlugen - nur reproduzierbar über die vollständige Suite, nicht isoliert, was es leicht zu übersehen machte.

Behoben, indem das Jar stattdessen aus `java.class.path` aufgelöst wird - gescannt nach einem `boxlang-*.jar`-Eintrag, der `miniserver` **nicht** enthält, mit Rückfall auf den alten CodeSource-Trick nur, falls kein solcher Eintrag gefunden wird.

## `deploy`s `ssh`-/`docker`-/`digitalocean`-Ziele werden nicht von einem automatisierten Real-Prozess-Test geprüft

`SshTargetSpec.bx`/`DockerTargetSpec.bx` prüfen den exakten `scp`-/`ssh`-/`docker`-Befehl, den jedes Ziel baut (ein echtes `ProcessBuilder`-Argument-Array), ohne je die echte Binärdatei aufzurufen - derselbe Ansatz "aufzeichnen, nicht ausführen", der an anderer Stelle in dieser Suite für alles genutzt wird, was eine Binärdatei braucht, die in CI eventuell nicht im `PATH` ist (siehe die `assumeTrue`-Skips von `MiniServerLauncherTest`). `local`s Kopierlogik WIRD echt geprüft (`LocalTargetSpec.bx`), einschließlich eines Regressionstests für einen echten latenten Fehler, den dieses Refactoring behoben hat: Die ursprüngliche `Deploy.bx` wählte die "neueste" `.bxa` per lexikalischer Dateinamensortierung, was ab zweistelligen Versionen stillschweigend die falsche wählt (`v9.0.0` sortiert nach `v10.0.0`) - behoben, indem nach tatsächlicher Änderungszeit der Datei sortiert wird (`DistArtifactLocator`).

`DigitalOceanTargetSpec.bx` testet ähnlich nur die reine `buildAppSpec()`/`findExistingAppId()`-Logik als Unit-Test - die echten `GET/POST /v2/apps`-Aufrufe werden in CI nie geprüft, da das ein echtes DigitalOcean-Konto und API-Token braucht. Vor einer Nutzung in Produktion mindestens einmal manuell `deploy --name=<ssh-entry>` gegen eine echte Wegwerf-VM und einmal `deploy --name=<digitalocean-entry>` gegen ein echtes DO-Konto mit einer Wegwerf-App durchführen - dieselbe ehrliche Einordnung, die oben bereits für die Lücke "nur Mock-Provider getestet" verwendet wurde.

## `deploy`s `ftp`-/`sftp`-Ziele: echte Verbindungsbehandlung belegt, ein echter erfolgreicher Upload nicht

Anders als `ssh`/`docker` (die zu externen Binärdateien ausgelagert werden und daher nur in ihrer *Befehlskonstruktion* ohne echten Server getestet werden können), rufen `ftp`/`sftp` die `bx:ftp`-Komponente des echten Moduls [`bx-ftp`](https://github.com/ortus-boxlang/bx-ftp) **In-Prozess** auf - es gibt keine Option "Befehl aufzeichnen, nicht ausführen". `BaseFtpTargetSpec.bx` unternimmt stattdessen einen echten Verbindungsversuch gegen `127.0.0.1` auf einem Port, auf dem nichts lauscht, und prüft, dass der echte Fehler "Verbindung abgelehnt" abgefangen und als klares `BxAgents.DeployFailed` erneut geworfen wird (bestätigt über bx-ftps eigenen Quellcode, dass jede Action bei einem Fehler wirft statt ein weiches `succeeded: false` zurückzugeben). Das belegt den echten Verbindungs-/Fehlerkapselungs-/Aufräumpfad Ende-zu-Ende - was es nicht belegen kann, ist ein echter erfolgreicher Upload.

Diese Lücke liegt speziell daran, dass **diese Entwicklungs-Sandbox überhaupt keinen ausgehenden Roh-TCP-Zugang hat** - nur HTTPS über den Proxy der Umgebung (direkt bestätigt: `curl ftp://test.rebex.net` und eine rohe `/dev/tcp`-Verbindung zu einem öffentlichen FTP-Host hingen beide und liefen in ein Timeout, und `docker info` zeigt keinen laufenden Daemon, sodass auch bx-ftps eigene gebündelte Docker-FTP-/SFTP-Testserver hier nicht gestartet werden konnten). Das ist eine Sandbox-Einschränkung, kein Code-Mangel - vor einer Nutzung in Produktion mindestens einmal manuell `deploy --name=<ftp-entry>` und einmal `deploy --name=<sftp-entry>` gegen einen echten erreichbaren Server (oder bx-ftps eigene `docker-compose up`-Testserver, von einer Maschine mit Docker-/Netzwerkzugriff) durchführen - dieselbe ehrliche Einordnung, die oben bereits für `ssh`/`digitalocean` verwendet wurde.

## `build` rollt ein teilweise geschriebenes `.build/app` bei einem Absturz mitten in der Generierung nicht zurück

`BuildPipeline.build()` löscht und erzeugt `.build/app` im Voraus neu und führt dann die Generatoren aus Phase 5 der Reihe nach aus. Alles, was Phase 3 (`ProjectValidator`) prüfen kann, wird geprüft, bevor irgendetwas davon passiert, ein echter Absturz mitten in der Generierung sollte also in der Praxis selten sein - kommt es aber doch dazu (z. B. ein `models/`-/`schedules/`-/`mcp/`-Eintrag, der nicht geladen werden kann, oder ein Umgebungs-/Dateisystemproblem), bleibt `.build/app` in einem teilweise geschriebenen Zustand auf der Platte, statt auf den vorherigen Inhalt zurückgesetzt oder aufgeräumt zu werden. Ein nachfolgender erfolgreicher `build` überschreibt es sauber, das ist also nicht dauerhaft, aber alles, was zwischen einem fehlgeschlagenen Build und dem nächsten `.build/app` inspiziert (ein CI-Schritt, ein manueller `package`-Retry), kann eine kaputte, halb generierte App sehen. Es gibt noch keinen Rollback-/Temp-Verzeichnis-dann-Tausch-Schritt.

## Ein sporadischer `StackOverflowError` wurde in `testBx` beobachtet, unabhängig vom eigenen Code dieses Projekts

Während der Untersuchung dieses Meilensteins stürzte `./gradlew testBx` gelegentlich (nicht bei jedem Lauf) die gesamte JVM mit einem `StackOverflowError` innerhalb der eigenen generischen Objekt-JSON-Serialisierung der BoxLang-Engine ab (`DynamicObjectSerializer`/`BoxStructSerializer`, die sich gegenseitig aufrufen, abwechselnd, bis der Stack erschöpft ist - bestätigt, dass dies auch mit `-Xss16m` noch passiert, es also ein echter Zyklus ist, keine bloß tiefe, aber endliche Struktur). Isoliert über `git stash` (der Absturz reproduzierte sich identisch ohne die Änderungen dieser Session, gegen genau denselben Commit, der bereits auf `development` liegt) und über eine Bisektion von `tests/specs/**` in jedes Unterverzeichnis, einzeln und kombiniert (jede Teilmenge, und sogar jede Teilmenge-minus-eins, lief sauber durch) - nur der einzelne vollständige Lauf reproduziert es gelegentlich, und ein sofortiger erneuter Lauf derselben vollständigen Suite nach einem Absturz besteht manchmal sauber. Das deutet auf eine zeitabhängige Race-Condition hin (plausibel im Zusammenspiel mit dem echten Hintergrund-Task `everySecond()` von `ExampleScheduler`, der aus seinem eigenen Thread-Pool druckt, gleichzeitig mit welcher Spec auch immer in diesem Moment gerade läuft) statt auf einen Fehler in einer einzelnen Spec oder im eigenen generierten Code dieses Projekts. Falls `testBx` mit einem `StackOverflowError` ohne andere Erklärung fehlschlägt, sollte er wiederholt werden, bevor eine echte Regression angenommen wird - er ist derzeit nicht auf Abruf reproduzierbar, es existiert also kein automatisierter Regressionstest dafür, und ihn weiter bis auf eine einzelne Grundursache zurückzuverfolgen lag außerhalb des Umfangs dieser Runde.

## Push-Style-Gateways (Telegram) werden nur gegen eine gemockte API-/Scheduler-Naht getestet - kein Live-Plattform-Integrationslauf in CI

`TelegramGatewaySpec.bx` prüft die eigene Logik von `TelegramGateway` (Inbound-Normalisierung, Outbound-Chunking an der 4096-Zeichen-Grenze, HITL-Inline-Keyboard-Bau, Scheduler-Task-Registrierung/-Entfernung) ausschließlich gegen eine injizierbare `apiCaller`/`setScheduler()`-Testnaht - nie einen echten Telegram-Bot-API-Aufruf und nie einen echten ColdBox-Scheduler-Boot. Das belegt, dass der eigene Code des Gateways korrekt ist, nicht, dass es tatsächlich Ende-zu-Ende gegen Telegrams echte API oder einen echten laufenden Scheduler funktioniert. Vor einer Nutzung in Produktion mindestens einmal manuell `bxAgents serve` gegen ein Projekt mit einem echten, `botTokenEnvVar`-gestützten Telegram-Bot durchführen - dieselbe ehrliche Einordnung, die oben bereits für die Lücken "nur Mock-Provider"/"keine Live-Verbindung" an anderer Stelle in dieser Datei verwendet wurde. Dieselbe Einschränkung wird für jedes zukünftige, auf dieselbe Art gebaute Push-Style-Gateway gelten (Slack, Discord, E-Mail, WhatsApp).

`SlackGatewaySpec.bx` trägt dieselbe Lücke, eine Ebene tiefer: Die persistente Websocket-Verbindung von `SlackGateway` wird über eine injizierbare `setSocketOpener()`-Naht getestet (ein Fake-Objekt als Stellvertreter für das echte `java.net.http.WebSocket`), sodass jede Frame-Behandlungs-/Reconnect-Logik-Assertion in der Spec ohne echte Netzwerk-I/O läuft. Was tatsächlich direkt verifiziert wurde (nicht gemockt): Ein eigenständiger Smoke-Test instanziierte den echten `SlackSocketListener(gateway)` (der direkt `implements="java:java.net.http.WebSocket$Listener"` - BoxLang kompiliert das als echten JVM-Implementierer, kein Proxy nötig) und rief den echten `HttpClient.newWebSocketBuilder().buildAsync(...)` gegen eine unerreichbare Adresse auf, was bestätigte, dass die BoxLang-zu-Java-Interop selbst bis an die Netzwerkgrenze korrekt funktioniert (es schlug mit einem einfachen `java.net.ConnectException` fehl, nicht mit einem Casting-/Interop-Fehler) - aber kein Test hier hat je einen echten Socket-Mode-Handshake gegen Slacks tatsächliche Server abgeschlossen. Vor einer Nutzung in Produktion mindestens einmal manuell `bxAgents serve` gegen ein Projekt mit echten `botTokenEnvVar`-/`appTokenEnvVar`-gestützten Slack-App-Zugangsdaten durchführen.

`DiscordGatewaySpec.bx` trägt dieselbe identische Lücke, aus demselben Grund: Die Frame-Behandlungs-/Heartbeat-/Reconnect-Logik von `DiscordGateway` wird ausschließlich gegen injizierbare `setApiCaller()`-/`setSocketOpener()`-Nähte geprüft, null echte Netzwerk-I/O. Dieselbe eigenständige Smoke-Test-Disziplin wurde auch hier angewandt - `gateway.onConnect()`, gesteuert gegen einen echten `HttpClient.newWebSocketBuilder().buildAsync(...)`-Aufruf an eine unerreichbare Adresse, schlug mit einem einfachen `java.net.ConnectException` fehl, nicht mit einem Casting-/Interop-Fehler, was die Interop-Kette bestätigte. Was NICHT verifiziert wurde: ein echter Gateway-Handshake (`Hello` → `Identify` → `READY`) gegen Discords tatsächliche Server, echtes Heartbeat-Timing innerhalb Discords eigener Toleranz, oder dass der Standardwert von `intents` (`GUILDS`+`GUILD_MESSAGES`+`DIRECT_MESSAGES`+`MESSAGE_CONTENT` = `37377`) tatsächlich ausreicht, um Nachrichteninhalt zu empfangen, sobald `MESSAGE_CONTENT` für einen echten Bot im Discord Developer Portal aktiviert/genehmigt ist. Vor einer Nutzung in Produktion mindestens einmal manuell `bxAgents serve` gegen ein Projekt mit einem echten, `botTokenEnvVar`-gestützten Discord-Bot (mit aktiviertem `MESSAGE_CONTENT`) durchführen.

`EmailGatewaySpec.bx` trägt eine größere Version derselben Lücke. Inbound-IMAP wird ausschließlich über eine injizierbare `setImapPoller()`-Naht getestet (vorgefertigte normalisierte Nachrichtenstrukturen, keine echte Mailbox), und Outbound ausschließlich über eine injizierbare `setMailService()`-Naht (`FakeMailService`/`FakeMail`, als Stellvertreter für `MailService@cbmailservices` - `EmailGateway` geht in diesen Specs nie durch echtes WireBox, da es keinen echten ColdBox-Boot gibt). Was in dieser Session tatsächlich direkt verifiziert wurde (nicht gemockt, nicht angenommen): die echte `jakarta.mail`-API-Oberfläche, von der `fetchInboundMessages()` abhängt (`Session.getDefaultInstance()`, `Flags`/`Flags.Flag`/`FlagTerm`, `Store.getStore("imaps")`, `Folder.READ_WRITE`, `MimeMultipart`, `InternetAddress`), gegen die echten `jakarta.mail-api`-/Angus-Mail-Jars (eigens dafür heruntergeladen - sie sind im eigenen Test-Klassenpfad dieses Repos nicht vendort), womit bestätigt wurde, dass jeder verwendete Klassen-/Methodenname tatsächlich existiert und auflöst; ein echtes `Store.connect()` gegen eine unerreichbare Adresse, gesteuert durch `EmailGateway.pollInbox()` selbst (kein umgangener Helper), schlug mit einem einfachen Verbindungs-Timeout-Fehler fehl, nicht mit einem Interop-/Casting-Fehler, was bestätigte, dass die Interop-Kette die echte Netzwerkgrenze korrekt erreicht, dieselbe Disziplin wie bei den Websocket-Smoke-Tests von Slack/Discord. Was ausdrücklich NICHT verifiziert wurde, und eine strikt größere Lücke ist als bei den Chat-Plattform-Gateways: kein echter IMAP-Handshake gegen eine echte Mailbox, kein echtes `cbmailservices`-/`bx-mail`-Modul irgendwo in diesem Repo oder seinem Test-Harness installiert (keines von beiden ist so vendort wie `bx-ai`/TestBox - siehe den Snapshot-Verzögerungseintrag unten für dieselbe auf ein anderes Modul angewandte Umgehung), sodass der WireBox-Auflösungspfad (`MailService@cbmailservices`, das tatsächlich existiert, `BXMail`s `bx:mail`-Aufruf, der tatsächlich sendet) in dieser Codebasis nie geprüft wurde, weder gemockt noch echt. Vor einer Nutzung dieses Gateways in Produktion mindestens einmal manuell `bxAgents serve` gegen ein Projekt mit echten IMAP-Zugangsdaten UND einer echten `cbmailservices`-/`bx-mail`-Installation durchführen (bestätigen, dass `box install` erfolgreich war und `moduleSettings.cbmailservices` auflöst) - das ist das am wenigsten verifizierte der bisher ausgelieferten vier Push-Style-Gateways.

`WhatsAppCloudGatewaySpec.bx` deckt die eigene Logik des Gateways gründlich und echt ab, nicht gemockt: Der Signaturprüfungspfad wird mit tatsächlich berechneten HMAC-SHA256-Signaturen geprüft (`javax.crypto.Mac`/`SecretKeySpec`, in dieser Session unabhängig sowohl gegen `openssl dgst -hmac` als auch gegen Pythons eigenes `hmac`-Modul querverifiziert, bevor der BoxLang-Berechnung vertraut wurde - eine echte Referenzvektor-Abweichung wurde dabei aufgefangen und stellte sich als Tippfehler im handkopierten Erwartungswert heraus, nicht als Fehler, aber nur die Querverifikation hat das aufgefangen), der Verify-Handshake, Webhook-Dispatch/-Dedup, Outbound-Send und interaktives Button-/Listen-Rendering werden alle durch die echten öffentlichen Methoden des Gateways gesteuert, wobei nur der ausgehende Graph-API-HTTP-Aufruf selbst gestubbt ist (`setApiCaller()`). Was NICHT verifiziert wurde: die eigenen ColdBox-Request-Context-Aufrufe des generierten `handlers/WhatsAppCloud.bx` (`event.getHTTPContent()`/`event.getHTTPHeader()`/`event.renderData()`, der über das URL-Scope gemergte, dotted-key Query-Parameter-Zugriff von `rc` für den GET-Handshake) gegen einen echten ColdBox-Boot - das SIND die dokumentierten, standardmäßigen ColdBox-REST-Handler-Idiome (gegen ColdBoxs eigenes Rezept "Building REST APIs" bestätigt, nicht geraten), ein deutlich vertrauenswürdigerer Ausgangspunkt als die undokumentierte `aiGatewayRegistry()`-Schlüssel-Annahme, die sich an anderer Stelle in dieser Datei als falsch herausstellte - aber "dokumentiert" ist nicht dasselbe wie "in diesem generierten Kontext als funktionierend belegt", und die eigenen echten `runColdBoxIntegrationTests.bxs`-/Miniserver-Harness des Projekts darauf zu erweitern, hätte entweder erfordert, gefälschte Umgebungsvariablen in einen separat gestarteten Miniserver-Subprozess zu übergeben (kein bestehender Mechanismus dafür), oder ein Risiko echter, konfigurationsbedingter Boot-Fehler in der geteilten `e2e-coldbox-route`-Fixture einzugehen, von der andere bestehende Tests abhängen - zurückgestellt, statt diesen Wirkungsradius für eine Lücke einzugehen, bei der die Wahrscheinlichkeit eines tatsächlichen Fehlers geringer ist als beim Registry-Schlüssel-Fehler. Vor einer Nutzung dieser Route in Produktion mindestens ein echtes `bxAgents serve` + einen echten Meta-Webhook-Test (oder `curl`) gegen `/webhooks/whatsapp-cloud` durchführen. Es wurde auch nie ein echter Graph-API-Aufruf getätigt - die HTTP-Schicht von `deliver()`/`requestHumanInteraction()` wird nur über die `apiCaller`-Testnaht geprüft.

`TeamsGatewaySpec.bx` deckt die eigene Logik des Gateways gründlich und echt ab, nicht gemockt: Die JWT-Verifikation wird gegen ein tatsächlich generiertes 2048-Bit-RSA-Schlüsselpaar geprüft (`java.security.KeyPairGenerator`) und handsignierte Test-JWTs, die vollständig innerhalb der Spec gebaut werden (keine vorberechneten Fixtures, keine externe `openssl`-Abhängigkeit zur Testzeit) - eine gültige Signatur wird akzeptiert und dispatcht, während eine manipulierte Signatur, eine falsche `aud`, eine falsche `iss` und ein abgelaufenes `exp` jeweils unabhängig bestätigt mit 401 abgelehnt werden. Der Invoke-Activity-Pfad (Adaptive-Card-Button-Klick), Nachrichten-Dispatch, personenbezogene Filterung (nur Scope), Threading über `replyToId`, Chunking und Adaptive-Card-Rendering werden alle durch die echten öffentlichen Methoden des Gateways gesteuert, wobei nur der ausgehende Connector-REST-Aufruf selbst gestubbt ist (`setApiCaller()`) und die JWKS-/OAuth2-Token-Abrufe gestubbt sind (`setJwksFetcher()`/`setTokenFetcher()`). Was NICHT verifiziert wurde: die eigenen ColdBox-Request-Context-Aufrufe des generierten `handlers/Teams.bx` gegen einen echten ColdBox-Boot (dieselbe Art von Lücke wie beim eigenen Handler von WhatsApp Cloud, aus demselben Grund zurückgestellt - siehe diesen Eintrag oben); es wurde nie ein echter OAuth2-Token-Abruf oder Connector-REST-Aufruf gegen Microsofts tatsächliche Endpunkte getätigt; und der Kompromiss "JWKS für die Lebensdauer der Instanz gecacht" (siehe den Teams-Abschnitt in `docs/conventions/gateways.md`) bedeutet, dass auch ein echtes Schlüsselrotationsszenario nie geprüft wurde. Vor einer Nutzung dieses Gateways in Produktion mindestens ein echtes `bxAgents serve` + eine echte Teams-App-Registrierung (App-ID/Passwort aus dem Azure-/Bot-Framework-Portal) + einen echten Teams-Client, der eine DM sendet, durchführen.

`TwilioGatewaySpec.bx` deckt die eigene Logik des Gateways gründlich und echt ab, nicht gemockt: Der `X-Twilio-Signature`-HMAC-SHA1-/Base64-Verifikationspfad wird mit einer inline in der Spec tatsächlich berechneten Signatur geprüft, in dieser Session unabhängig gegen Pythons eigene `hmac`-/`hashlib`-Module querverifiziert, bevor der BoxLang-Implementierung vertraut wurde (dieselbe Disziplin wie bei WhatsApp Clouds eigenem HMAC-SHA256-Querabgleich) - eine echte Referenzsignatur wurde in Python für eine bekannte Kombination aus Auth-Token/URL/Parametern berechnet und bestätigt, dass sie exakt mit BoxLangs Ausgabe übereinstimmt. Die Form-Body-Analyse (einschließlich eines wörtlichen `+`, das korrekt über die Prozent-Kodierung `%2B` hin- und zurückläuft), der `publicUrl`-Override für Proxy-/Tunnel-Deployments, das zweigleisige Modell "TwiML-Bestätigung dann asynchrone REST-Antwort", Outbound-Chunking und die per Telefonnummer geschlüsselte HITL-Antwortkorrelation werden alle durch die echten öffentlichen Methoden des Gateways gesteuert, wobei nur der ausgehende Messages-API-HTTP-Aufruf selbst gestubbt ist (`setApiCaller()`). Was NICHT verifiziert wurde: der eigene `event.getUrl()`-Aufruf des generierten `handlers/Twilio.bx` gegen einen echten ColdBox-Boot (dieselbe Art von Lücke wie bei den eigenen Handlern von WhatsApp Cloud/Teams, aus demselben Grund zurückgestellt) - `event.getUrl()` ist eine dokumentierte ColdBox-Routable-/Request-Context-Methode (über die ColdBox-Docs-MCP bestätigt, keine Vermutung), aber "dokumentiert" ist nicht "in diesem generierten Kontext belegt". Es wurde auch nie ein echter Twilio-Messages-API-Aufruf getätigt. Vor einer Nutzung dieser Route in Produktion mindestens ein echtes `bxAgents serve` + einen echten Twilio-Telefonnummer-Webhook-Test durchführen - und die per Telefonnummer geschlüsselte HITL-Korrelation hat eine echte, dokumentierte Einschränkung (siehe den Twilio-Abschnitt in `docs/conventions/gateways.md`): Ein zweiter HITL-Request an dieselbe Telefonnummer, bevor der erste beantwortet wurde, würde den `pendingApprovals`-Eintrag des ersten überschreiben und ihn stillschweigend verwaisen lassen. Für eingehende SMS ist außerdem keine Allowlist/Ratenbegrenzung eingebaut - anders als bei Eve, das dokumentiert (aber im Code nicht erzwingt), dass die eigene `allowFrom`-Konfiguration "mandatory" ist, hat dieser Port überhaupt kein entsprechendes Gate - jede Telefonnummer kann eine deployte Twilio-Nummer anschreiben und den Agenten erreichen.

`GitHubGatewaySpec.bx` deckt die eigene Logik des Gateways gründlich und echt ab, nicht gemockt, und die Kernlogik dieses Gateways wurde zusätzlich während der Entwicklung durch einen eigenständigen echten BoxLang-Smoke-Test gesteuert (nicht nur die dauerhafte Spec) - so wurde ein echter Fehler gefunden, bevor er je die Testsuite erreichte: Die Substring-Logik des Mention-Extraktions-Helfers rief `left( body, 0 )` auf, wann immer der `@mention` ganz am Anfang eines Kommentars stand (ein sehr häufiger Fall), und BoxLangs `left()` wirft `"Count cannot be zero"`, statt bei einer Anzahl von null einen leeren String zurückzugeben - bestätigt durch Auslösen mit einem echten Kommentartext, dann behoben, indem explizit um den Nullenlängenfall herum verzweigt wird, statt anzunehmen, dass `left()`/`mid()` das tolerieren. Die `X-Hub-Signature-256`-Verifikation, das `@mention`-Regex-Lookahead-Gating (bestätigt über einen dedizierten Smoke-Test, dass ein Bot namens `mybot` bei `@mybot2` NICHT auslöst), Bot-Loop-Schutz, Delivery-ID-Dedup, Issue-versus-Review-Thread-Konversationsidentität, `deliver()` und die `@mention`-zu-Reply-HITL-Korrelation werden alle durch die echten öffentlichen Methoden des Gateways gesteuert, wobei nur der ausgehende GitHub-REST-Aufruf selbst gestubbt ist (`setApiCaller()`). Was NICHT verifiziert wurde: das generierte `handlers/GitHub.bx` gegen einen echten ColdBox-Boot (dieselbe Art von Lücke wie beim eigenen Handler jedes anderen Webhook-Gateways in diesem Projekt, aus demselben Grund zurückgestellt). Es wurde nie ein echter GitHub-API-Aufruf getätigt, und nie eine echte GitHub-App/PAT gegen ein echtes Repository verwendet - vor einer Nutzung dieses Gateways in Produktion mindestens ein echtes `bxAgents serve` + einen echten, gegen ein Test-Repository konfigurierten GitHub-Webhook durchführen.

`SignalGatewaySpec.bx` deckt die eigene Logik des Gateways gründlich und echt ab, nicht gemockt: Das JSON-RPC-/SSE-Parsing von `handleSseEvent()` (Leerzeilen-/ungültige-JSON-Behandlung, Gruppen-Nachrichten-Filterung, Zitat-Threading, `sourceUuid`-Fallback bei fehlendem Anzeigenamen), die Sende-Form und das Chunking von `deliver()` sowie die HITL-Entscheidungskorrelation/-Zuordnung werden alle durch die echten öffentlichen Methoden des Gateways gesteuert, wobei nur die ausgehenden `rpcCaller`-/`connector`-I/O-Aufrufe gestubbt sind, dieselbe Naht-Test-Disziplin wie bei jedem anderen Gateway in diesem Projekt. Zwei Erkenntnisse auf BoxLang-Ebene traten während der Entwicklung auf, beide gelöst und wert, als allgemeine Minen statt gateway-spezifische Fehler festgehalten zu werden: (1) ein wegwerfbares Smoke-Test-Skript, das eine lokale Variable `request` nannte, interagierte stillschweigend mit BoxLangs eigenem reserviertem `request`-Scope statt eine gewöhnliche Variable zu erzeugen, was irreführende Fehler "Methode nicht gefunden"/"Argumenttyp-Fehlanpassung" von `HttpClient.send()` erzeugte, die genau wie eine echte Java-Interop-Einschränkung aussahen, aber vollständig verschwanden, sobald die Variable umbenannt wurde - `SignalGateway.bx` selbst hatte nie einen Fehler; (2) ein `try/catch` direkt auf oberster Ebene eines eigenständigen `.bxs`-Smoke-Test-Skripts (nicht innerhalb einer Funktion) löste einen `java.lang.VerifyError: Inconsistent stackmap frames` aus, eine echte Bytecode-Verifikationseinschränkung von BoxLangs Top-Level-Skript-Compiler, über den Stacktrace bis zur eigenen generierten Klasse des Testskripts zurückverfolgt, nicht zu `SignalGateway.bx` - behoben, indem das try/catch stattdessen in eine benannte Funktion eingewickelt wurde. Was NICHT verifiziert wurde, und die größte Lücke unter allen bisher ausgelieferten Push-Style-Gateways ist: In dieser Umgebung war nie ein echter `signal-cli`-Daemon verfügbar, sodass der gesamte asynchrone SSE-Verbindungslebenszyklus - das Öffnen des Streams über `HttpClient.sendAsync()`+`BodyHandlers.ofLines()`, die exponentielle Reconnect-Backoff-Schleife gegen eine tatsächlich flackernde Verbindung, der 30s/120s-Idle-Watchdog, der einen Reconnect erzwingt, und ein echter JSON-RPC-Roundtrip - nie Ende-zu-Ende geprüft wurde, nur auf Interop-Ebene per Smoke-Test (ein eigenständiger Test erreichte einen echten `java.net.ConnectException` gegen eine unerreichbare Adresse, was beweist, dass die Kette solide ist, nicht dass sie gegen einen Live-Daemon funktioniert). Vor einer Nutzung dieses Gateways in Produktion mindestens einmal manuell `bxAgents serve` gegen ein Projekt mit einem echten, laufenden `signal-cli`-Daemon und einem echten verknüpften Signal-Konto durchführen - das ist eine in dieser Codebasis genuin neue Transportarchitektur (das einzige SSE-basierte Gateway unter Telegram/Slack/Discord/E-Mail/Signal), nicht nur eine neue Plattform auf einer bereits belegten Transportform.

## WhatsApp Personal (inoffizielle persönliche Konto-Bridge) - recherchiert, nicht gebaut

Der ursprüngliche Plan (angelehnt an Hermes Agents eigene Architektur) sah eine `WhatsAppPersonalGateway` vor, gebaut durch das Starten eines Node.js-Subprozesses mit `@whiskeysockets/baileys` (dem Multi-Device-WhatsApp-Web-Protokoll-Client, den Hermes selbst nutzt - MIT-lizenziert, dessen vollständige `bridge.js` wurde direkt aus Hermes' echtem Quellcode gelesen, nicht zusammengefasst). Dieser Ansatz wurde mitten in der Session auf direkte Anweisung zurückgestellt, um eine native BoxLang-/JVM-Integration einer Subprozess-Bridge vorzuziehen und nur eine native Java-Bibliothek zu verwenden, falls sie Open Source und weder GPL noch LGPL ist.

Diese Suche fand **Cobalt** (`com.github.auties00:cobalt`, ehemals WhatsappWeb4j) - eine echte, MIT-lizenzierte, aktiv gepflegte (900+ Sterne) Java-Implementierung von WhatsApps Multi-Device-"Linked Client"-Protokoll, mit einer dokumentierten fluenten API (`WhatsAppClient.builder().linkedApi().webClient()...`, `addNewMessageListener()`, `sendMessage()`), die sauber aus einer BoxLang-Closure heraus per BoxLangs eigener dokumentierter Java-SAM-Koerzion nutzbar wäre (über die BoxLang-Docs-MCP bestätigt - kein `createDynamicProxy()` nötig für einen einzelnen Single-Abstract-Method-Listener). Zwei echte Blocker traten während der Verifikation auf, nicht geraten:

1. **Der erste Lese-Vorgang der pom.xml (Cobalts `master`-Branch, ein laufendes Multi-Modul-Rewrite) erfordert Java 25** - zwei Hauptversionen vor BoxLangs eigener dokumentierter Baseline (Java 21+, über die BoxLang-Docs-MCP bestätigt und passend zum eigenen `21.0.10`-JDK dieses Projekts). Erneute Prüfung gegen das tatsächlich auf Maven Central veröffentlichte Artefakt (`cobalt:0.0.10`, das echte Ding, das eine `<dependency>` heute auflösen würde, nicht das unveröffentlichte Rewrite) zeigte `<java.version>21</java.version>` - der Java-25-Befund war also ein Fehlalarm, verursacht durch das Lesen des falschen Branches, kein echter Blocker. Wert, als Warnung festgehalten zu werden: Die `pom.xml` des Default-Branches eines GitHub-Repos ist nicht notwendigerweise das, was auf Maven Central liegt.
2. **Das echte, veröffentlichte `cobalt:0.0.10` zieht `com.aspose:aspose-words` als harte Compile-Zeit-Abhängigkeit hinein** (intern genutzt, um Link-Preview-Thumbnails aus Word-Dokumenten zu erzeugen) - Aspose.Words for Java ist kommerziell/proprietär lizenziert, nicht Open Source, sodass das Bündeln dieselbe Lizenzbeschränkung verletzen würde, die Cobalt selbst gerade erfüllen sollte. Der gesamte Abhängigkeitsgraph (~15 Jars: zxing, qr-terminal, curve25519, protobuf-base, jackson oder fastjson2 je nach Version, libphonenumber, dd-plist, apk-parser, link-preview, jaffree, ez-vcard, slf4j, plus Aspose) müsste alle manuell heruntergeladen und im `libs/`-Ordner dieses Moduls gebündelt werden - BoxLang-Module haben keine eigene Maven-artige Abhängigkeitsauflösung (über die BoxLang-Docs-MCP bestätigt: Drittanbieter-Jars werden direkt in den `libs/`-Ordner eines Moduls gebündelt, geladen von einem Pro-Modul-Classloader - es gibt keinen `javaLibraries`-Schlüssel in `box.json`, der automatisch einen Abhängigkeitsbaum auflösen würde).

Angesichts der Lizenzkontamination über Aspose und des manuellen Fat-Jar-Zusammenbau-Aufwands ohne verfügbares Abhängigkeitsauflösungs-Tooling, um zu verifizieren, dass das Ergebnis tatsächlich lädt, wurde **WhatsApp Personal descoped**, statt entweder als echtes Gateway oder als Stub ausgeliefert zu werden. `ProjectValidator`s `validGatewayTypes` und `GatewayGenerator`s `TYPE_CLASS_MAP` enthalten keinen `whatsapp-personal`-Eintrag - ein Projekt, das versucht, einen solchen zu deklarieren, erhält den bestehenden Validierungsfehler "unknown gateway type", wie jeder andere nicht unterstützte Typ, statt einen irreführenden halb gebauten Stub. Das erneut aufzugreifen ist sinnvoll, falls Cobalt jemals die Aspose-Abhängigkeit fallen lässt (es ist ein schmales Feature - Link-Preview-Thumbnailing von Word-Dokumenten -, nicht zentral fürs Messaging), oder falls eine zukünftige Session entscheidet, dass der Node-/Baileys-Subprozess-Bridge-Ansatz (diese Runde aus Architektur-Präferenz abgelehnt, nicht wegen eines technischen Blockers) doch vorzuziehen ist.

## `GatewaySession` ist projektweit und nur für den Root-Agenten (v1)

Ein Projekt mit mindestens einem Push-Style-Gateway-Eintrag erhält genau eine generierte `GatewaySession`, die jedes Push-Style-Gateway bündelt und immer an den Root-Agenten des Projekts gebunden ist - passend zum bestehenden Präzedenzfall, dass die HTTP-Exposure `exposes: "agent"` ebenfalls immer nur den Root-Agenten betrifft (siehe [gateways/](conventions/gateways.md#3-push-style-gateways-type-telegram--slack--discord--email--whatsapp-cloud--teams--twilio--github--signal-and-friends)). Ein Projekt mit Subagenten kann noch nicht unterschiedliche Gateways zu unterschiedlichen Subagenten routen (z. B. "Telegram spricht mit SupportBot, Slack spricht mit ResearchBot"). Ein zukünftiger, pro Gateway konfigurierbarer `targetAgent: "SubagentName"`-Schlüssel, konsumiert von einer Pro-Agenten-Knoten-`GatewaySession` statt einer projektweiten Session, ist die natürliche Erweiterung - noch nicht gebaut.

## Behoben: `GatewaySessionBootstrap.bx` schlug Gateways unter dem falschen `aiGatewayRegistry()`-Schlüssel nach (kaputt über alle vier Push-Style-Gateways ausgeliefert, während der WhatsApp-Recherche-Runde entdeckt)

Ein echter, bereits ausgelieferter Fehler: Der `aiGatewayRegistry().get(...)`-Aufruf des generierten Interceptors verwendete den eigenen Dateinamen des entdeckten `gateways/*`-Eintrags (z. B. `"telegramChannel"`, aus `gateways/telegramChannel.bx`), aber bx-ais echtes `GatewayRegistry.register()` schlüsselt immer nach dem eigenen festen `getName()` der Gateway-KLASSE (z. B. `"telegram"`, einmalig in `TelegramGateway.init()` gesetzt) - nie nach etwas vom Aufrufer Übergebenem. Sowohl durch direktes Lesen des bx-ai-Quellcodes als auch empirisch bestätigt (ein echtes Gateway registrieren, dann `.get()` mit seinem entdeckten Eintragsnamen aufrufen, warf `"No item found in registry"`). Das bedeutete, dass die `GatewaySession`-Konstruktion beim ColdBox-Boot (`afterConfigurationLoad`) für **jedes** generierte Projekt mit einem Push-Style-Gateway einen Fehler geworfen hätte - Telegram, Slack, Discord und E-Mail wurden alle mit diesem Fehler ausgeliefert; es blieb unentdeckt, weil die einzige vorherige Testabdeckung den ROHEN STRING-INHALT der generierten Datei prüfte, nie eine Live-Registry.

Behoben in `GatewayGenerator.generate()`: Der Interceptor schlägt Gateways jetzt anhand ihres TYPE-Strings nach (der für jedes bisher gebaute Push-Style-Gateway immer identisch zum registrierten Namen ist), dedupliziert. `GatewayGeneratorSpec.bx` erhielt einen dauerhaften Regressionstest, der eine ECHTE Gateway-Instanz registriert und belegt, dass der EXAKTE, gerade vom Generator emittierte Schlüssel sie über eine echte `aiGatewayRegistry()` auflöst - was genau die Lücke schließt, die es dieses Mal unentdeckt ausliefern ließ.

**Eine echte, dauerhafte Konsequenz, die dieser Fix sichtbar macht (kein neues Verhalten, jetzt nur korrekt erreichbar)**: Da die Registry nach Typ, nicht nach Eintrag geschlüsselt ist, **kollidieren zwei `gateways/*`-Einträge desselben Push-Style-Typs auf demselben Registry-Slot, projektweit** - z. B. würden zwei `type: "telegram"`-Einträge (zwei verschiedene Bot-Tokens) still die zweite Registrierung die erste überschreiben lassen, und `GatewaySession` würde nur je eines der beiden sehen. Es gibt heute keinen Pro-Eintrag-Alias/Registrierungsnamen-Override. Eine Instanz pro Push-Style-Typ, pro Projekt, ist die tatsächliche v1-Obergrenze - vor diesem Fix, der es sichtbar machte, nicht als solche dokumentiert.

## `./gradlew downloadModules` kann einen bx-ai-Snapshot holen, der vorübergehend hinter der `aiGatewayRegistry()`-Codegenerierung von `GatewayGenerator` zurückliegt

bx-ai hat `gatewayRegistry()` auf seinem `development`-Branch in `aiGatewayRegistry()` umbenannt (direkt bestätigt - `bifs/gatewayRegistry.bx` wurde komplett gelöscht, kein Rückwärtskompatibilitäts-Alias) - `GatewayGenerator` wurde entsprechend angepasst, da bx-ai noch kein Release geschnitten hat und die eigene Anweisung dieses Projekts lautete, direkt zu folgen, statt drumherum zu shimmen. Der Haken: `downloadModules` holt ein festes, kontinuierlich neu veröffentlichtes Snapshot-Artefakt (`bx-ai@3.4.0-snapshot`) von `downloads.ortussolutions.com`, und dieses veröffentlichte Zip kann dem eigenen Git-`development`-HEAD von bx-ai um eine gewisse Zeit hinterherhinken (in dieser Session direkt bestätigt: unmittelbar nachdem diese Umbenennung vorgelagert landete, hatte der herunterladbare Snapshot noch das ALTE `gatewayRegistry.bx`). Holt ein frisches `downloadModules` einen Snapshot, der dieser Umbenennung vorausgeht, scheitert jedes Projekt mit einem Channel-Adapter-`gateways/*`-Eintrag beim Booten mit `Function 'aiGatewayRegistry' not found`, da der generierte Code jetzt den neuen Namen aufruft, das geladene Modul aber nur den alten hat. Das lässt sich nicht von der BX-Agents-Seite aus beheben - es löst sich von selbst, sobald ForgeBox den Snapshot aus dem aktuellen bx-ai-`development` neu veröffentlicht. Verifiziert, dass der eigene generierte Code dieses Projekts korrekt gegen bx-ais echten HEAD ist, indem eine lokale Modulstruktur direkt aus dessen Git-Quellcode (`ortus-boxlang/bx-ai`) gebaut wurde, statt sich auf das möglicherweise veraltete heruntergeladene Zip zu verlassen.

Denselben Zeitversatz unabhängig bestätigt, während an der Push-Style-Gateway-/`GatewaySession`-Arbeit gebaut wurde: Der zu dieser Zeit heruntergeladene `bx-ai@3.4.0-snapshot` hatte noch kein `GatewaySession.bx`, keine `aiGatewaySession()`/`aiGatewayRegistry()`-BIFs und ein `BaseGateway.bx`/`IGateway.bx` ganz ohne `onMessage()`/`onError()` - ein Snapshot von deutlich vor derselben Umbenennung. `testBx` wurde in diesem Zustand ausgeführt, indem `bifs/`/`models/`/`public/`/`ModuleConfig.bx` von `src/test/resources/modules/bxai` durch eine frische Kopie aus bx-ais eigenem Git-Quellcode ersetzt wurden (dessen `libs/`/`box.json` unangetastet gelassen), dieselbe Umgehung wie oben - nichts, was End-Nutzer von `build`/`serve` selbst tun müssen, sobald ForgeBox aufgeholt hat, aber notwendig für die eigene CI-lose Verifikation dieser Session.

**CI umgeht das jetzt automatisch, weil es musste.** Der erste echte GitHub-Actions-Lauf dieser Suite belegte, dass der Zeitversatz nicht kosmetisch ist: Der veröffentlichte `bx-ai@3.4.0-snapshot` liefert weiterhin `bifs/gatewayRegistry.bx` aus (vorgelagert längst in `aiGatewayRegistry` umbenannt) und enthält **überhaupt kein `models/gateway/BaseGateway.bx`** - die Klasse, die alle neun Push-Style-Gateways in diesem Modul erweitern. Dagegen getestet, schlagen 41 Specs aus Gründen fehl, die nichts mit dem eigenen Code dieses Moduls zu tun haben (`The method aiGatewayRegistry does not exist`, dann jede Gateway-Spec kaskadierend an einer Basisklasse, die nicht da ist). `.github/workflows/tests.yml` klont deshalb bx-ais `development`-Branch, führt dessen `createModuleStructure` aus und überlagert das Ergebnis über das, was `downloadModules` geholt hat - dieselbe manuelle Umgehung wie oben, automatisiert. `bx-ftp` und `bx-sqlite` sind stabile Releases und kommen weiterhin unverändert von `downloadModules`. Den Überlagerungsschritt löschen, sobald ein veröffentlichter Snapshot aufgeholt hat; bis dahin gilt: CI testet gegen bx-ais Branch-HEAD statt gegen ein gepinntes Artefakt, sodass eine vorgelagerte Fehlfunktion hier als bx-agents-Fehler erscheint.

Ein drittes Mal getroffen, beim Verdrahten von `/compact`: bx-ai hat `IAiMemory.summarize()` auf `development` nach `userId`/`conversationId` geschoppt (Commit `f9ac7bd`), aber ein frisches `downloadModules` holte weiterhin einen Snapshot mit dem alten einargumentigen `summarize( struct config = {} )`. Dieselbe Umgehung - das Modul wurde aus bx-ais eigenem Git-Quellcode neu gebaut (`./gradlew createModuleStructure` in diesem Repo, kopiert über `src/test/resources/modules/bxai`) und das geschoppte Verhalten direkt gegen diesen Build verifiziert. Anders als bei den ersten beiden hat dieser eine Laufzeitkonsequenz, auf die eine Endnutzerin stoßen kann: `/compact` ruft `mem.summarize( config, userId, conversationId )` auf, und auf einem bx-ai vor diesem Commit werden die zusätzlichen Argumente einfach ignoriert, sodass die Kompaktierung den *Standard*-Scope der Memory-Instanz zusammenfassen würde statt den des Aufrufers. Eine generierte App braucht daher bx-ai auf oder nach `f9ac7bd`, spezifisch für `/compact`; jede andere Route ist unbetroffen. Nichts in der eigenen Testsuite dieses Repos regressiert dabei auf einem älteren Snapshot, da die Web-UI-Specs generierten Quelltext prüfen, statt ihn auszuführen.

## Die v1-Web-Chat-UI (`exposes: "webui"`) - was echt ist, und was nur gegen die Dokumentation, nicht gegen einen Live-Server geprüft werden konnte

`WebUiGeneratorSpec.bx` und ein Ende-zu-Ende-Test von `BuildPipelineSpec.bx` steuern beide die echten `WebUiGenerator`-/`BuildPipeline`-Klassen gegen echte Fixtures: Die statische `<path>/index.html`-Hülle wird bestätigt geschrieben und korrekt templatet (`__API_BASE__`-/`__APP_TITLE__`-Platzhalter ersetzt, nie in der Ausgabe verbleibend), die optionale `interceptors/WebUiAuthGate.bx` wird bestätigt nur generiert, wenn `apiKeyEnvVar` konfiguriert ist, sperrt genau `<path>/api/*` und nie die bloße `<path>`-Hülle selbst, und wird korrekt in die `interceptors:[...]`-Liste von `config/ColdBox.bx` registriert, Ende-zu-Ende über die echte `BuildPipeline`. Der generierte Interceptor wurde in dieser Session zusätzlich bestätigt, sauber über einen eigenständigen Smoke-Test zu kompilieren und zu instanziieren (geladen über dieselbe `DynamicClassLoader`-Primitive, die die Build-Pipeline selbst nutzt).

Was NICHT verifiziert wurde - und in dieser Entwicklungsumgebung auch nicht konnte: ein echter `bxAgents serve` + ein echter Browser-Test des tatsächlichen Ladens der Seite, des Streamens einer Antwort, und des tatsächlichen Ablehnens/Akzeptierens von Requests über echtes HTTP durch das `X-API-Key`-Gate. Das eigene `runColdBoxIntegrationTests.bxs`-/`tests/coldbox`-Harness dieses Projekts (dasselbe, das belegt hat, dass `toAi()`s `/invoke`-Route für `http-gateway-agent` Ende-zu-Ende funktioniert) erfordert, dass `tests/coldbox` über ein echtes `box install` in `tests/` vorhanden ist - CommandBox- und ForgeBox-Netzwerkzugriff waren in der Sandbox dieser Session beide nicht verfügbar, sodass dieses Harness für die Web-UI (oder erneut für alles andere) in dieser Session nicht ausgeführt werden konnte. Zwei direkte Konsequenzen:

- Die exakte `/invoke`-JSON-Antwortform, die die eigenen generierten Seiten/Dokumente dieses Projekts verwenden (`{"input": "..."}` rein, eine Antwort mit `"success": true`), ist **nur** empirisch über die eigene, bereits bestehende Assertion von `runColdBoxIntegrationTests.bxs` bestätigt (eine Substring-Prüfung, keine vollständige Form-Assertion) - in dieser Session nicht erneut verifiziert.
- Das `/stream`-SSE-Wireformat, das das JS der Web-UI selbst parst (`data: {"token":"..."}`-Zeilen, terminiert durch `data: [DONE]`), stammt direkt aus ColdBoxs eigener offizieller "AI Routing"-Dokumentation und wurde in dieser Session nicht unabhängig gegen einen Live-Server erneut bestätigt - anders als fast jede andere Wireformat-Behauptung in den Dokumenten dieses Projekts, die, wo immer möglich, gegen echten laufenden Code querverifiziert wurden (siehe z. B. die unabhängigen Python-/openssl-Querabgleiche der HMAC-SHA256-/SHA1-Signaturschemata an anderer Stelle in dieser Datei).

Vor einer Nutzung dieses Features in Produktion mindestens ein echtes `bxAgents serve` + einen echten Browser-Test durchführen (Nachricht gesendet, Antwort streamt herein, das `X-API-Key`-Gate lehnt einen Request ohne den Schlüssel tatsächlich mit 401 ab) - derselbe Standardrat, der bereits an anderer Stelle in dieser Datei für die ungeprüfte Lücke jedes generierten Webhook-Handlers gegen einen echten Boot gegeben wurde.

## Der SQLite-Store der Web-UI - auf Bibliotheksebene verifiziert, nicht über einen echten ColdBox-Boot

Der qb-+-bx-sqlite-Stack unter `models/ChatDb.bx` wurde in dieser Session direkt gegen die echten Jars verifiziert, nicht aus der Dokumentation abgeleitet: qb 13.1.0s `.cfc`-Quellen kompilieren und laufen nativ auf BoxLang 1.16, ganz ohne `bx-compat-cfml`; `SQLiteGrammar` + `SchemaBuilder` erzeugen die v1-Tabellen und -Indizes tatsächlich gegen eine echte SQLite-Datei; ein zweiter Migrationsdurchlauf ist ein sauberer No-Op; `QueryBuilder` roundtrippt Inserts, gefilterte/sortierte Reads und Deletes; und der zusammengesetzte Primärschlüssel von `preferences` lehnt ein doppeltes `(userId, prefKey)` tatsächlich ab. Zwei Einschränkungen wurden auf diesem Weg gefunden, nicht angenommen - qb erfordert eine **benannte** Datenquelle (die eigene `appendSqlComments()`-Funktion typisiert dieses Argument als `string`, sodass eine Inline-Struktur wirft, bevor je ein SQL läuft), und `SchemaBuilder@qb` ist nur mit seinem `grammar`-Argument gemappt und erhält nie `moduleSettings.qb.defaultOptions` - und der generierte Code ist um beides herum geformt.

Was **nicht** verifiziert wurde, aus demselben Grund wie beim Rest der Web-UI: nichts davon lief innerhalb eines echten ColdBox-Boots. `tests/coldbox` erfordert ein echtes `box install`, und CommandBox-/ForgeBox-Netzwerkzugriff war in der Sandbox dieser Session nicht verfügbar. Die Migrationslogik ist also belegt, aber drei Verdrahtungsannahmen sind nicht Ende-zu-Ende geprüft: dass `getInstance( "ChatDb" )` über das eigene WireBox der generierten App auflöst, dass `SchemaBuilder@qb`/`QueryBuilder@qb` auflösen, sobald qb als echtes ColdBox-Modul installiert ist (qb ist eine `box.json`-Abhängigkeit, hier nicht vendort - dieselbe ehrliche Lücke, die `cbmailservices` bereits trägt), und dass `this.datasources` im generierten `Application.bx` wie erwartet aufgegriffen wird. Vor einer Nutzung des Stores in Produktion einmal `bxAgents serve` gegen ein webui-Projekt mit tatsächlich installiertem `qb` und `bx-sqlite` durchführen; der Fehlermodus, falls eines davon fehlt, ist beim Boot laut (eine nicht auflösbare WireBox-Zuordnung oder ein unbekannter JDBC-Treiber), nicht still.

## Die Web-UI über echtes HTTP: durch die Probe des Integration-Runners belegt

`runColdBoxIntegrationTests.bxs` ruft `GET /chat/api/health` von außerhalb des Servers ab, gegen die von einem echten `boxlang-miniserver` gebootete generierte Fixture-App, und lässt den Build scheitern, falls es kein 200 ist. In CI liefert es:

```
+ App probe GET /chat/api/health -> status=200
  body: {"success":true,"status":"ok"}
```

Dieser eine Request ist der Ende-zu-Ende-Beweis für die Serverseite der Web-UI: ColdBox-Routing, das `handlers/ChatUi.bx` erreicht, WireBox, das den Handler und `ChatDb` auflöst, `this.datasources`, das bx-sqlite eine nutzbare SQLite-Datei gibt, und qb als tatsächlich aktiviertes ColdBox-Modul - nichts davon lässt sich durch eine Quelltext-Assertion belegen. `WebUiRuntimeSpec.bx` deckt dann das Verhalten des Stores ab (Migrationen, gescoptes Konversations-CRUD, die Cross-User-Absicherung, Präferenz-Upserts), indem echte Objekte innerhalb desselben Boots aus WireBox gelesen werden.

**Was noch nicht abgedeckt ist:** die übrigen webui-Routen über HTTP. Nur `/health` wird von außerhalb des Prozesses abgerufen. Den Rest von `WebUiRuntimeSpec` aus zu steuern ist wie strukturiert nicht möglich, weil diese Spec *innerhalb* eines von genau demselben einzelnen Miniserver bedienten Requests läuft, sodass ein Loopback-Aufruf um denselben Worker-Pool konkurrieren würde, den der Runner selbst belegt. Den Rest abzudecken braucht entweder einen zweiten Serverprozess oder einen größeren Miniserver-Thread-Pool, und ist eher ein natürlicher Folgeschritt als etwas zum Vortäuschen.

Zu beachten: Dieser Absatz zitierte früher `runner-coldbox.bxm never responded: HTTP 408` als Beleg für diese Aushungerung. **Diese Zuschreibung war falsch**, und es lohnt sich, das festzuhalten, weil es dieses Projekt mehrere CI-Zyklen lang in die Irre führte: Die 408 hatte nichts mit Loopback-Aufrufen zu tun. Die Runner-Seite rief `chr( 10 )` auf - ein nicht existierender BIF -, sodass sie bei jedem Request von Anfang an mit 500 antwortete, und die Retry-Schleife des Orchestrators mahlte sich 90 Sekunden lang durch diese 500er, bevor sie den endgültigen Timeout meldete. Beides ist behoben. Die obige Sorge um Loopback-Aushungerung ist ein genuin struktureller Grund, keine weiteren Routen aus dieser Spec heraus zu steuern, bleibt aber eine gut begründete Erwartung, statt etwas, das hier tatsächlich beim Scheitern beobachtet wurde.

## Das eigene Frontend der Web-UI: in einem echten Browser gesteuert, aber gegen eine gemockte API

Das JavaScript der ausgelieferten Seite wurde in dieser Session tatsächlich gesteuert, nicht nur als Quelltext geprüft: Das generierte `index.html` wurde in einem headless Chromium geladen und Ende-zu-Ende durchgesteuert, wobei jede `<path>/api/*`-Route abgefangen und mit realistischen Payloads beantwortet wurde. Auf diesem Weg bestätigt funktionierend: das Rendern der Konversations-Seitenleiste aus `GET /conversations` sowie Wechseln/Umbenennen/Löschen über die eigenen Routen; `GET /info`, das die Toolbar formt (Compact erscheint nur, wenn `capabilities.compact` true ist, der Modellname landet im Header); das Theme, das serverseitig aus `preferences` ankommt und angewendet wird; `/history`, das das Transkript rehydriert; ein echter SSE-Turn, der Inhalt, Reasoning und Tool-Call-Chips aus der bx-ai-Hülle streamt; Markdown-Rendering; und **New chat**, das serverseitig eine Konversation erzeugt und öffnet. Null JavaScript-Fehler, null unersetzte `__TOKEN__`-Platzhalter, und das schmale Bildschirm-Layout wurde bei 390px gescreenshottet.

Was das **nicht** belegt: Die API war ein Playwright-Mock, nicht das generierte `handlers/ChatUi.bx`, das unter einem echten ColdBox-Boot gegen einen echten SQLite-Store läuft. Die Request- und Antwortformen wurden aus dem eigenen Quelltext des generierten Handlers übernommen, sodass eine Abweichung zwischen beiden hierdurch nicht aufgefangen würde. Alles im obigen Eintrag zu bekannten Einschränkungen über den Store gilt auch hier - ein manuelles `bxAgents serve` gegen ein webui-Projekt mit tatsächlich installiertem `qb` und `bx-sqlite` bleibt das ehrliche Tor vor einer Produktionsnutzung.
