SubagentRunner: Unabhängiger Kontext für Teilaufgaben
Verantwortung
SubagentRunner ist der von Cline gekapselte Executor für «ein LLM eine Teilaufgabe in seinem eigenen Kontextfenster ausführen zu lassen». Wenn der Haupt-Agent auf einen Werkzeugaufruf (tool_use) wie subagent trifft (new SubagentRunner:215), wird für jeden Prompt ein Runner gestartet. Der Runner führt intern eine Mini-Agent-Loop aus — Stream ziehen, tool_calls parsen, ausführen, Ergebnisse zurück in die Konversation pushen — aber mit einem vollständig unabhängigen conversation-Array und einem unabhängigen ContextManager, sodass die Nachrichten-History des Haupt-Tasks nicht verschmutzt wird.
Seine Position liegt unter dem Haupt-Task und über den konkreten Werkzeugen. Der Haupt-Task übergibt Prompt und TaskConfig, der Runner berechnet über SubagentBuilder die Werkzeug-Teilmenge, die System-Prompts und den API-Handler, die dieser Subagent nutzen darf, und iteriert dann in seiner eigenen run-Methode. Wenn der Subagent fertig ist, ruft er attempt_completion auf; der Ergebnis-String geht unverändert an den Haupt-Agent zurück, der aus Sicht des Haupt-Agent nur ein normales Werkzeugergebnis ist. Dieses Design erlaubt es dem Haupt-Task, teure Aktionen wie «Code erkunden, mehrere Dateien lesen, eine Antwort synthetisieren» in einem isolierten Fenster einzusperren, ohne dass die zwischenzeitlichen Token den Hauptkontext belasten.
Entwurfsmotivation
- Unabhängiges Kontextfenster: Der Subagent nutzt sein eigenes
ClineStorageMessage[]-Array; der Haupt-Task sieht die zwischenzeitlichen tool-Aufrufe des Subagenten nicht (conversation init:463). - Werkzeug-Whitelist: Der Subagent ist standardmäßig nur lesend,
SUBAGENT_DEFAULT_ALLOWED_TOOLSerlaubt nur file_read / list_files / search / list_code_def / bash / use_skill / attempt_completion (default allowed tools:14). - attempt_completion erzwingen: Unabhängig davon, welche Werkzeuge der Benutzer konfiguriert, wird
ATTEMPTimmer hinzugefügt (force ATTEMPT:96), da der Subagent sonst nicht abschließen kann. - Modell-Override pro Agent:
AgentConfigLoaderliest diemodelIdeines einzelnen Agenten,SubagentBuilder.applyModelOverrideersetzt den apiHandler (applyModelOverride:57), sodass verschiedene Subagenten verschiedene Modelle nutzen. - Automatisches Kontext-Komprimieren: Der Runner prüft vor jeder Anfrage die Token-Zahl der letzten Anfrage; bei Überschreitung der Schwelle wird
compactConversationForContextWindowaufgerufen, das zuerst file read-Optimierung durchführt und dann abschneidet (shouldCompactBeforeNextRequest:488). - Leer-Antwort-Wiederholung: Wenn das Modell in dieser Runde kein tool_use ausgibt, gilt dies als Leerantwort; es wird ein
noToolsUsed-Prompt angehängt und erneut gefragt, bei mehr alsMAX_EMPTY_ASSISTANT_RETRIES(3) Versuchen schlägt der Lauf direkt fehl (empty response retry:662). - Parallele Runner-Abbruch-Synchronisation: Ein subagent-Werkzeugaufruf kann mehrere Prompts enthalten; mehrere Runner laufen parallel, abort wird alle 100 ms einmal gepollt (
abort poll:216), um alle Runner gemeinsam abzubrechen.
Schlüsseldateien
SubagentRunner class:254— hält agent, apiHandler, allowedTools, Abort-Status.run method:330— Hauptschleife: pro Runde Stream ziehen, tool_calls parsen, ausführen, zurückpushen.while loop:485— Endlosschleife, verlassen via attempt_completion oder abort.createMessageWithInitialChunkRetry:519— zieht den Stream und komprimiert bei «context window exceeded» im ersten Chunk vor Wiederholung.tool whitelist check:726— wenn nicht in allowedTools, wird toolError zurückgegeben und nicht ausgeführt.attempt_completion handling:702— bei attempt wird der result-String an den Haupt-Agent zurückgegeben und die Schleife verlassen.abort method:272— ruft api.abort auf, bricht laufende Kommandos ab.compactConversationForContextWindow:895— versucht zuerst file read-Optimierung, danngetNextTruncationRangezum Abschneiden.shouldCompactBeforeNextRequest:977—useAutoCondense+ Next-Gen-Modell nutzt 0,75-Schwelle, sonstmaxAllowedSize.buildSystemPrompt:73— baut<generated> + <agent identity> + SUBAGENT_SYSTEM_SUFFIX.spawn runners:215— wo der Haupt-Agent den SubagentRunner tatsächlich startet.
Datenfluss
Beim Start bereitet der Runner den Systemprompt und den anfänglichen user-Content vor. Die Anfangskonversation enthält nur zwei Einträge: den Prompt des Benutzers + einen Workspace-Metadaten-Block, der für die Validierung des serverseitigen Task-Loops benötigt wird:
// apps/vscode/src/core/task/tools/subagent/SubagentRunner.ts
const conversation: ClineStorageMessage[] = [
{
role: "user",
content: [
{ type: "text", text: prompt } as ClineTextContentBlock,
// Server-side task loop checks require workspace metadata to be present in the
// initial user message of subagent runs.
...(workspaceMetadataEnvironmentBlock
? [{ type: "text", text: workspaceMetadataEnvironmentBlock } as ClineTextContentBlock]
: []),
],
},
];
while (true) {
if (
usageState.lastRequest &&
this.shouldCompactBeforeNextRequest(usageState.lastRequest.totalTokens, api, providerInfo.model.id)
) {
const compactResult = this.compactConversationForContextWindow(
contextManager,
conversation,
contextState.conversationHistoryDeletedRange,
);
contextState.conversationHistoryDeletedRange = compactResult.conversationHistoryDeletedRange;
// ...
}
// ...
}Dieses Stück liegt nahe conversation + while:463. Nach Eintritt in die Schleife zieht jede Runde createMessageWithInitialChunkRetry den Stream; Stream-Chunks werden in usage/text/tool_calls/reasoning klassifiziert. Nach Stream-Ende werden die finalisierten Tool-Calls einzeln ausgeführt; ein Call, der attempt_completion trifft, gibt das Ergebnis direkt an den Haupt-Agent zurück (return on attempt:723); andere Werkzeuge werden über coordinator.getHandler(toolName).execute ausgeführt, das Ergebnis als user-Content zurückgespült und die Schleife fortgesetzt.
Grenzen und Fehler
- Werkzeug nicht in Whitelist gibt direkt toolError: Wenn der Subagent z. B.
write_to_fileaufrufen will, wird es blockiert; die Fehler-Zeichenkette wird in den user-Content gesteckt (whitelist check:726), die Schleife läuft weiter und bricht die gesamte Aufgabe nicht ab. - attempt_completion ohne result: Wenn result leer ist, wird
missingToolParameterErroreingefügt statt abzuschließen (missing result:705). - Erster Chunk mit context window exceeded: Wiederholung: Wenn der erste Chunk fehlschlägt und der Fehler mit dem Kontextfenster zusammenhängt, wird sofort komprimiert und erneut versucht (
context window retry:1036), maximalMAX_INITIAL_STREAM_ATTEMPTSMal. - Laufendes Kommando bei abort: abort bricht nicht nur den API-Stream ab, sondern delegiert auch an
cancelRunningCommandTool, um den aktuellen bash zu stoppen (cancel running command:281). - native vs non-native tool calls fallback: Im non-native-Modus werden beim Empfang strukturierter tool_calls-Chunks diese dennoch ausgeführt, aber das Ergebnis als Plain-Text serialisiert zurückgegeben, um eine tool_result-Paarungs-Verschiebung zu vermeiden (
non-native fallback:631). - stats-Akkumulation: Bei jedem Chunk werden inputTokens/outputTokens/cacheWrite/cacheRead/totalCost aktualisiert und über
onProgressin Echtzeit ans Frontend gemeldet (stats accumulation:534). - Leere assistant-Antwort zählt als Runde: Wenn der assistant-Content vollständig leer ist, wird aktiv ein «Failure: I did not provide a response.» eingefügt und dann noToolsUsed angehängt (
empty assistant:671).
Zusammenfassung
SubagentRunner ermöglicht es dem Haupt-Agent, «große Erkundungen» an einen isolierten Mini-Agenten auszulagern. Um die Kontext-Komprimierungsstrategie zu sehen, von der er abhängt, lies context-manager; um zu sehen, wie der Haupt-Task Werkzeugergebnisse zurückführt und rekursiert, lies agent-loop/task-class; um zu sehen, wie MCP-Werkzeuge vom Haupt-Agent aufgerufen werden, lies mcp-hub.
Siehe offizielle Dokumentation: Cline-Dokumentation · README