Skip to content

Subagenten-Delegation

源码版本v2026.7.20

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

Datenfluss

  1. Die Hauptschleife erhält vom Provider (provider) tool_call: delegate.
  2. delegate_task:2426 wird aufgerufen und erhält die Beschreibung der Teilaufgabe sowie optionale Toolsets.
  3. In DaemonThreadPoolExecutor:2651 wird 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:588 auf, um die Schleife zu drehen
    • Werkzeugaufrufe des Subagenten laufen über registry:765 (Toolsets können vom Vater eingeschränkt werden)
  4. Nach Abschluss gibt der Subagent nur den finalen Text zurück; delegate_task verpackt ihn über tool_result:798 und füllt ihn beim Vater auf.
  5. 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:

python
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:

python
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_depth explizit erhöht, kann ein Subagent in Orchestrator-Rolle weiter delegieren; hängt eine beliebige Zwischenschicht, blockiert die gesamte Kette. DaemonThreadPoolExecutor löst nur das Hängen beim Prozess-Ausstieg, nicht das Laufzeit-Hängen.
  • Approval-Callback falsch angewendet: subagent_auto_approve=true ist 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.

Inoffizielle Community-Lernseite. Basiert auf dem MIT-lizenzierten NousResearch/hermes-agent-Quellcode.