SubagentRunner: contexto independiente para subtareas
Responsabilidades
SubagentRunner es el ejecutor con el que Cline encapsula la acción de «dejar que un LLM ejecute una subtarea en su propia ventana de contexto». Cuando el agente principal se encuentra con una llamada a la herramienta subagent (new SubagentRunner:215), lanza un runner por cada prompt. El runner ejecuta internamente un agent loop en miniatura —recibe el stream, parsea tool_calls, los ejecuta y vuelve a inyectar los resultados en la conversación— pero con un arreglo conversation propio y un ContextManager independiente, sin contaminar el historial de mensajes del Task principal.
Su posición está por debajo del Task principal y por encima de las herramientas concretas. El Task principal pasa el prompt y un TaskConfig; el runner usa SubagentBuilder para calcular el subconjunto de herramientas permitidas, el system prompt y el API handler del subagente, y luego entra en su bucle run. Al terminar, el subagente invoca attempt_completion y la cadena resultante se devuelve tal cual al agente principal, que la ve como un resultado de herramienta normal. Este diseño permite al Task principal confinar acciones costosas como «explorar código, leer varios archivos y sintetizar una respuesta» dentro de una ventana aislada, de modo que sus tokens intermedios no arrastren el contexto principal.
Motivación de diseño
- Ventana de contexto independiente: el subagente usa su propio arreglo
ClineStorageMessage[]; el Task principal no ve las llamadas a herramientas intermedias del subagente (conversation init:463). - Lista blanca de herramientas: el subagente por defecto solo lee y no escribe;
SUBAGENT_DEFAULT_ALLOWED_TOOLSsolo incluye file_read / list_files / search / list_code_def / bash / use_skill / attempt_completion (default allowed tools:14). - attempt_completion forzado: independientemente del conjunto de herramientas configurado por el usuario,
ATTEMPTsiempre se añade (force ATTEMPT:96), de lo contrario el subagente no podría cerrar. - Override de modelo por configuración de agente:
AgentConfigLoaderlee elmodelIdde cada agente ySubagentBuilder.applyModelOverridereemplaza el apiHandler (applyModelOverride:57), permitiendo distintos modelos para distintos subagentes. - Compactación automática de contexto: antes de cada ronda, el runner verifica el conteo de tokens de la petición anterior y, si supera el umbral, llama a
compactConversationForContextWindow, que primero aplica la optimización de file read y luego el truncado (shouldCompactBeforeNextRequest:488). - Reintento ante respuesta vacía: si en una ronda el modelo no emite tool_use, se trata como respuesta vacía; se inyecta un prompt
noToolsUsedy se reinterroga; si se superaMAX_EMPTY_ASSISTANT_RETRIES(3), falla directamente (empty response retry:662). - Cancelación sincronizada de runners paralelos: una llamada a la herramienta subagent puede traer varios prompts; los runners se ejecutan en paralelo y se hace polling del abort cada 100 ms (
abort poll:216), interrumpiendo todos a la vez.
Archivos clave
SubagentRunner class:254— mantiene agent, apiHandler, allowedTools y estado de abort.run method:330— bucle principal; en cada ronda recibe el stream, parsea tool_calls, los ejecuta y los reinyecta.while loop:485— bucle infinito; solo sale con attempt_completion o abort.createMessageWithInitialChunkRetry:519— recibe el stream y, si el primer chunk reporta «context window exceeded», compacta y reintenta.tool whitelist check:726— si la herramienta no está en allowedTools, devuelve toolError sin ejecutarla.attempt_completion handling:702— al detectar attempt, devuelve la cadena result al agente principal y sale.abort method:272— llama a api.abort y cancela el comando en ejecución.compactConversationForContextWindow:895— primero intenta la optimización de file read y luego pasa agetNextTruncationRangepara borrar un tramo.shouldCompactBeforeNextRequest:977— conuseAutoCondensey modelos next-gen usa umbral 0.75; en caso contrario,maxAllowedSize.buildSystemPrompt:73— compone<generated> + <agent identity> + SUBAGENT_SYSTEM_SUFFIX.spawn runners:215— punto donde el agente principal realmente instancia SubagentRunner.
Flujo de datos
Al arrancar, el runner prepara el system prompt y el contenido de usuario inicial. La conversación inicial solo contiene dos entradas: el prompt del usuario + un bloque de metadata de workspace, que el task loop del lado servidor exige verificar:
// 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;
// ...
}
// ...
}Esto está en conversation + while:463. Entrado el bucle, cada ronda createMessageWithInitialChunkRetry consume el stream y clasifica los chunks en usage/text/tool_calls/reasoning. Al acabar el flujo, ejecuta los finalized tool calls uno a uno; el que toca attempt_completion devuelve el result directamente al agente principal (return on attempt:723); las demás herramientas se ejecutan vía coordinator.getHandler(toolName).execute y el resultado se reinyecta como user content para seguir el bucle.
Límites y fallos
- Herramienta fuera de la lista blanca = toolError directo: si el subagente intenta invocar
write_to_file, se intercepta y se inserta la cadena de error en el user content (whitelist check:726), el bucle continúa y no se aborta toda la tarea. - attempt_completion sin result: si result está vacío, se inserta
missingToolParameterErroren lugar de cerrar (missing result:705). - Retry si el primer chunk reporta context window exceeded: si el primer chunk falla con un error relacionado a context window, se compacta la conversación y se reintenta (
context window retry:1036), hastaMAX_INITIAL_STREAM_ATTEMPTSveces. - Comando en ejecución durante abort: abort no solo cancela el flujo de API, también delega a
cancelRunningCommandToolpara detener el bash en curso (cancel running command:281). - Fallback native vs non-native tool calls: en modo non-native, al recibir chunks estructurados de tool_calls se ejecutan igual, pero el resultado se serializa como texto plano para evitar desalineación en el emparejamiento tool_result (
non-native fallback:631). - Acumulación de stats: cada chunk actualiza inputTokens/outputTokens/cacheWrite/cacheRead/totalCost y los comunica a la UI en tiempo real vía
onProgress(stats accumulation:534). - Assistant vacío también cuenta como ronda: si el contenido del assistant viene completamente vacío, se inserta manualmente «Failure: I did not provide a response.» antes de inyectar noToolsUsed (
empty assistant:671).
Resumen
SubagentRunner permite al agente principal externalizar la «exploración de gran alcance» a un mini-agent aislado. Para ver la estrategia de compactación de contexto de la que depende, consulta context-manager; para ver cómo el Task principal reinyecta los resultados de herramientas y recursiona, consulta agent-loop/task-class; para ver cómo las herramientas MCP son invocadas por el agente principal, consulta mcp-hub.