8 Min. Lesezeit

Verwaiste Agenten-Subprozesse können Zugangsdaten aktiv halten

Verwaiste Agenten-Subprozesse können Zugangsdaten und Verbindungen nach einem Timeout aktiv halten. Erfahre mehr über die Bereinigung von Prozessgruppen, den Widerruf des Laufstatus und Audit-Methoden.

Verwaiste Agenten-Subprozesse können Zugangsdaten aktiv halten

Ein KI-Agent, dessen Zeitlimit abgelaufen ist, wurde nicht unbedingt beendet. Wenn sein Shell-Wrapper, Compiler, HTTP-Helfer oder SSH-Befehl vor Ablauf des Timeouts einen Kindprozess erzeugt hat, kann dieser nach dem Verschwinden der Eltern-PID weiterlaufen. Hat er eine verwendbare Zugangsdaten oder eine bereits authentifizierte Verbindung geerbt, wurde die Berechtigung, deren Widerruf du angenommen hast, durch das Timeout nicht beendet.

Ich habe Incident-Notizen gesehen, die mit «Der Agent wurde um 14:03 beendet» beginnen und mit einem API-Aufruf um 14:11 enden. Meist hat niemand einen raffinierten Exploit gefunden. Der Runner beendete einen Prozess, während die eigentliche Arbeit bereits in einen anderen verschoben worden war. Prozessbereinigung klingt nach bloßer Infrastruktur, bis ein autonomer Agent Produktionszugangsdaten verwenden kann. Dann gehört auch diese Infrastruktur zur Sicherheitsgrenze.

Der Tod des Elternprozesses beendet die Arbeit nicht

Ein Kindprozess kann seinen Elternprozess überleben, weil der Kernel Prozesse unabhängig voneinander verwaltet und nicht als wegwerfbare Erweiterungen eines Shell-Befehls. Wenn ein Elternprozess endet, weist das Betriebssystem seine Kinder einem System-Reaper oder Supervisor zu. Das Kind behält seine eigene PID, seinen Speicher, seine Dateideskriptoren, Sockets, sein aktuelles Verzeichnis und oft auch seine Umgebung.

Dieses Verhalten ist legitim. Build-Systeme nutzen es. Terminal-Multiplexer nutzen es. Dienstmanager verlassen sich darauf. Der Fehler besteht darin, die PID des Elternprozesses als Ausführungsgrenze eines Agentenlaufs zu behandeln.

Betrachte eine vertraute Kette:

agent-runner (PID 4102)
  shell tool wrapper (PID 4131)
    deployment script (PID 4140)
      ssh helper (PID 4144)

Der Runner erreicht seine Frist und sendet ein Signal an PID 4102. Wenn die Shell oder das Deployment-Skript nicht mit ihm beendet wurde, kann die übrige Kette weiterlaufen. Schwieriger wird es, wenn das Skript mit & einen Hintergrundjob startet, nohup verwendet, einen Dienstmanager aufruft oder einen entfernten Rechner anweist, Arbeit zu beginnen. Der Elternprozess kann sauber enden, während das Kind sich bereits unabhängig gemacht hat.

Deshalb ist ein verwaister Agenten-Subprozess mehr als ein übrig gebliebener CPU-Verbraucher. Er führt eine Entscheidung unbemerkt fort, von der ein Mensch oder Scheduler glaubte, sie sei beendet. Der Schaden hängt davon ab, worauf der Prozess noch zugreifen kann. Der Fehler beginnt jedoch früher: Der Runner hat die falsche Einheit zum Beenden ausgewählt.

POSIX beschreibt die Grundlagen dieses Verhaltens in seinen Schnittstellen für Prozesse und Jobsteuerung. Eine Prozessgruppe ist eine Gruppe verwandter Prozesse mit einer gemeinsamen Prozessgruppen-ID. Signale können an die Gruppe statt an ein einzelnes Mitglied gesendet werden. Eine Sitzung umfasst eine oder mehrere Prozessgruppen und steht normalerweise in einer Beziehung zu einem steuernden Terminal. Das sind unterschiedliche Kernel-Konzepte. Wer die Begriffe gleichsetzt, schreibt schwache Bereinigungslogik.

Ein Timeout muss eine Isolierungseinheit beenden

Ein Timeout ist nur dann sicher, wenn es auf eine Isolierungseinheit zielt, die vor dem Start des Agenten eingerichtet wurde. Für einen lokalen Befehlsbaum ist das normalerweise eine eigene Prozessgruppe. Der Runner speichert die Gruppen-ID sofort und sendet bei Ablauf der Frist Signale an diese Gruppe.

Die Reihenfolge ist wichtig. Warte mit der Suche nach den Kindern nicht bis zur Bereinigung. Bis dahin kann der ursprüngliche Elternprozess verschwunden sein, die PPID-Beziehung bereits irreführend sein und ein weiterer Lauf begonnen haben.

Ein praktischer Timeout-Ablauf umfasst fünf Schritte:

  1. Erstelle vor dem Start des Agenten eine eigene Gruppe oder einen von einem Supervisor verwalteten Job.
  2. Speichere Lauf-ID, PID, PGID, Startzeit, Befehl und Frist in einem gemeinsamen Datensatz.
  3. Markiere den Lauf bei einem Timeout als abgelaufen, bevor du ein Signal sendest, damit neue Aktionen nicht mit genehmigter Arbeit verwechselt werden.
  4. Sende TERM an die gespeicherte Isolierungseinheit, warte eine kurze festgelegte Kulanzzeit und sende anschließend KILL nur an die verbleibenden Prozesse.
  5. Sichere das Ergebnis und speichere die Signalzeitpunkte, den Exit-Status und die noch laufenden PIDs.

Der Datensatz kommt zuerst, weil die Bereinigung mit einem schnellen Prozessende konkurrieren kann. Wenn dein Protokoll nur «Agent beendet» sagt, kann es nicht beantworten, welchen Prozess du beendet hast, welche Kinder zu seiner Gruppe gehörten oder ob ein Kind vor dem Signal entkommen ist.

Prüfe auf macOS die Kennungen, statt einer Darstellung des Prozessbaums blind zu vertrauen. Dieser Befehl zeigt die Felder, die die meisten Timeout-Fehler erklären:

ps -axo pid,ppid,pgid,sid,lstart,etime,command

Ein sauber abgelaufener Lauf könnte vor der Bereinigung ungefähr so aussehen:

  PID  PPID  PGID   SID  STARTED                  ELAPSED COMMAND
 4102  3988  4102  4102  Thu Jul 24 14:00:02 2026   00:31 agent-runner ...
 4131  4102  4102  4102  Thu Jul 24 14:00:02 2026   00:31 /bin/sh -c ...
 4144  4131  4102  4102  Thu Jul 24 14:00:04 2026   00:29 ssh ...

Die Werte dienen nur zur Veranschaulichung und dürfen nicht fest einprogrammiert werden. Entscheidend ist, dass der Lauf eine bekannte PGID hat, hier 4102. Die Bereinigung sendet das Signal an diese Gruppe und nicht nur an PID 4102. Ein negatives Ziel weist kill an, ein Signal an eine Prozessgruppe zu senden:

kill -TERM -4102

Prüfe die Syntax für die von dir verwendete Laufzeitumgebung und das Betriebssystem. Manche Wrapper verarbeiten negative Zahlen fehlerhaft oder behandeln ein zusätzliches Argument als Befehlsoption. Baue keine Sicherheitsgrenze auf einer Befehlszeile auf, die du nicht unter einem echten Timeout getestet hast.

Ein Gruppensignal hat trotzdem Grenzen. Ein Kind kann setsid aufrufen, eine neue Prozessgruppe erstellen, Arbeit an einen anderen Dienst übergeben oder einen Prozess auf einem entfernten Rechner starten. Die Disziplin der Prozessgruppe schließt den gewöhnlichen lokalen Ausweg, aber nicht jede mögliche Flucht. Dein Runner muss solche Übergänge als ausdrückliche Übergaben mit eigener Abbruch- und Audit-Logik behandeln.

Prozessgruppen, Sitzungen und Zugangsdaten lösen unterschiedliche Probleme

Eine Prozessgruppe gibt dir ein lokales Signalziel. Eine Sitzung bietet eine umfassendere Grenze und kann die Jobsteuerung des Terminals isolieren. Keine von beiden widerruft Zugangsdaten, die ein Prozess in den Speicher kopiert oder auf die Festplatte geschrieben hat. Das sind getrennte Kontrollen, für die du getrennte Nachweise brauchst.

Diese Unterscheidung verschwimmt, weil der Fehler wie ein einziges Ereignis aussieht: Ein Agent überschreitet sein Zeitlimit und ein späterer Aufruf ist erfolgreich. Die Bereinigung fragt: «Welche lokalen Prozesse sollten beendet werden?» Der Umgang mit Zugangsdaten fragt: «Welcher Prozess kann diese externe Aktion noch autorisieren?» Das Audit fragt: «Können wir nachweisen, welche Aktion nach dem Ende der Freigabe stattgefunden hat?» Eine Prozessgruppe beantwortet nur die erste Frage.

Umgebungsvariablen sind die klassische schlechte Übergabe. Wenn ein Runner API_TOKEN exportiert, erhält jeder Nachkomme das Token, sofern ein späterer Prozess die Umgebung nicht bereinigt. Ein Kind kann das Token kopieren, an ein anderes Programm weitergeben oder eine authentifizierte Verbindung offen halten. Das Ersetzen einer Umgebungsvariable, nachdem der Elternprozess gestartet wurde, zieht die bereits geerbten Bytes nicht zurück.

Dateien und Sockets sind ebenso wichtig. Ein Prozess kann einen offenen Dateideskriptor erben, der auf eine Zugangsdaten-Datei, einen verbundenen HTTPS-Socket mit Sitzungsstatus oder einen SSH-Agent-Socket zeigt. Ein Hintergrundprozess kann den Deskriptor weiter verwenden, nachdem sein Elternprozess beendet wurde. Close-on-exec verhindert die versehentliche Vererbung bei einem neuen exec, hilft aber nicht, wenn das Kind den Deskriptor bereits besitzt oder das Programm ihn absichtlich weitergibt.

SSH erfordert besondere Vorsicht. Ein in einen lokalen Helfer geladener privater Schlüssel, ein SSH-Agent-Socket, eine multiplexte Steuerverbindung und ein entfernter Befehl haben vier unterschiedliche Lebenszyklen. Das Beenden des lokalen Elternprozesses kann eine bestehende Verbindung oder einen entfernten Befehl weiterlaufen lassen. Wenn der Agent beliebige SSH-Aufrufe ausführen kann, muss der Runner wissen, ob er den Aufruf eines lokalen Helfers, einen authentifizierten Kanal oder einen entfernten Job freigegeben hat. Alle drei einfach «SSH-Befehl» zu nennen, verschleiert die Grenze, die du kontrollieren musst.

Das sicherere Muster übergibt eine Aktionsanfrage statt des rohen Geheimnisses. Der Prozess bittet eine Autoritätskomponente, eine bestimmte HTTP-Anfrage oder SSH-Aktion auszuführen. Diese Komponente hält das Geheimnis, entscheidet, ob der aktuelle Lauf es verwenden darf, führt die Aktion aus und gibt das Ergebnis zurück. Ein entkommenes Kind kann Anfragen trotzdem wiederholen, solange sein Lauf autorisiert bleibt. Deshalb muss die Autoritätskomponente auch Ablauf oder Widerruf verstehen.

Sallyport folgt dieser Trennung für seine unterstützten HTTP- und SSH-Kanäle: Der Agent fragt über seinen MCP-Shim an, während die App das API- oder SSH-Material aufbewahrt und die Aktion ausführt, statt das Geheimnis in den Agentenprozess zu geben. Das ersetzt die Prozessbereinigung nicht, beseitigt aber die einfachste Form der Zugangsdatenvererbung.

Prozesse im Hintergrund sind ein Ausweg, kein harmloses Shell-Detail

Der häufigste Fehler bei der Bereinigung beginnt mit einer Shell-Bequemlichkeit. Jemand schreibt command &, startet eine Pipe, verwendet nohup oder startet eine Laufzeitumgebung, die Worker erzeugt. Der Elternbefehl sieht beendet aus oder läuft in ein Timeout, während der Worker weitermacht.

Eine Shell-Pipeline verdient besondere Aufmerksamkeit. Ein Runner kann /bin/sh -c 'generator | uploader' ausführen und die Shell-PID speichern. Die Shell hat für beide Seiten der Pipeline eigene Kinder. Je nachdem, wie der Runner die Gruppe erstellt, kann das Beenden nur der Shell den Generator oder Uploader weiterlaufen lassen. Wenn der Uploader Zugangsdaten und eine Netzwerkverbindung besitzt, ist das genau der Prozess, den du beenden musst.

nohup ist schlimmer, als viele zugeben. Es verhindert, dass ein Prozess ein Hangup-Signal empfängt. Es macht einen Prozess weder vertrauenswürdig noch meldet es ihn deinem Runner. In der Automatisierung bedeutet es normalerweise, dass jemand möchte, dass Arbeit ein Terminal- oder Elternprozessende überlebt. Das kann für einen verwalteten Dienst sinnvoll sein, muss dann aber in einen Supervisor verschoben werden, der den Job besitzt und seinen Lebenszyklus erfasst. Es sollte nicht versehentlich innerhalb eines Agenten-Tool-Aufrufs sitzen.

Abgekoppelte Kinder erzeugen eine eigene Problemklasse. In Node.js gibt spawn mit detached: true dem Kind auf Unix-ähnlichen Systemen absichtlich eine neue Prozessgruppe und Sitzung. Das kann für eine Desktop-Anwendung sinnvoll sein, die ihren Starter überleben muss. In einem Agenten-Executor ist es eine schlechte Voreinstellung, weil es das normale Beenden der Gruppe durch den Executor verhindert.

import { spawn } from "node:child_process";

const child = spawn("/bin/sh", ["-c", "sleep 600"], {
  detached: true,
  stdio: "ignore"
});
child.unref();

Dieses Snippet dient als Testfall für die Bereinigung und ist kein Ausführungsmuster, das du in einen Agenten-Runner übernehmen solltest. Es erzeugt ein Kind, das die normale Prozessgruppe des Elternprozesses nicht mehr teilt. Wenn ein Tool-Framework solchen Code ausführen kann, reicht die Bereinigung der Prozessgruppe allein nicht für eine vollständige Garantie. Beschränke abgekoppelte Ausführung, fange sie an der Tool-Grenze ab oder führe das Tool in einer stärkeren Betriebssystem-Isolierung aus.

Auch die beliebte Empfehlung, «einfach den Prozessbaum zu beenden», scheitert, wenn damit ein rekursiver PPID-Durchlauf gemeint ist. PPIDs beschreiben einen Zeitpunkt, keine dauerhafte Mitgliedschaftsgrenze. Eine Kindprozess kann zwischen deinem Durchlauf und dem Signal einen Fork ausführen. Ein Nachkomme kann den Baum verlassen. Speichere beim Start eine Gruppen- oder Jobidentität und mache jeden Austritt aus dieser Identität zu einem bewussten, auditierbaren Vorgang.

Bereinige nicht nach Befehlsnamen

Die Spur offline prüfen
Prüfe Sallyports verschlüsselte, hashverkettete Audit-Spur offline, ohne einen Tresorschlüssel zu benötigen.

pkill und eine breite Namenssuche wirken attraktiv, weil sie kurz sind. Für parallele Agentenläufe passen sie jedoch schlecht. pkill ssh kann eine unabhängige interaktive Verbindung beenden. pkill python kann das lokale Tool eines Entwicklers entfernen. Eine Suche nach einer Argumentzeichenfolge kann einen Prozess übersehen, der seine Argumente geändert hat, oder einen falschen Treffer in einem anderen Lauf liefern.

Verwende einen Befehlsnamen nur als Hinweis für die Untersuchung. Für die Durchsetzung brauchst du eine Besitzgrenze.

Ein Runner, der eine Prozessgruppe kennt, kann zunächst ihre Mitglieder prüfen. Filtere auf macOS die Prozesstabelle mit der gespeicherten Nummer und nicht mit einem geratenen ausführbaren Namen:

ps -axo pid,ppid,pgid,sid,etime,command | awk '$3 == 4102 || NR == 1'

Die erste Spalte ist bei diesem Befehlsaufbau die PID, die zweite die PPID und die dritte die PGID. Vermeide in Produktionscode das Parsen menschenorientierter Textausgaben, wenn deine Sprache native Prozess-APIs aufrufen kann. Für ein Operator-Terminal liefert diese Ausgabe eine schnelle, verständliche Prüfung, bevor du ein Signal auswählst.

Sende anschließend das Signal an die Gruppe und prüfe erneut:

kill -TERM -4102
sleep 2
ps -axo pid,ppid,pgid,sid,etime,command | awk '$3 == 4102 || NR == 1'

Wenn Mitglieder bleiben, finde zuerst den Grund heraus, bevor du reflexartig KILL sendest. Ein Prozess, der in nicht unterbrechbarer Kernel-Arbeit blockiert ist, erfordert eine andere Untersuchung als ein Prozess, der TERM ignoriert hat. Ein Prozess mit einer anderen PGID hat dein Signal nicht zufällig überlebt, sondern die Grenze überschritten. Das ist ein Hinweis auf eine Designentscheidung, ein Framework-Verhalten oder ein bösartiges Tool.

Nach der festgelegten Kulanzzeit ist KILL für lokale Prozesse angemessen, die weiterhin zur gespeicherten Gruppe gehören und nicht fertig werden sollen. Versprich keine schonende Bereinigung, wenn der Agent sein Berechtigungsfenster überschritten hat. Kulanz dient dazu, Dateien zu schließen und den Status zu erfassen, nicht dazu, externe Arbeit unbegrenzt fortzusetzen.

Container und Dienstmanager können eine bessere Besitzstruktur bieten als eine rohe Prozessgruppe, wenn du die Ausführungsumgebung kontrollierst. MacOS-Desktop-Workflows für Agenten erhalten nicht automatisch Linux-cgroups. Übertrage cgroup-Empfehlungen deshalb nicht unverändert. Auf einem Mac sind eine eigene Kindprozessgruppe, enge Tool-Berechtigungen und ein ausdrücklicher Vertrag für entfernte Jobs oft die Grundlage. Wenn du Ressourcengrenzen oder eine vollständige Begrenzung aller Nachkommen brauchst, verwende eine Ausführungsumgebung, die diese Kontrollen tatsächlich bereitstellt.

Das Audit muss den Elternprozess überleben

Du kannst eine entkommene Aktion nicht aus dem abschließenden Text des Agenten rekonstruieren. Der Elternprozess kann gestorben sein, bevor er Protokolle geleert hat, und ein weiterlaufendes Kind hat keinen Grund, Bericht zu erstatten. Baue das Audit auf unveränderlichen Ereignissen auf, die außerhalb des Agentenprozesses beobachtet werden.

Erfasse für jeden Lauf mindestens die folgenden Felder, bevor du externe Arbeit zulässt:

{
  "run_id": "run-7f3c",
  "started_at": "2026-07-24T14:00:02Z",
  "deadline_at": "2026-07-24T14:00:32Z",
  "parent_pid": 4102,
  "process_group": 4102,
  "approval_identity": "signed agent process identity",
  "state": "active"
}

Der Aktionsdatensatz muss Lauf-ID, eine Aktionssequenz, den Zeitpunkt der Annahme durch das Gateway, den Kanal, die Zielidentität und das Ergebnis enthalten. Protokolliere standardmäßig niemals rohe Bearer-Werte, private Schlüssel, Autorisierungs-Header oder vollständige Request-Bodies. Ein Datensatz, der die Zugangsdaten bei der Dokumentation ihrer Verwendung offenlegt, erzeugt einen zweiten Incident.

Wenn das Timeout eintritt, füge vor dem Senden irgendeines Signals einen Zustandsübergang hinzu:

{
  "run_id": "run-7f3c",
  "event": "deadline_expired",
  "observed_at": "2026-07-24T14:00:32Z",
  "process_group": 4102,
  "signal": "TERM"
}

Damit hat deine Untersuchung eine klare Frage mit einer klaren Antwort: Begann eine externe Aktion nach deadline_expired? Wenn das Gateway eine Aktion nach diesem Ereignis annimmt, hat es entweder den Laufstatus nicht durchgesetzt oder der Aufrufer trug nicht die Identität, die das Gateway erwartet hat. Begann eine Aktion vor Ablauf und endete danach, sage das ausdrücklich. Start und Abschluss sind unterschiedliche Zeitpunkte. Wer sie zusammenfasst, lässt normale laufende Arbeit wie eine Flucht aussehen.

Bewahre die Wanduhrzeit für Menschen und einen monotonen verstrichenen Wert zur Reihenfolge von Ereignissen innerhalb einer Maschine auf. Wanduhren können sich durch Synchronisierung oder manuelle Anpassung ändern. Während eines Incidents brauchst du keine Vorlesung über Zeitmessung, sondern genug Daten, um nicht zu behaupten, eine Aktion sei nach dem Tod des Elternprozesses passiert, wenn die Uhren nicht übereinstimmen.

Sallyports Sitzungs- und Aktivitätsjournale trennen die Ansicht des Laufs von einzelnen Aufrufen. Sein verschlüsseltes, hashverkettetes Protokoll kann mit sp audit verify offline geprüft werden. Das ist nach dem Verschwinden eines Elternprozesses nützlich, weil die Audit-Spur nicht davon abhängt, dass der Agent seine eigene Historie freiwillig liefert.

Eine Audit-Spur ist ein Nachweis, keine Begrenzung. Sie zeigt, dass ein Aufruf erfolgt ist, und unterstützt die spätere Prüfung. Sie beendet keinen lokalen verwaisten Prozess, holt bereits an ihn übermittelte Daten nicht zurück und bricht keinen entfernten Befehl ab, für den es keinen Abbruchweg gibt. Behandle das als unterschiedliche Verantwortlichkeiten und mache jede davon im Laufdatensatz sichtbar.

Rekonstruiere den Fehler in der richtigen Reihenfolge

Berechtigungen über den Runner hinaus verlagern
Agenten fordern HTTP- und SSH-Aktionen über den MCP-Shim an, während Sallyport sie selbst ausführt.

Wenn du einen mutmaßlichen Waisenprozess findest, sichere die Fakten, bevor du die Situation bereinigst. Ein hastiges kill -9 kann gerechtfertigt sein, um schädliche Arbeit zu stoppen, doch es kann die Prozessbeziehungen löschen, die du zur Behebung des Runners brauchst. Erstelle zuerst einen Prozess-Snapshot, sofern die Lage das zulässt.

Beginne mit dem Datensatz des abgelaufenen Laufs. Notiere seine Eltern-PID, PGID oder Jobidentität, Startzeit, Frist und jedes gesendete Signal. Erstelle dann den aktuellen Prozess-Snapshot. Vergleiche auf macOS PID, PPID, PGID, SID, verstrichene Zeit und Befehl. Ein Waisenprozess hat oft eine PPID von 1 oder einem Supervisor, aber mache das nicht zu deinem einzigen Test. Ein Prozess kann weiterhin ein aktives Risiko sein, während seine PPID noch auf etwas anderes zeigt.

Ordne anschließend ein, was entkommen ist:

  • Ein Prozess in der ursprünglichen PGID hat das erwartete Signal überlebt.
  • Ein Prozess hat eine neue PGID oder SID und sich daher lokal abgekoppelt.
  • Ein lokaler Prozess hat eine Anfrage zum Starten entfernter Arbeit gestellt, und der entfernte Job lief weiter.
  • Eine Zugangsdaten oder ein authentifizierter Kanal blieb nach Ablauf des Laufs verwendbar.

Jede Klasse erfordert eine andere Reparatur. Die erste deutet auf Signalverarbeitung oder den Zeitpunkt der Bereinigung hin. Die zweite deutet auf einen nicht genehmigten Mechanismus zum Abkoppeln hin. Die dritte erfordert eine Remote-Job-ID und ein Abbruchprotokoll. Die vierte erfordert, dass die Aktionsautorität Anfragen an einen aktuellen Lauf bindet, statt darauf zu vertrauen, dass sich der Elternprozess korrekt verhält.

Gleiche danach die Aktionsdatensätze mit dem Zeitablauf ab. Beginne nicht mit den Agenten-Transkripten. Suche nach nach dem Ablauf angenommenen Anfragen, Zielen, die von der ursprünglichen Anweisung abweichen, wiederholten Aufrufen zur Fortsetzung eines Batches und Aufrufen, die der Elternprozess nicht melden konnte, weil er bereits beendet war. Das Transkript kann später die Absicht erklären. Es kann nicht beweisen, dass eine Netzwerkanfrage stattgefunden hat oder nicht.

Entscheide schließlich, ob die Zugangsdaten rotiert oder widerrufen werden müssen. Wenn kein rohes Geheimnis in den Agenten gelangt ist und das Aktions-Gateway Aufrufe nach Ablauf abgewiesen hat, kann sich das verbleibende Risiko auf bereits abgeschlossene Daten oder Änderungen beschränken. Wenn das Kind ein Bearer-Token, einen privaten Schlüssel, SSH-Agent-Zugriff oder einen verbundenen Administrationskanal geerbt hat, musst du davon ausgehen, dass es weiter handeln kann, bis du diese Berechtigung deaktiviert hast. An diesem Punkt verlieren Teams Zeit mit der Frage, ob der Prozess «wahrscheinlich» etwas getan hat. Wenn du nicht feststellen kannst, dass er den Zugriff verloren hat, entziehe den Zugriff.

Ein Fehlertest muss den Waisenprozess sichtbar machen

Aktionen hinter Sallyport stellen
Setze eine signierte Mac-Menüleisten-App als Grenze zwischen deinen Agenten und externen Aktionen ein.

Ein Timeout-Testsystem braucht einen Test, der laut fehlschlägt, wenn ein Kind überlebt. Nur zu prüfen, ob der Elternprozess mit einem Timeout zurückkehrt, beweist fast nichts.

Erstelle ein Fixture, das einen Eltern- und einen Kindprozess in derselben gespeicherten Gruppe startet. Das Kind sollte lange genug warten, damit das Testsystem den Elternprozess mit einem Timeout beendet. Der Elternprozess sollte vor seiner Blockierung die PID und Gruppe des Kindes melden. Prüfe nach dem Timeout, dass keiner der beiden Prozesse existiert und dass das Testaktionsprotokoll nach dem Ablaufereignis keinen angenommenen Aufruf enthält.

Dieses Shell-Fixture zeigt die Form des Problems:

#!/bin/sh
(
  trap 'exit 0' TERM INT
  sleep 600 &
  child=$!
  printf 'parent=%s child=%s pgid=' "$$" "$child"
  ps -o pgid= -p "$$" | tr -d ' '
  wait "$child"
)

Führe es mit demselben Startcode aus, den dein Agenten-Executor verwendet, und nicht aus einem interaktiven Terminal, das unbemerkt ein anderes Verhalten bei der Jobsteuerung liefert. Erfasse die ausgegebenen IDs, erzwinge die Frist und prüfe die Prozesstabelle. Wenn das Kind bleibt, hast du etwas Konkretes gelernt: Entweder hat dein Start keine Gruppe erstellt, wie du angenommen hast, oder dein Timeout-Signal hat sie nicht erreicht.

Wiederhole den Test für die Tools, die deine Agenten tatsächlich aufrufen. Shell-Wrapper, Node-Kindprozess-APIs, Python-Subprozess-Aufrufe, Paketmanager, Compiler und SSH-Helfer treffen unterschiedliche Entscheidungen zu Gruppen, Deskriptoren und Signalweiterleitung. Du brauchst keine Hunderte Tests. Du brauchst einen destruktiven Test für jedes Startmuster, das Nachkommen erzeugen kann.

Füge einen zweiten Fixture hinzu, das versucht, sich abzukoppeln. Der Test kann einen Aufruf zur Sitzungserstellung oder eine Laufzeitoption verwenden, die ein abgekoppeltes Kind erzeugt. Das erwartete Ergebnis sollte eine Ablehnung, die Übernahme durch einen Supervisor oder ein ausdrückliches Registrierungsevent sein. Ein stiller Erfolg ist ein Fehler, weil er dem Agenten zeigt, dass er Arbeit außerhalb der Timeout-Grenze verschieben kann.

Beschränke dich nicht auf lokale PIDs. Wenn dein Agent entfernte Arbeit starten kann, muss die entfernte Seite eine Jobkennung zurückgeben, und du musst den Abbruch nach dem lokalen Timeout testen. Der gewünschte Zustand lautet nicht «Der SSH-Client ist beendet». Der gewünschte Zustand lautet «Der von diesem Lauf identifizierte entfernte Job ist beendet, oder seine verbleibende Arbeit hat einen dokumentierten Verantwortlichen».

Die Freigabe muss vor dem Prozess ablaufen

Die menschliche Freigabe eines Agentenlaufs bedeutet wenig, wenn ein Kind diese Freigabe nutzen kann, nachdem der Lauf für beendet erklärt wurde. Binde die Autorität für externe Aktionen an eine Laufidentität und einen Status, den die Autoritätskomponente bei jeder Anfrage prüft. Wenn der Runner den Lauf als abgelaufen markiert, müssen neue Aktionen dieses Laufs scheitern, selbst wenn ein lokaler Waisenprozess noch CPU-Zeit erhält.

Das bedeutet nicht, für jeden Shell-Befehl einen Menschen zu fragen. Es bedeutet, dass die Komponente, die Berechtigungen einsetzen kann, weiß, wann ein Lauf endete. Diese Komponente kann einem Lauf eine kurzlebige Kennung geben, sie nach dem Widerruf ablehnen und die Ablehnung protokollieren. Das Beenden der Prozessgruppe begrenzt dann den lokalen Schaden, während die Aktionsgrenze die Folgen eines Bereinigungsrennens begrenzt.

Behalte eine Freigabe pro Aktion für Vorgänge bei, die sie benötigen, aber verwechsle Freigabe nicht mit Überwachung. Ein Benutzer kann zu Beginn einer Sitzung einen signierten Agentenprozess freigeben. Wenn ein Nachkomme nach dem Ende des Agenten weiterläuft, hat er die genehmigte Ausführungsdauer überschritten, selbst wenn er denselben lokalen Kontext geerbt hat. Dein System muss diesen Zustand durchsetzen können, statt darauf zu hoffen, dass sich das Kind anständig verhält.

Der Runner sollte außerdem das Abbrechen eines Laufs vom Abbrechen eines externen Effekts unterscheiden. Eine abgebrochene Anfrage kann die API bereits erreicht haben. Ein beendeter SSH-Client kann bereits einen entfernten Befehl gestartet haben. Erfasse die Abbruchanfrage, das beobachtete lokale Ende und jede vom externen System zurückgegebene Bestätigung als getrennte Ereignisse. Diese Formulierung ist weniger beruhigend als «abgebrochen», zeigt den Operatoren aber, was sie tatsächlich wissen.

Die erste Reparatur, die ich an einem Agenten-Executor vornehmen würde, ist klein und unspektakulär: Erstelle und speichere beim Start eine eigene Prozessgruppe, beende diese Gruppe im Timeout-Pfad und prüfe das Ergebnis in einem Fehlertest. Kombiniere das mit einer Aktionsgrenze, die einen Lauf nach Ablauf ablehnt. Sobald diese beiden Fakten gelten, ist ein Waisenprozess ein Vorfall, den du eindämmen und erklären kannst, statt einer vagen Angst, dass möglicherweise noch etwas läuft.

FAQ

Was ist ein verwaister Subprozess in einem KI-Agentenlauf?

Ein verwaister Prozess ist ein Prozess, dessen ursprünglicher Elternprozess beendet wurde, sodass das Betriebssystem ihn neu zuordnet. Er kann weiterlaufen, Netzwerkverbindungen offen halten und bereits erhaltenes Zugangsdatenmaterial verwenden. Ein Timeout des Elternprozesses zeigt nur, dass eine einzelne PID beendet wurde.

Beendet das Beenden eines KI-Agenten auch seine Kindprozesse?

Nur wenn der Runner den Agenten in einer isolierten Prozessgruppe oder einer vergleichbaren Begrenzung startet und anschließend diese gesamte Begrenzung beendet. Wird nur die PID des Elternprozesses beendet, bleiben unabhängig gestartete Kindprozesse bestehen. Teste das mit einem Kindprozess, der weiter schläft, nachdem sein Elternprozess beendet wurde.

Sollte ich für Agenten-Timeouts eine Prozessgruppe oder eine Sitzung verwenden?

Verwende für einen lokalen Befehlsbaum auf macOS eine Prozessgruppe und sende nach Ablauf der Frist ein Signal an die negative Prozessgruppen-ID. Eine Sitzung kann eine umfassendere Grenze bieten, während ein Dienstmanager oder Container stärkere Ressourcenbeschränkungen ermöglicht. Wähle eine Grenze und speichere ihre Kennung, bevor die Arbeit beginnt.

Kann ein verwaister Prozess nach dem Beenden des Agenten noch einen API-Schlüssel verwenden?

Ein SSH-Kontrollsocket, eine offene authentifizierte Verbindung, ein geerbter Dateideskriptor oder ein langlebiges Bearer-Token kann nach dem Tod des Elternprozesses weiter nutzbar sein. Vermeide deshalb die Übergabe roher Zugangsdaten an den Agentenprozess und gib jeder externen Aktion eine begrenzte Lebensdauer. Der Widerruf muss den Aktionspfad abdecken, nicht nur die Chatsitzung.

Wie finde ich auf macOS die Kindprozesse eines Agenten, dessen Timeout abgelaufen ist?

Prüfe auf macOS mit ps PID, PPID, PGID, SID, verstrichene Zeit und Befehlszeile, bevor du ein Signal sendest. Suche nach Prozessen, deren PPID auf 1 oder einen anderen Supervisor geändert wurde und deren PGID weiterhin zur abgelaufenen Ausführung gehört. Sichere diesen Snapshot vor der Bereinigung, wenn du ihn möglicherweise für einen Incident-Bericht brauchst.

Ist `pkill` für die Bereinigung abgelaufener KI-Agenten sicher?

Nein. Eine Namenssuche kann einen unabhängigen Editor, Test-Runner oder einen anderen Agentenlauf beenden und trotzdem ein Kind übersehen, das seinen ausführbaren Namen geändert hat. Sende stattdessen ein Signal an eine gespeicherte Prozessgruppe oder einen von einem Supervisor verwalteten Job.

Was sollte ein Audit der Ausführung eines KI-Agenten erfassen?

Beginne mit der Frist, der Prozessgruppen- oder Job-Kennung, der PID des Elternprozesses, der freigegebenen Aktionsidentität und Zeitstempeln einer monotonen Uhr. Ergänze jeden externen Aufruf, den Exit-Status und jedes Bereinigungssignal. Ohne die Kennung der Grenze wird die spätere Zuordnung zur Vermutung.

Kann ein Aktions-Gateway verhindern, dass Zugangsdaten durch verwaiste Prozesse offengelegt werden?

Es hilft nur, wenn das Gateway die Zugangsdaten außerhalb des Agenten aufbewahrt und die Aktion unabhängig von der eigenen Ausgabe des Agenten protokolliert. Du brauchst trotzdem eine Begrenzung auf Betriebssystemebene, weil ein verwaister Prozess weiterhin Dateien, CPU, Sockets und bereits erhaltene Daten verwenden kann. Ein Gateway dokumentiert, was seine Grenze passiert hat, es lässt einen fehlerhaften Prozessbaum jedoch nicht verschwinden.

Was sollte ich tun, nachdem ich einen verwaisten Agentenprozess entdeckt habe?

Beende zuerst die gespeicherte Gruppe, warte kurz und eskaliere nur bei verbleibenden Prozessen. Sichere anschließend die noch laufenden Prozesse und gleiche Startzeit, Gruppe, offene Netzwerkaktivitäten und Aktionsprotokolle ab. Rotieren oder widerrufen den Zugriff eines Zugangsdatenmaterials, wenn du nicht feststellen kannst, dass der entkommene Prozess ihn verloren hat.

Wie teste ich die Timeout-Bereinigung für einen autonomen Coding-Agenten?

Führe in CI einen absichtlichen Fehlertest aus: Lass einen Elternprozess ein Kind starten, den Timeout des Elternprozesses überschreiten und prüfe anschließend, dass keine der beiden PIDs mehr existiert und nach dem Ablaufereignis keine Aktion angenommen wurde. Wiederhole den Test für Shell-Wrapper, Laufzeitumgebungen und SSH-Helfer, weil jede Schicht weitere Prozesse erzeugen kann. Eine nicht getestete Bereinigung ist nur eine Hoffnung.

Sallyport

Sallyport führt API-Aufrufe und SSH-Befehle für Ihren KI-Agenten aus. Die Schlüssel bleiben in einem lokalen Tresor auf Ihrem Mac; Sie geben jeden Lauf frei, und jede Aktion landet in einem versiegelten Journal.

© 2026 Sallyport · Open Source unter Apache-2.0 · Oleg Sotnikov