# Wie MCP-stderr-Limits Agentenläufe am Laufen halten

Ein lautes Hilfsprogramm kann einen Agentenlauf stoppen, ohne auch nur eine einzige JSON-RPC-Nachricht zu beschädigen. Es verwandelt stderr in eine unbegrenzte Warteschlange und lässt dann einen anderen Teil des Stacks die Rechnung bezahlen: Eine Pipe läuft voll, ein Leser hält Megabytes fest, das Herunterfahren wartet endlos oder eine Genehmigungskarte erscheint erst, nachdem der Benutzer dem Lauf nicht mehr vertraut.

Behandle stderr als nicht vertrauenswürdige Eingabe mit einem festen Budget. Das klingt kleinlich, bis du gesehen hast, wie ein ganz gewöhnlicher Befehl für jeden Wiederholungsversuch eine Diagnose ausgibt, während die unterstützte Aktion längst abgeschlossen ist und der übergeordnete Prozess weiterhin versucht, die Ausgaben einzusammeln. Die Lösung lautet nicht „Logging abschalten“. Die Lösung besteht darin, den Datenstrom kontinuierlich zu leeren, nur eine begrenzte Menge zu behalten, verworfene Daten zu erfassen und den Aktionsstatus unabhängig von der Textausgabe zu halten.

## Stderr ist ein Rückstaupfad

Stderr kann ein Hilfsprogramm blockieren, wenn der übergeordnete Prozess es an eine Pipe anschließt und diese nicht schnell genug ausliest. Betriebssysteme geben Pipes nur begrenzte Puffer. Sobald ein Kindprozess diesen Puffer füllt, wartet der nächste Schreibvorgang auf einen Leser. Wenn das Kind erst nach dem Schreiben der Diagnose den Erfolgsweg erreicht, scheint die Aktion zu hängen, obwohl die Netzwerk-Anfrage, die SSH-Verbindung oder die lokale Arbeit ansonsten problemlos war.

Der umgekehrte Fehler ist weniger auffällig, aber oft teurer. Ein übergeordneter Prozess kann stderr rechtzeitig lesen und jedes Byte an einen String, einen Ereignispuffer oder ein MCP-Tool-Ergebnis anhängen. Die Pipe läuft nie voll, doch ein redseliges Kind kann genug Arbeitsspeicher verbrauchen, um den Host zu verlangsamen, Speicherdruck auszulösen oder spätere Aufrufe scheitern zu lassen. Eine Begrenzung, die nur die Pipe schützt, reicht nicht aus. Eine Begrenzung, die nur den Arbeitsspeicher schützt, ebenfalls nicht.

Bei Agenten fällt das stärker ins Gewicht, weil sie viele Hilfsprozesse parallel starten. Eine Aufgabe kann mehrere Helfer starten, und eine Wiederholungsschleife kann eine neue Welle erzeugen, bevor die erste abgearbeitet ist. Eine im interaktiven Terminal harmlos wirkende Log-Rate wird zum Ressourcenproblem, wenn mehrere Sitzungen sie gleichzeitig erfassen.

Es gibt drei getrennte Größen, die begrenzt werden müssen:

- Bytes, die in der Pipe des Betriebssystems warten.
- Bytes, die die Bridge für einen Aufruf im Arbeitsspeicher behält.
- Bytes, die die Bridge einem Agenten, einer Benutzeroberfläche oder einer Journal-Projektion zur Verfügung stellt.

Verwechsle eine Zeilenbegrenzung nicht mit einer Byte-Begrenzung. Eine einzige Zeile kann einen großen Antworttext, eine Zertifikatskette oder ein minimiertes Fehlerobjekt enthalten. Gehe außerdem nicht davon aus, dass UTF-8 immer in vollständigen Zeichen ankommt. Stream-Leser erhalten Bytes in Blöcken, und die Begrenzung muss auch dann funktionieren, wenn ein Mehrbytezeichen eine Blockgrenze überschreitet.

Ein praktischer Leser führt einen Byte-Zähler für den gesamten Datenstrom und einen separaten Ringpuffer für den behaltenen Ausschnitt. Sobald die Gesamtmenge das Limit überschreitet, liest er weiter, damit das Kind beendet werden kann. Er lässt den gespeicherten Puffer nicht weiter wachsen und vermerkt, dass eine Kürzung stattgefunden hat. Das Kind sofort beim ersten überschüssigen Byte zu beenden, kann für ein Hilfsprogramm richtig sein, dessen Ausgabe selbst missbräuchlich ist. Für Diagnosedaten ist es jedoch die falsche Standardeinstellung. Oft brauchst du den tatsächlichen Exit-Status und die letzten Zeilen, die den Fehler erklären.

## MCP hält stdout außerhalb der Grenzen

Ein MCP-stdio-Server muss stdout als Protokollbereich behandeln. Die Hinweise zum stdio-Transport in der Spezifikation des Model Context Protocol besagen, dass ein Server außer gültigen MCP-Nachrichten nichts auf stdout schreiben darf. Diese Regel lässt sich leicht als reine Formatierungsvorgabe abtun. In der Praxis verhindert sie eine weitaus hässlichere Fehlerklasse: Eine beiläufige Fortschrittszeile des Hilfsprogramms kann den Host dazu bringen, ungültiges JSON zu lesen und eine ansonsten intakte Sitzung aufzugeben.

Lege menschlich lesbare Diagnosen auf stderr, aber gehe nicht davon aus, dass sie dadurch harmlos werden. Stderr ist nur im protokollarischen Sinn ein Kanal außerhalb des Bandes. Der Prozess-Host entscheidet weiterhin, ob er diesen Datenstrom erbt, über eine Pipe leitet, erfasst, in ein Terminal schreibt oder in ein strukturiertes Log weiterleitet. Jede Wahl verändert das Fehlerverhalten.

Vererbung funktioniert für Entwickler lokal, weil das Terminal die Ausgabe verarbeitet und der Entwickler sie sehen kann. Für eine Agenten-Bridge ist sie eine schlechte Standardeinstellung. Sie leitet beliebigen Text an einen Ort weiter, der möglicherweise nicht gespeichert wird, Ausgaben paralleler Aufrufe vermischen kann und Details enthalten kann, die der Agent niemals erhalten sollte. Eine vollständige Erfassung hilft bei der Diagnose nur dann, wenn sie ein Budget hat.

Halte die Protokollgrenze unspektakulär. Der MCP-Server sollte gültige JSON-RPC-Nachrichten auf stdout senden, seine eigenen Diagnosedaten auf stderr begrenzen und Hilfsprogramme mit ausdrücklich kontrollierten Datenströmen starten. Ein Hilfsprogramm sollte strukturierte Ergebnisse über den vorgesehenen Kanal zurückgeben. Es sollte kein JSON-Objekt auf stderr ausgeben und hoffen, dass der übergeordnete Prozess es später erkennt.

Diese Unterscheidung verhindert einen wiederkehrenden Fehler: stderr als alternativen Antwortkanal zu behandeln. Das ist er nicht. Der Kanal hat keine zuverlässige Rahmung, kann beim Abbruch unvollständig sein und kann Ausgaben von Bibliotheken enthalten, die nichts von deinem Aktionsmodell wissen. Wenn ein Aufrufer eine Anzahl von Wiederholungen, einen entfernten Fehlercode oder eine Liste geänderter Dateien braucht, gehört diese Information in das strukturierte Ergebnis. Stderr sollte Nachweisen vorbehalten bleiben, die eine Person bei der Diagnose eines Fehlers brauchen könnte.

## Das Erfassen der Ausgabe braucht ein eigenes Speicherbudget

Stderr in einer Hintergrundaufgabe zu lesen, macht die Erfassung nicht sicher. Es verlagert den Engpass lediglich von der Pipe auf den Heap. Ich habe Hosts gesehen, die einen Deadlock behoben haben, indem sie beide Datenströme gleichzeitig lasen, und danach feststellen mussten, dass ein fehlerhaftes Hilfsprogramm sie zwang, jedes Byte bis zum Ende der Aktion aufzubewahren. Der Prozess beendet sich jetzt zwar, aber der Host leidet darunter.

Verwende für gespeicherte Diagnosedaten einen Ringpuffer. Nach Erreichen seiner Kapazität behält ein Ringpuffer die neuesten Bytes, und genau dort steht oft die nützliche Fehlermeldung. Bewahre zusätzlich einen kurzen Anfang auf, wenn die erste Zeile in deiner Umgebung wichtig ist, etwa der Befehlsaufruf oder die Bibliotheksversion. Bewahre nicht beide Teile unbegrenzt auf.

Der folgende Pseudocode beschreibt das Verhalten, das sich zu implementieren lohnt. Er hängt nicht von einer bestimmten Sprache ab.

```text
on_stderr_chunk(bytes):
  stderr_seen += length(bytes)
  if stderr_seen <= capture_limit:
    append_tail(bytes)
  else:
    append_tail(bytes)       # ring buffer evicts older bytes
    stderr_truncated = true
  continue_reading()
```

Der Kommentar verdient Aufmerksamkeit. `capture_limit` sollte die Menge bedeuten, die du behältst, nicht den Punkt, an dem du aufhörst zu lesen. Eine strikte Implementierung kann `append_tail` nach Erreichen des Limits überspringen und stattdessen die ersten Bytes behalten. Ich bevorzuge das Ende, weil Fehlermeldungen oft nach Seiten voller Fortschrittsausgaben erscheinen. Wie du dich auch entscheidest, benenne es im Datensatz, damit spätere Untersuchungen erkennen lassen, ob sie den Anfang oder das Ende lesen.

Setze zusätzlich eine zweite Begrenzung um das Objekt, das Diagnosedaten in den Agentenkontext überträgt. Ein Agent braucht kein mehrere Megabyte großes Protokoll, um zu entscheiden, ob er eine Aktion wiederholen soll. Er braucht einen knappen Fehler, den Exit-Status und höchstens einen ausgewählten Ausschnitt vom Ende. Wenn die Bridge rohe Hilfsprogrammdaten weitergibt, weil „das Modell sie vielleicht brauchen könnte“, erhält jedes Hilfsprogramm einen Weg, den übrigen Aufgabenkontext zu verdrängen.

Dekodiere und kodiere einen großen Datenstrom nicht neu, nur um eine Textbegrenzung durchzusetzen. Zähle die Rohbytes, bevor du Strings erzeugst. Dekodiere den behaltenen Ausschnitt mit einer Ersetzungsstrategie für ungültige Sequenzen und kennzeichne ihn als erfasstes stderr. So vermeidest du sowohl unnötigen Speicherverbrauch als auch eine trügerische Sicherheit, wenn ein Hilfsprogramm versehentlich Binärdaten ausgibt.

## Eine abgeschlossene Aktion kann beim Herunterfahren trotzdem scheitern

Beim Herunterfahren von Prozessen verwandeln sich Log-Fluten in irreführende Incident-Berichte. Die Aktion kann remote erfolgreich gewesen sein, das Hilfsprogramm kann seine letzte Diagnose ausgegeben haben, und der übergeordnete Prozess kann trotzdem kein Ergebnis erzeugen, weil er in der falschen Reihenfolge auf das falsche Ereignis wartet.

Eine bekannte Abfolge sieht so aus:

1. Die Bridge startet ein Hilfsprogramm und beginnt, stdout zu lesen, aber das Lesen von stderr kommt während einer Welle nicht hinterher.
2. Das Hilfsprogramm schließt seine externe Aktion ab und schreibt danach genug Diagnosedaten, um die stderr-Pipe zu füllen.
3. Der übergeordnete Prozess bricht die Sitzung ab oder erreicht eine Frist und sendet ein Beendigungssignal.
4. Der übergeordnete Prozess wartet auf das Kind, bevor er die Leser der Datenströme schließt oder weiter ausliest.
5. Ein Leser wartet auf das Dateiende, während eine andere Aufgabe auf den Leser wartet, und die Sitzung erreicht nie ihren abschließenden Datensatz.

Die externe Wirkung kann bereits eingetreten sein. Eine HTTP-Anfrage kann angenommen worden sein oder ein SSH-Befehl kann eine entfernte Datei geändert haben. Diesen Aufruf einfach als „Timeout“ zu melden, lässt den Betreiber mit der denkbar schlechtesten Antwort zurück: Er weiß nicht, ob ein erneuter Versuch dieselbe Änderung wiederholen wird.

Gib jedem Hilfsprogramm einen Besitzer, der vier Dinge gemeinsam verwaltet: das Kind-Handle, den stdout-Leser, den stderr-Leser und den Abbruch. Warte bei normalem Abschluss auf das Ende des Prozesses und leere die Leser bis zum Dateiende, bevor du das endgültige Ergebnis erstellst. Fordere beim Abbruch die Beendigung an, lies beide Datenströme weiter, warte eine begrenzte Kulanzfrist und erzwinge danach das Ende, wenn die Plattform das erlaubt. Warte schließlich, bis die Leser fertig sind, und erfasse den tatsächlich beobachteten Exit-Status.

Mache die Stream-Leser nicht allein zu Kindern des Request-Handlers. Ein Handler kann verschwinden, wenn ein Client die Verbindung trennt. Das Hilfsprogramm und seine Leser brauchen einen Besitzer, der lange genug bestehen bleibt, um die Bereinigung abzuschließen und den Endstatus zu schreiben. Sonst kann ein Agentenprozess beendet werden, der Host die letzten Referenzen auf seine Leser verwerfen und ein Kind mit offenen Pipes zurückbleiben, die niemand mehr liest.

Für Prozessgruppen gilt dieselbe Sorgfalt. Ein Shell-Wrapper kann Nachkommen starten, die stderr erben. Wird nur der Wrapper beendet, kann ein Nachkomme die Pipe offen halten. Vermeide eine Shell, wenn du kannst. Wenn du eine brauchst, starte sie in einer abgeschirmten Prozessgruppe und lege genau fest, welche Nachkommen der Abbruch erreicht. Teste anschließend den Fall, in dem der Wrapper beendet wird, aber ein Enkelprozess weiter schreibt.

## Die Genehmigungszeit darf die Log-Menge nicht berücksichtigen

Die Genehmigungszeit sollte dem Aktionsstatus folgen, nicht dem Tempo der Diagnosedaten eines Hilfsprogramms. Wenn deine Oberfläche eine Genehmigung erst anzeigt, nachdem ein Hilfsprogramm ein Vorabprotokoll erzeugt hat, verändert ein lauter Vorab-Schritt den Zeitpunkt, zu dem der Benutzer entscheiden soll. Die Genehmigung wirkt dadurch zufällig, und Menschen lernen, Karten zu bestätigen, ohne zu verstehen, warum eine länger gebraucht hat.

Definiere die Übergänge, bevor du den UI-Code schreibst. Ein Aufruf kann empfangen, validiert, wartend auf Genehmigung, autorisiert, gestartet, abgeschlossen, abgebrochen oder vor dem Start fehlgeschlagen sein. Stderr kann zu einem Aufruf gehören, darf aber nicht über den Übergang entscheiden. Die Bridge sollte das angeforderte Ziel und die Parameter prüfen, den Aufrufdatensatz anlegen und jede erforderliche Genehmigung anzeigen, bevor sie eine genehmigungspflichtige Aktion startet.

Es gibt eine sinnvolle Ausnahme. Ein Hilfsprogramm kann benötigt werden, um festzustellen, welche Aktion ausgeführt werden soll, etwa indem es einen lokalen Konfigurationsnamen in einen konkreten Endpunkt auflöst. Behandle diese Ermittlung als eigene, nicht ausführende Operation mit eigenem Ausgabebudget. Verstecke keine Aktion in einem „Vorab-Schritt“, um später zu behaupten, der Benutzer habe sie genehmigt.

Gib der Genehmigung eine Frist nach der Wanduhr, die sich nicht zurücksetzt, weil neue stderr-Daten eintreffen. Sammle den begrenzten Ausschnitt weiter, solange die Karte sichtbar ist, aber aktualisiere die Karte niemals für jede einzelne Zeile. Eine Warnung über einen Wiederholungsversuch oder eine Fehlerzusammenfassung kann vor der Genehmigung hilfreicher Kontext sein. Sie sollte jedoch als stabile Erklärung erscheinen, nicht als endlose bewegte Log-Ansicht.

Trenne Einwilligung und Betriebsfähigkeit in deinen Tests. Ein Fluttest sollte die Zeit von einer gültigen Aufrufanforderung bis zur Genehmigungskarte messen, wobei stderr sowohl davor als auch danach erzeugt wird. Ein Test mit langsamem Leser sollte prüfen, dass die Karte nutzbar bleibt, während die Bridge weiterliest. Ein Abbruchtest sollte sicherstellen, dass das Schließen der Karte keinen laufenden Hilfsprozess hinterlässt und einen abschließenden Datensatz erzeugt.

Die menschliche Konsequenz ist klar: Eine Genehmigungskarte muss eine bestimmte Aktion zu einem bestimmten Zeitpunkt beschreiben. Wenn die Log-Ausgabe diesen Zeitpunkt verzögern, verändern oder überdauern kann, berichtet die Oberfläche die Verwirrung des Prozesses, statt dem Benutzer Kontrolle zu geben.

## Aufrufdatensätze brauchen zuerst Lebenszyklusfakten und erst danach Diagnosedaten

Ein Aufrufdatensatz ist vollständig, wenn er den Status der Aktion erklärt, nicht wenn er jede vom Hilfsprogramm ausgegebene Zeile enthält. Logs sind Nachweise. Lebenszyklusereignisse sind der Datensatz.

Schreibe den Versuch vor dem Start. Füge eine stabile Aufruf-ID, die Sitzungs-ID, den angeforderten Aktionstyp, die genehmigte oder abgelehnte Entscheidung und die Zielinformationen ein, die dem Benutzer gezeigt wurden. Wenn der Start beginnt, ergänze diese Tatsache. Wenn er endet, ergänze das beobachtete Ergebnis: Erfolg, entfernter Fehler, lokaler Fehler, Abbruch, erzwungenes Beenden oder ein unbekanntes Ergebnis, weil die Prozessgrenze verloren ging.

Füge danach Metadaten zu den Diagnosedaten an. Erfasse mindestens die Gesamtzahl der gesehenen stderr-Bytes, die behaltenen Bytes, ob eine Kürzung stattgefunden hat, den Exit-Status des Hilfsprogramms, sofern verfügbar, und ob der Datenstrom sauber beendet wurde. Damit bleibt ein kurzer Ausschnitt ehrlich. Spätere Leser können zwischen „Der Befehl hat das ausgegeben“ und „Die Bridge hat den letzten Teil der Ausgabe des Befehls behalten“ unterscheiden.

Ein Datensatz kann so klein sein:

```json
{
  "call_id": "c_7f2a",
  "state": "cancelled_after_dispatch",
  "stderr_bytes_seen": 184320,
  "stderr_bytes_retained": 16384,
  "stderr_truncated": true,
  "exit_status": null,
  "stream_end": "reader_completed_after_cancel"
}
```

Schreibe nicht `exit_status: 0`, nur weil ein übergeordneter Prozess einen erfolgreichen Antworttext erhalten hat. Ein Antworttext und das Ende eines Kindprozesses sind zwei verschiedene Beobachtungen. Schreibe nicht `state: failed`, wenn der Abbruch nach dem Start erfolgte und die entfernte Seite möglicherweise gehandelt hat. Diese Unterscheidung wirkt bei der Implementierung pedantisch und kann zwei Uhr morgens den Unterschied zwischen einer sicheren Untersuchung und einem blinden Wiederholungsversuch ausmachen.

Der NIST-Leitfaden SP 800-92 zur Verwaltung von Computersicherheitsprotokollen weist auf einen wichtigen Punkt hin: Log-Management umfasst Erzeugung, Übertragung, Speicherung, Analyse und Entsorgung, nicht bloß das Sammeln von Text. Übertrage diese Überlegung auf Agentenaktionen. Wenn das Sammeln den Abschluss verhindern kann, ist der Logging-Pfad Teil der Ausführung geworden. Er braucht wie jeder andere Ausführungspfad Begrenzungen, Statusinformationen und Fehlerbehandlung.

Sallyport erstellt seine Journale für Sessions und Aktivitäten aus einem schreibblinden, verschlüsselten und hash-verketteten Audit-Log. So kann ein Aufrufer den Agentenlauf von den einzelnen Aktionsdatensätzen unterscheiden, statt ein Hilfsprotokoll als Historie zu verwenden.

## Begrenze den Lärm an drei Stellen

Eine einzige Begrenzung am oberen Ende des Stacks lässt zu viel Raum für Unfälle. Setze Limits beim Hilfsprogramm, bei der Bridge und am Ziel der Diagnosedaten. Jede Begrenzung schützt eine andere Grenze.

Mache Hilfsprogramme zuerst standardmäßig weniger redselig. Lege routinemäßige Fortschrittsmeldungen hinter eine ausdrückliche Debug-Einstellung, gib für eine Folge von Wiederholungsversuchen eine Zusammenfassung aus und vermeide es, Anfrage- oder Antworttexte standardmäßig zu drucken. Ein Hilfsprogramm darf niemals Zugangsdaten, Authorization-Header oder Material privater Schlüssel auf stderr schreiben. Eine nachträgliche Schwärzung ist ein nützlicher Schutz, kann aber ein Geheimnis nicht zurückholen, das bereits in einem Terminal, einem Absturzbericht oder einem unbegrenzten Puffer lag.

Lass die Bridge zweitens kontinuierlich lesen und einen begrenzten Anfang oder Ausschnitt vom Ende behalten. Sie sollte für jeden Datenstrom sowohl eine maximale Dauer als auch eine maximale Zahl gespeicherter Bytes durchsetzen. Außerdem sollte sie ein prozessweites Diagnosebudget haben. Ohne diese letzte Kontrolle können fünfzig Aufrufe, die jeweils innerhalb ihres Einzelbudgets bleiben, gemeinsam trotzdem eine Speicherspitze erzeugen.

Begrenze drittens das Ziel. Wenn du Diagnosedaten eines Aufrufs an eine Benutzeroberfläche, eine Agentenantwort oder eine Datei exportierst, setze auch dort eine weitere Obergrenze. Ein Journal kann strukturierte Lebenszyklusfelder und einen Digest der verworfenen Ausgabe speichern, ohne bei jedem Wiederholungsversuch denselben Stacktrace erneut abzulegen.

Verwende eine ausdrücklich definierte Konfigurationsstruktur. Die Namen sind unwichtig. Die Trennung ist entscheidend.

```yaml
helper_output:
  stderr_retained_per_call_bytes: 16384
  stderr_retained_process_bytes: 262144
  stderr_agent_excerpt_bytes: 4096
  shutdown_grace_seconds: 5
  retain: tail
```

Diese Konfiguration verhindert einen häufigen Fehler: Ein Team setzt ein Limit für die Agentenantwort und nimmt an, der Host sei geschützt. Der Host liest und speichert die vollständigen Daten trotzdem, bevor er die Antwort kürzt. Das Limit pro Aufruf schützt eine einzelne Aktion. Das Prozesslimit schützt parallele Aktionen. Das Limit für den Agentenausschnitt schützt den Kontext, den ein Modell mit dem übrigen Arbeitskontext teilen muss.

Verwende keinen einzelnen globalen Schalter namens `quiet`. Er erschwert die Diagnose von Produktionsfehlern und verleitet Entwickler dazu, bei Bedarf wieder unbegrenzte Logs zu aktivieren. Halte die normale Ausgabe knapp, biete einen kontrollierten Debug-Modus für ein kurzes Zeitfenster an und bewahre im Aufrufdatensatz die Information auf, dass der Debug-Modus aktiv war.

Vermeide die verbreitete Empfehlung, stderr nach `/dev/null` umzuleiten. Sie wird empfohlen, weil sie den unmittelbaren Stillstand beseitigt und Agentenantworten übersichtlich hält. Gleichzeitig löscht sie den ersten brauchbaren Hinweis, wenn ein SSH-Hilfsprogramm sich nicht authentifizieren kann, eine Zertifikatsprüfung fehlschlägt oder ein entfernter Befehl einen unerwarteten Fehler zurückgibt. Lies den Datenstrom stattdessen weiter, begrenze ihn und kennzeichne ihn.

## Kürzungen sollten sichtbar sein, nicht dramatisch

Eine Kürzung ist sicher, wenn sie ausdrücklich gekennzeichnet ist und das System weiterliest. Sie ist unsicher, wenn ein späterer Leser nicht erkennen kann, ob eine Fehlermeldung vollständig ist, wenn die Bridge das Lesen einstellt und das Kind blockiert oder wenn die verworfene Ausgabe den einzigen Nachweis einer Aktion enthalten könnte, für die es keinen anderen Datensatz gibt.

Der gespeicherte Ausschnitt vom Ende sollte mit einer von der Bridge erzeugten Markierung beginnen, nicht mit einer des Hilfsprogramms. Zum Beispiel:

```text
[stderr truncated: kept last 16384 of 184320 bytes]
connection retry 18 failed: remote side closed the channel
```

Diese Markierung gehört zum Datensatz und ist keine Dekoration. Sie erklärt dem Benutzer, warum die erste sichtbare Zeile abrupt beginnt, und verhindert, dass ein Agent einen unvollständigen Stacktrace als vollständige Erklärung behandelt. Wenn du Anfang und Ende behältst, nenne beide Byte-Zahlen. Füge sie niemals stillschweigend zusammen.

Lege die Limits ausgehend von der Parallelität fest, statt eine Zahl aus einem anderen Projekt zu übernehmen. Frage, wie viele Aktionen ein Agentenlauf gleichzeitig ausführen kann, wie viele Läufe der Host akzeptiert, wie viel Arbeitsspeicher Diagnosedaten während einer schlechten Minute verbrauchen dürfen und wie viel Text eine Person sinnvoll prüfen kann. Die letzte Menge ist gewöhnlich deutlich kleiner als erwartet.

Halte das Aktionsergebnis vom Ausschnitt getrennt. Eine erfolgreiche HTTP-Aktion sollte ihr vorgesehenes strukturiertes Ergebnis zurückgeben, selbst wenn stderr das Limit erreicht hat, außer das Hilfsprogramm wertet den Überlauf selbst als Fehler. Umgekehrt beweist ein sauberer stderr-Datenstrom nicht, dass eine Aktion erfolgreich war. Behandle Diagnosedaten als eines von mehreren Beobachtungsfeldern.

## Fluttests gehören neben normale Integrationstests

Ein Hilfsprogramm, das zu viel stderr schreibt, ist kein exotischer Sicherheitstest. Es ist ein grundlegender Zuverlässigkeitstest. Bibliotheken werden nach einem Versionswechsel ausführlicher, entfernte Clients wiederholen Warnungen während erneuter Versuche und fehlerhafte Eingaben können eine Fehlerschleife auslösen. Wenn die Bridge in Tests nur ruhige Hilfsprogramme auf dem Erfolgsweg sieht, gibt es keinen Nachweis dafür, dass ihre Prozessverwaltung unter Druck funktioniert.

Beginne mit einem Fixture, das eine festgelegte Menge stderr ausgibt, mit einem gewählten Status beendet wird und keine externe Arbeit ausführt. Auf einem Unix-ähnlichen System erzeugt dieser Befehl eine absichtliche Flut für ein lokales Test-Harness:

```sh
yes helper-diagnostic 1>&2
```

Führe es mit einer kurzen Frist aus. Das erwartete Ergebnis ist nicht bloß, dass die Frist abläuft. Prüfe, dass die Bridge höchstens die konfigurierte Menge behält, die Kürzung markiert, das Kind beendet, die Datenströme bis zu ihrem Ende leert und ein abschließendes Lebenszyklusereignis schreibt.

Füge dann die Fälle hinzu, die Fehler in der Reihenfolge sichtbar machen:

- Ein Hilfsprogramm schreibt stderr, bevor es auf eine Genehmigung wartet.
- Ein Hilfsprogramm schreibt während der Genehmigung und beendet sich unmittelbar danach.
- Ein Hilfsprogramm führt eine externe Aktion erfolgreich aus, flutet stderr und wird während der Bereinigung abgebrochen.
- Ein Wrapper beendet sich, während ein Nachkomme stderr offen hält.
- Ein Hilfsprogramm schreibt ungültige Bytefolgen und eine einzelne Zeile, die größer als das Speicherlimit ist.

Miss für jeden Fall eine kleine Gruppe von Fakten: maximal gespeicherte Diagnose-Bytes, Zeit bis zur Genehmigung, Zeit vom Abbruch bis zum Ende des Prozesses, den Endstatus und das Vorhandensein des erwarteten Aufrufdatensatzes. Begnüge dich nicht mit einem Test, der nur prüft, ob eine Fehlermeldung „truncated“ enthält. Diese Zeichenfolge kann erscheinen, während eine Leseraufgabe weiterhin blockiert oder der abschließende Datensatz nie gespeichert wird.

Führe dieselben Fixtures mit parallelen Aufrufen aus. Eine Begrenzung pro Aufruf kann isoliert korrekt aussehen und trotzdem scheitern, wenn alle Aufrufe ihr Limit gleichzeitig erreichen. Teste auch die Trennung eines Clients. Der Agentenprozess darf verschwinden. Der Host muss die Aktion trotzdem auf definierte Weise abschließen oder abbrechen.

Die letzte Prüfung ist die Offline-Verifikation der Audit-Kette und ein Vergleich mit dem erwarteten Lebenszyklus des Fixtures. Die Verifikation kann zeigen, ob gespeicherte Einträge verändert wurden. Der Fixture-Vergleich zeigt, ob die Bridge die Einträge überhaupt geschrieben hat, die sie hätte schreiben sollen. Du brauchst beides.

## Halte Logs nützlich, ohne sie die Aktion steuern zu lassen

Die nützliche Regel ist einfach: stdout überträgt MCP-Protokollnachrichten, strukturierte Ergebnisse tragen Aktionsresultate und stderr enthält begrenzte Diagnosedaten. Sobald diese Kanäle unterschiedliche Aufgaben haben, hören Genehmigungszeit, Herunterfahren, Speicherabrechnung und Audit-Datensätze auf, um denselben unstrukturierten Text zu konkurrieren.

Beginne mit dem Hilfsprogramm, das dich schon einmal in Verlegenheit gebracht hat: dem, das zu laut wiederholt, nach einem Abbruch hängt oder bei einem Fehler die gesamte entfernte Antwort ausgibt. Setze es hinter einen begrenzten Leser und führe das Flut-Fixture aus, bis der Aktionsdatensatz wahrheitsgemäß bleibt. Leise Logs sind angenehm. Begrenzte Logs sind Betriebskontrolle.
