Subagenten-Delegation
Verantwortung
delegate_tool lässt den Haupt-Agenten eine Teilaufgabe an einen isolierten Subagenten delegieren: Der Subagent hat einen eigenen Dialog, ein eigenes Terminal, einen eigenen Werkzeugsatz (toolset); wenn er fertig ist, gibt er nur das finale Ergebnis an den Vater-Agenten zurück. Das realisiert eine Pipeline mit «null Kontextkosten (context cost)» – der Kontext (context) des Vaters wird nicht mit Prozessdetails der Teilaufgabe verschmutzt. Der Subagent läuft in einem ThreadPoolExecutor-Worker; gefährliche Befehle sind per Default auto-deny (konfigurierbar auf auto-approve).
Designmotiv
Lange Pipelines explodieren nicht, weil das Modell unfähig wäre, sondern weil der Vater-Agent alle Zwischenschritte der Werkzeugaufrufe gesehen hat. 200 Zeilen grep, der fehlgeschlagene Patch-Diff, probeweises Build — diese Details landen im Vater-Kontext und verdünnen spätere Entscheidungen. delegate_task wettet darauf, dass der Prozess des Subagenten für den Vater unsichtbar bleibt; nur der finale Text wird zurückgefüllt. Der Preis ist der Verlust an Beobachtbarkeit, also gibt es eine Beobachtungsschicht dazu: DaemonThreadPoolExecutor hält Subagenten-Threads davon ab, den Prozess-Ausstieg zu blockieren; das _active_subagents-Register erlaubt dem TUI, lebende Subagenten aufzulisten, zu pausieren und abzubrechen; max_concurrent_children und max_spawn_depth setzen harte Obergrenzen für Parallelität und Tiefe. Die Isolationsgrenze wird mit «sichtbar und haltbar» erkauft.
Schlüsseldateien
def delegate_task:2426— Delegations-Einstiegclass DelegateEvent:625— Delegations-Event-Enumauto-deny / auto-approve Callback:61-103—_subagent_auto_deny(sichere Vorgabe) /_subagent_auto_approve(opt-in YOLO)_get_subagent_approval_callback:102-160— wählt den Callback gemäßdelegation.subagent_auto_approveDaemonThreadPoolExecutor:1978-1990— residenter Thread-Pool für Subagentenwith DaemonThreadPoolExecutor:2651-2660— Delegations-Ausführungskontexttools/daemon_pool.py— Implementierung desDaemonThreadPoolExecutorMulti-Backend-Terminal:5-15— das Terminal des Subagenten läuft in einem der 6 Sandbox-Backends (sandbox backends) (siehe Sandbox)
Datenfluss
- Die Hauptschleife erhält vom Provider (provider)
tool_call: delegate. delegate_task:2426wird aufgerufen und erhält die Beschreibung der Teilaufgabe sowie optionale Toolsets.- In
DaemonThreadPoolExecutor:2651wird ein Worker gestartet:- der Worker-Installer installiert den Approval-Callback (
tools/delegate_tool.py:102); per Default auto-deny für gefährliche Befehle - der Subagent baut seinen eigenen Dialogkontext auf und ruft
run_conversation:588auf, um die Schleife zu drehen - Werkzeugaufrufe des Subagenten laufen über
registry:765(Toolsets können vom Vater eingeschränkt werden)
- der Worker-Installer installiert den Approval-Callback (
- Nach Abschluss gibt der Subagent nur den finalen Text zurück;
delegate_taskverpackt ihn übertool_result:798und füllt ihn beim Vater auf. - Der Kontext des Vaters wächst nur um ein Tool-Ergebnis, nicht um Prozessdetails.
Warum einen Approval-Callback installieren? Der Kommentar sagt es direkt: Der interaktive Approval-Callback der CLI liegt in terminal_tool.py in einem threading.local(); der Worker-Thread erbt ihn nicht; ohne installierten Callback fällt er auf input() zurück, und stdin aus einem Worker-Thread verklemmt sich mit dem prompt_toolkit-TUI des Vaters. Also lautet der Default deny:
def _subagent_auto_deny(command: str, description: str, **kwargs) -> str:
"""Auto-deny dangerous commands in subagent threads (safe default)."""
logger.warning(
"Subagent auto-denied dangerous command: %s (%s). "
"Set delegation.subagent_auto_approve: true to allow.",
command, description,
)
return "deny"Die Delegation nutzt DaemonThreadPoolExecutor statt die Standardbibliothek; der Grund steht am Anfang von daemon_pool.py: Standard-Worker sind nicht-daemonisch; _python_exit joint sie bedingungslos, und ein hängender Worker kann den Prozess-Ausstieg für Minuten blockieren. Die Daemon-Variante spawnt Daemon-Threads und überspringt die _threads_queues-Registrierung. Der erste Schritt in delegate_task ist die Pause-Abfrage is_spawn_paused(); dieser Schalter wird vom TUI (p-Taste unter /agents) oder vom RPC delegation.pause getriggert und friert bei außer-Kontrolle-geratenen Delegationsbäumen neue Delegationen ein, ohne bereits laufende Subagenten zu berühren:
if is_spawn_paused():
return tool_error(
"Delegation spawning is paused. Clear the pause via the TUI "
"(`p` in /agents) or the `delegation.pause` RPC before retrying."
)Auch die Tiefe ist hart begrenzt; default MAX_DEPTH = 1, also vom Vater (0) zum Kind (1) und Schluss; ein Enkel wird abgewiesen. Mehrstufige Delegation erfordert explizite Konfiguration delegation.max_spawn_depth.
Grenzen und Fehler
- Budget-Leck (budget leak): Der Subagent hat sein eigenes
max_iterations, aber die Token-Ausgaben sind für den Vater unsichbar; bei N parallel delegierten Subagenten häuft sich die Provider-Rechnung auf das N-Fache.max_concurrent_children(Default 3) ist nur eine weiche Obergrenze; hochgedreht, reißt es das Budget (budget) auf einen Schlag. - Verschachtelungs-Deadlock: Default-Tiefe ist 1; wird
max_spawn_depthexplizit erhöht, kann ein Subagent in Orchestrator-Rolle weiter delegieren; hängt eine beliebige Zwischenschicht, blockiert die gesamte Kette.DaemonThreadPoolExecutorlöst nur das Hängen beim Prozess-Ausstieg, nicht das Laufzeit-Hängen. - Approval-Callback falsch angewendet:
subagent_auto_approve=trueist eine prozessweite Konfiguration; einmal gesetzt, YOLOen alle Subagenten. Es gibt keine Per-Job-Granularität; in Batch-Szenarien wird es leicht versehentlich eingeschaltet. - Ergebnis-Glaubwürdigkeit: Der Vater erhält nur den finalen Text, der Zwischenprozess ist nicht im Kontext. Schreibt der Subagent eine Halluzination als Fakt in die Endantwort, kann der Vater darauf nicht prozessbasiert reagieren; bei langen Delegationsketten schaukelt sich die Halluzination Ebene für Ebene hoch.
Zusammenfassung
Subagenten-Delegation = Kontext-Isolation + Kontrolle gefährlicher Befehle. Der Vater sieht nur das Ergebnis, nicht den Prozess, was lange Pipelines vor Prozessrauschen schützt. Standard ist auto-deny; für Batch-/Cron-Szenarien lässt sich auto-approve opt-in einschalten. Die Terminal-Ausführung fällt auf die Sandbox-Backends.