Cuando un solo agente no es suficiente

Todo lo que hemos construido hasta ahora en esta serie ha sido un solo agente: un LLM con un prompt de sistema, un conjunto de herramientas y un bucle que se ejecuta hasta que la tarea esté completa. Eso funciona notablemente bien para tareas enfocadas — responder una pregunta, escribir una función, buscar en una base de datos. ¿Pero qué pasa cuando la tarea es demasiado grande, demasiado variada o demasiado compleja para que un solo agente la maneje?

Considera construir una aplicación web a partir de una especificación en lenguaje natural. Alguien necesita descomponer la especificación en componentes. Alguien necesita escribir el código del frontend. Alguien más escribe el backend. Un revisor verifica bugs y problemas de seguridad. Un tester escribe y ejecuta pruebas. En un equipo humano, estas son personas diferentes con habilidades diferentes y contextos diferentes. ¿Podrían agentes separados alimentados por LLMs llenar cada rol?

Un sistema multiagente es exactamente esto: múltiples agentes alimentados por LLMs que se comunican y colaboran para lograr una tarea que ninguno de ellos podría (o debería) manejar solo. Cada agente tiene su propio prompt de sistema que define su rol, su propio conjunto de herramientas y su propio historial de conversación. Interactúan pasando mensajes — la salida de uno se convierte en la entrada de otro.

¿Por qué no simplemente darle a un agente todas las herramientas y todas las instrucciones? Tres razones surgen repetidamente en la práctica:

  • Límites de contexto: un solo agente acumula todos los resultados de herramientas, todo el razonamiento intermedio y todo el contexto de la tarea en una conversación. Para tareas complejas, esto llena rápidamente la ventana de contexto. Agentes separados mantienen cada uno su propio contexto más pequeño — solo la información relevante para su rol.
  • Especialización: un prompt de sistema que dice "Eres un revisor de seguridad senior. Tu trabajo es encontrar vulnerabilidades" produce un comportamiento diferente a uno que dice "Eres un desarrollador Python. Escribe código limpio y probado." Un agente tratando de ser todo a la vez tiende a ser mediocre en cada rol. Agentes separados con prompts de sistema enfocados son individualmente mejores en sus tareas asignadas.
  • Paralelismo: un solo agente es inherentemente secuencial — hace una cosa a la vez. Si la tarea implica buscar en cinco bases de datos diferentes, un solo agente las llama una por una. Cinco agentes paralelos pueden buscar simultáneamente, reduciendo el tiempo real por un factor de cinco.

Estos beneficios son reales, pero como veremos al final de este artículo, vienen con costos reales. La pregunta no es "¿debería usar multiagente?" sino "¿la tarea realmente lo necesita?"

Patrones de orquestación

Si tienes múltiples agentes, necesitas una forma de coordinarlos. ¿Quién habla con quién? ¿Quién decide qué se hace a continuación? ¿Cómo fluyen los resultados entre agentes? La respuesta depende del patrón que elijas, y hay cuatro principales.

Secuencial (pipeline). El patrón más simple: el Agente A termina, pasa su salida al Agente B, que termina y pasa al Agente C. Piensa en una línea de ensamblaje: un agente Investigador recopila información, un agente Escritor la convierte en prosa, y un agente Editor pule la prosa. Cada agente ve solo la salida del anterior. Es fácil de implementar, fácil de depurar (puedes inspeccionar la salida en cada etapa) y predecible. La desventaja es la velocidad: no hay paralelismo. Si el Investigador toma 30 segundos, el Escritor espera. Si algún agente en la cadena falla, todo lo que viene después se bloquea.

# Pseudocode: sequential multi-agent pipeline
def run_pipeline(topic):
    # Stage 1: Research
    research = researcher_agent.run(
        f"Research the topic: {topic}. Return key facts and sources."
    )

    # Stage 2: Write (receives research output)
    draft = writer_agent.run(
        f"Write a blog post based on this research:
{research}"
    )

    # Stage 3: Edit (receives draft)
    final = editor_agent.run(
        f"Edit this draft for clarity and accuracy:
{draft}"
    )

    return final

Paralelo (fan-out / fan-in). Múltiples agentes trabajan simultáneamente en diferentes subtareas, y un agente final combina los resultados. Por ejemplo, buscar en cinco fuentes de datos diferentes en paralelo: cada agente de búsqueda consulta una fuente, y un agente sintetizador fusiona los resultados en una sola respuesta. Esto es rápido — el tiempo total es el tiempo del agente más lento, no la suma — pero la coordinación es más difícil. El sintetizador necesita reconciliar información conflictiva de diferentes fuentes, manejar fallos parciales (¿qué pasa si un agente de búsqueda agota el tiempo?) y producir un todo coherente de partes dispares.

import asyncio

async def parallel_search(query):
    # Fan-out: launch search agents in parallel
    tasks = [
        search_agent_arxiv.run(query),
        search_agent_wikipedia.run(query),
        search_agent_news.run(query),
        search_agent_github.run(query),
    ]
    results = await asyncio.gather(*tasks)

    # Fan-in: synthesise all results
    combined = "

".join(
        f"Source {i+1}:
{r}" for i, r in enumerate(results)
    )
    answer = synthesiser_agent.run(
        f"Synthesise these search results into a single answer:
{combined}"
    )
    return answer

Jerárquico (gerente / trabajador). Un agente gerente descompone la tarea en subtareas y delega cada una a un agente trabajador especializado. El gerente ve el panorama general y coordina; los trabajadores se enfocan en la ejecución. Esto refleja cómo funcionan los equipos humanos: un gerente de proyecto asigna trabajo de frontend al desarrollador de frontend, trabajo de backend al desarrollador de backend, y pruebas al ingeniero de QA. El gerente puede decidir dinámicamente qué trabajadores invocar, cómo particionar el trabajo, y cuándo iterar — si el tester encuentra un bug, el gerente lo enruta de vuelta al programador. Es el patrón más flexible pero también el más complejo: el propio agente gerente necesita ser lo suficientemente capaz para planificar, descomponer y enrutar eficazmente.

Debate / discusión. En lugar de dividir el trabajo, este patrón tiene agentes con diferentes perspectivas que argumentan y refinan una respuesta. Un agente propone una solución, un segundo agente la critica, y van y vienen hasta que convergen. Este enfoque fue formalizado en el artículo "Improving Factuality and Reasoning in Language Models through Multiagent Debate" (Du et al., 2023) , que mostró que el debate multiagente mejora la factualidad en benchmarks como TruthfulQA y el razonamiento matemático en GSM8K. La idea es que un solo modelo puede estar equivocado con confianza, pero cuando se le obliga a defender su respuesta contra un crítico, los errores salen a la superficie y se corrigen. El costo es que este es el patrón que más tokens consume: cada ronda de debate duplica (o más) las llamadas al LLM, y la convergencia no está garantizada.

💡 Estos patrones no son mutuamente excluyentes. Un sistema jerárquico podría usar un pipeline secuencial dentro de un trabajador, o desplegar múltiples trabajadores en paralelo. Los sistemas del mundo real a menudo mezclan patrones según la estructura de la tarea.

CrewAI

Ahora que tenemos la teoría, ¿cómo se ven estos patrones en código? Han surgido varios frameworks para facilitar la construcción de sistemas multiagente. Empecemos con CrewAI (GitHub) , uno de los frameworks multiagente más populares. Su metáfora central es un crew (equipo): un grupo de agentes con roles definidos que colaboran en un conjunto de tareas.

Cada agente en CrewAI se define por cuatro cosas:

  • Role: un título de trabajo como "Analista de Datos Senior" o "Escritor Técnico". Esto se convierte en parte del prompt de sistema del agente.
  • Goal: lo que el agente intenta lograr, por ejemplo "Analizar datos de ventas trimestrales e identificar tendencias."
  • Backstory: contexto adicional que moldea la persona y el comportamiento del agente: "Eres un analista veterano de 10 años conocido por encontrar perspectivas que otros pasan por alto."
  • Tools: las herramientas específicas a las que puede acceder este agente (una herramienta de búsqueda, una herramienta de ejecución de código, una herramienta de lectura de archivos, etc.).

Las tareas se definen por separado con descripciones y salidas esperadas, y luego se asignan a agentes específicos. El crew entonces orquesta la ejecución — ya sea secuencialmente (los agentes se ejecutan uno tras otro, cada uno recibiendo la salida del agente anterior) o jerárquicamente (un agente gerente delega tareas a trabajadores). La metáfora de juego de roles es lo que hace distintivo a CrewAI: al darle a los agentes personas, objetivos e historias explícitas, el framework aprovecha la capacidad del modelo para actuar roles, lo que en la práctica lleva a salidas más enfocadas y orientadas a la tarea.

from crewai import Agent, Task, Crew, Process

# Define agents with roles, goals, and backstories
researcher = Agent(
    role="Senior Research Analyst",
    goal="Find comprehensive, accurate information on the given topic",
    backstory="You are an experienced researcher who excels at finding "
              "reliable sources and extracting key insights from them.",
    tools=[search_tool, web_scraper_tool],
    verbose=True,
)

writer = Agent(
    role="Technical Writer",
    goal="Write clear, engaging content based on research findings",
    backstory="You are a skilled technical writer who translates complex "
              "topics into accessible prose without losing accuracy.",
    tools=[],  # Writer doesn't need external tools
    verbose=True,
)

# Define tasks and assign to agents
research_task = Task(
    description="Research the topic '{topic}' thoroughly. "
                "Find key facts, recent developments, and expert opinions.",
    expected_output="A structured research brief with key findings and sources.",
    agent=researcher,
)

writing_task = Task(
    description="Write a blog post based on the research brief. "
                "Make it engaging, accurate, and well-structured.",
    expected_output="A polished blog post of 800-1200 words.",
    agent=writer,
)

# Create the crew and run
crew = Crew(
    agents=[researcher, writer],
    tasks=[research_task, writing_task],
    process=Process.sequential,  # researcher runs first, writer second
    verbose=True,
)

result = crew.kickoff(inputs={"topic": "quantum error correction"})

Detrás de escena, CrewAI maneja la fontanería: pasa la salida del investigador al contexto del escritor, gestiona el historial de conversación de cada agente, y proporciona registro para que puedas ver exactamente qué hizo cada agente. El framework soporta tanto modelos de OpenAI como de código abierto, y puede extenderse con herramientas personalizadas.

AutoGen y LangGraph

La metáfora de juego de roles de CrewAI funciona bien para delegación directa, pero patrones multiagente más complejos — conversaciones dinámicas, ramificación condicional, aprobación con humano en el bucle — necesitan frameworks más flexibles. Dos de los más prominentes son AutoGen y LangGraph.

AutoGen (Wu et al., 2023) es el framework de conversación multiagente de Microsoft. Su abstracción central es el agente conversable : una entidad que puede enviar y recibir mensajes. Los agentes no solo ejecutan tareas de forma aislada — tienen conversaciones entre sí. Un AssistantAgent (alimentado por LLM) podría discutir un problema con un UserProxyAgent (que representa a un humano o ejecuta código en nombre del humano), pasando mensajes de ida y vuelta hasta que lleguen a una solución.

Lo que hace distintivo a AutoGen es que un humano es simplemente otro agente en la conversación. El UserProxyAgent puede configurarse para siempre pasar mensajes a un humano para aprobación, para auto-aprobar ciertos tipos de acciones, o para ejecutar código y devolver la salida. Esto hace que los flujos de trabajo con humano en el bucle sean naturales en lugar de añadidos. AutoGen también soporta chat grupal : múltiples agentes comparten un solo hilo de conversación, con un gerente que decide quién habla a continuación. Esto habilita el patrón de debate de forma natural — un proponente, un crítico y un moderador pueden todos participar en un hilo.

from autogen import AssistantAgent, UserProxyAgent

# An LLM-powered assistant
assistant = AssistantAgent(
    name="analyst",
    system_message="You are a data analyst. Write Python code to answer "
                   "questions about data. Always explain your reasoning.",
    llm_config={"model": "gpt-4o"},
)

# A proxy that executes code on behalf of the user
user_proxy = UserProxyAgent(
    name="user",
    human_input_mode="NEVER",        # auto-execute, no human approval
    code_execution_config={"work_dir": "workspace"},
    max_consecutive_auto_reply=5,     # stop after 5 back-and-forth rounds
)

# Start a conversation: user_proxy sends a task, agents chat until done
user_proxy.initiate_chat(
    assistant,
    message="Analyse the dataset in 'sales.csv'. "
            "What month had the highest revenue?",
)

LangGraph (GitHub) toma un enfoque fundamentalmente diferente. Donde AutoGen modela a los agentes como participantes conversacionales, LangGraph los modela como nodos en un grafo . Las aristas entre nodos representan transiciones: después de que el nodo investigador termina, el control fluye al nodo escritor (o al nodo revisor, dependiendo de una condición). El grafo mantiene un objeto de estado compartido del que todos los nodos pueden leer y escribir, y la lógica de enrutamiento en cada arista determina qué nodo se ejecuta a continuación.

Este enfoque de máquina de estados te da control fino sobre el flujo. Puedes definir aristas condicionales ("si el código pasa las pruebas, ir al nodo de despliegue; si falla, volver al nodo del programador"), ciclos ("iterar entre programador y tester hasta que todas las pruebas pasen"), y ramas paralelas ("ejecutar los nodos de frontend y backend simultáneamente, luego unir en el nodo integrador"). La desventaja es más código repetitivo: estás conectando explícitamente un grafo, lo cual es más código que la definición declarativa de crew de CrewAI pero te da más control sobre exactamente qué sucede y cuándo.

from langgraph.graph import StateGraph, END
from typing import TypedDict

class AppState(TypedDict):
    task: str
    code: str
    test_result: str
    attempts: int

def coder_node(state: AppState) -> dict:
    # LLM generates or fixes code based on state
    code = coding_agent.run(state["task"], previous_code=state.get("code"))
    return {"code": code, "attempts": state.get("attempts", 0) + 1}

def tester_node(state: AppState) -> dict:
    # Run tests on the generated code
    result = testing_agent.run(state["code"])
    return {"test_result": result}

def should_retry(state: AppState) -> str:
    if "PASS" in state["test_result"]:
        return "done"
    if state["attempts"] >= 3:
        return "done"       # give up after 3 attempts
    return "retry"

# Build the graph
graph = StateGraph(AppState)
graph.add_node("coder", coder_node)
graph.add_node("tester", tester_node)

graph.set_entry_point("coder")
graph.add_edge("coder", "tester")           # coder always goes to tester
graph.add_conditional_edges("tester", should_retry, {
    "retry": "coder",   # loop back if tests fail
    "done": END,         # finish if tests pass or max attempts reached
})

app = graph.compile()
result = app.invoke({"task": "Write a function to sort a linked list"})
💡 La diferencia clave: CrewAI es "define el equipo y déjalos trabajar", AutoGen es "define la conversación y déjalos hablar", y LangGraph es "define el grafo y controla el flujo". La elección correcta depende de si tu problema se modela mejor como un equipo, una conversación o un flujo de trabajo.

OpenAI Swarm y el enfoque de Anthropic

Mientras que los frameworks anteriores ofrecen orquestación multiagente con funciones completas, hay un argumento convincente para mantener las cosas mínimas. Dos enfoques de proveedores de modelos principales ilustran esta filosofía.

OpenAI Swarm (GitHub) es un framework experimental y educativo que reduce los sistemas multiagente a dos primitivas: agentes (un prompt de sistema más una lista de funciones) y traspasos (un agente transfiriendo control a otro). Un traspaso se implementa como una función ordinaria que devuelve el siguiente agente — cuando el agente de triaje decide que la pregunta del usuario es sobre facturación, llama a una función transfer_to_billing() que devuelve el agente de facturación, y la conversación continúa sin interrupciones con el prompt de sistema y las herramientas del agente de facturación.

from swarm import Swarm, Agent

client = Swarm()

# A handoff is just a function that returns another agent
def transfer_to_billing():
    """Transfer the conversation to the billing specialist."""
    return billing_agent

def transfer_to_technical():
    """Transfer the conversation to technical support."""
    return technical_agent

triage_agent = Agent(
    name="Triage",
    instructions="You are a customer service triage agent. "
                 "Determine if the user needs billing help or technical help, "
                 "then transfer to the appropriate specialist.",
    functions=[transfer_to_billing, transfer_to_technical],
)

billing_agent = Agent(
    name="Billing",
    instructions="You are a billing specialist. Help with invoices, "
                 "payments, and subscription changes.",
    functions=[lookup_invoice, process_refund],
)

technical_agent = Agent(
    name="Technical Support",
    instructions="You are a technical support specialist. "
                 "Help debug issues and guide users through fixes.",
    functions=[check_system_status, create_ticket],
)

# Run: Swarm manages handoffs automatically
response = client.run(
    agent=triage_agent,
    messages=[{"role": "user", "content": "I was charged twice last month"}],
)

Swarm es explícitamente no es un framework de producción . OpenAI lo describe como un recurso educativo — un patrón que puedes entender en una tarde y adaptar a tus propias necesidades. No hay estado persistente, no hay memoria integrada, no hay características de producción. El valor está en la idea: los traspasos son una primitiva limpia y mínima para la coordinación multiagente, y puedes implementar el mismo patrón en unas pocas docenas de líneas de código sin ningún framework.

El enfoque de Anthropic con Claude es diferente. En lugar de construir un framework multiagente, Claude Code usa un agente orquestador único que puede generar sub-agentes para tareas paralelas. El orquestador maneja la conversación con el usuario, hace planes de alto nivel, y delega piezas específicas de trabajo a sub-agentes que se ejecutan independientemente y devuelven sus resultados. Esto es estructuralmente similar al patrón jerárquico de gerente/trabajador, pero con una diferencia clave: los sub-agentes se crean dinámicamente según lo que requiere la tarea, en lugar de estar predefinidos. Si el orquestador decide que necesita buscar tres archivos en paralelo, genera tres sub-agentes sobre la marcha. Si no necesita trabajo paralelo, simplemente hace todo él mismo.

Este diseño refleja una perspectiva práctica: para la mayoría de las tareas, un solo agente capaz con buenas herramientas es suficiente. La coordinación multiagente añade latencia (los agentes necesitan comunicarse), costo de tokens (cada agente tiene su propio contexto), y modos de fallo (¿qué pasa si un agente malinterpreta la salida de otro?). Generar sub-agentes solo cuando es necesario mantiene el caso común simple y rápido mientras preserva la capacidad de paralelizar cuando genuinamente ayuda.

Cuándo usar multi-agente vs agente único

Con todos estos frameworks y patrones disponibles, es tentador recurrir a sistemas multiagente por defecto. Pero la respuesta honesta es: la mayoría de las tareas para las que la gente construye sistemas multiagente pueden hacerse con un solo agente bien configurado con buenas herramientas . Multiagente añade complejidad — más partes móviles, más modos de fallo, más esfuerzo de depuración — y esa complejidad necesita justificarse con un beneficio concreto.

Un agente único es la elección correcta cuando la tarea está bien definida, el número de herramientas es manejable (digamos, menos de 15-20), y el contexto completo de la tarea cabe en una ventana de contexto. ¿Un asistente de codificación que lee archivos, escribe código y ejecuta pruebas? Un agente. ¿Un bot de soporte al cliente que busca pedidos y procesa devoluciones? Un agente. ¿Un asistente de investigación que busca en la web y resume resultados? Probablemente un agente.

Multiagente se vuelve genuinamente valioso en cuatro situaciones:

  • Paralelismo genuino: la tarea involucra múltiples subtareas independientes que pueden ejecutarse simultáneamente. Buscar en cinco bases de datos, procesar cinco documentos o generar cinco alternativas — estas se benefician de agentes paralelos porque el tiempo real es el tiempo del agente más lento, no la suma.
  • Contextos de seguridad diferentes: agentes que necesitan permisos diferentes no deberían compartir el mismo conjunto de herramientas. Un agente que lee datos de clientes no debería tener acceso al pipeline de despliegue. Un agente que escribe código no debería tener acceso a la base de datos de producción. Agentes separados con conjuntos de herramientas separados imponen estos límites de forma natural.
  • Desbordamiento de contexto: cuando el historial de conversación, los resultados de herramientas y el contexto de la tarea genuinamente exceden lo que un agente puede rastrear. Una refactorización compleja de base de código podría involucrar cientos de archivos — un orquestador que delega a trabajadores a nivel de archivo mantiene manejable el contexto de cada trabajador.
  • Verificación adversaria: cuando necesitas que un agente revise el trabajo de otro. Un agente que escribe código y un agente que revisa código con prompts de sistema diferentes detectan clases diferentes de errores. Un agente de propuesta y un agente de crítica convergen en mejores respuestas que las que cualquiera produciría solo.

Si ninguno de estos aplica, empieza con un solo agente. Siempre puedes descomponerlo en múltiples agentes después si el agente único se topa con una pared. Lo inverso — simplificar un sistema multiagente de vuelta a un solo agente — es mucho más difícil porque ya has construido la infraestructura de coordinación.

💡 El campo todavía está descubriendo cuándo multiagente es genuinamente mejor versus cuándo es complejidad innecesaria. Una heurística útil: si puedes describir tu sistema multiagente como "El Agente A hace X, luego el Agente B hace Y", pregúntate si un solo agente con la instrucción "Primero haz X, luego haz Y" funcionaría igual de bien. A menudo así es.

Quiz

Pon a prueba tu comprensión de los sistemas multiagente, patrones de orquestación y cuándo usarlos.

En el patrón de orquestación jerárquico (gerente/trabajador), ¿cuál es la responsabilidad principal del agente gerente?

En el framework Swarm de OpenAI, ¿cómo se implementa un traspaso entre agentes?

¿Cuál de las siguientes NO es una razón fuerte para usar multiagente en lugar de agente único?

¿Qué distingue el enfoque de LangGraph para la orquestación multiagente del de CrewAI?