Un sistema multi-agente sin orquestador es solo una colección de agentes. Cada uno es capaz, pero ninguno está coordinado. Pueden ser excelentes en sus tareas individuales — buscar en la web, escribir código, llamar APIs — pero sin algo que decida qué se hace, en qué orden, quién lo hace y qué hacer cuando un resultado sale mal, el sistema no se comporta como un sistema. Se comporta como un trabajo grupal sin líder de proyecto.
El orquestador es ese líder de proyecto. Su trabajo no es hacer el trabajo: es asegurarse de que el trabajo se haga. Y eso es un problema más difícil y sutil de lo que parece.
Qué hace un orquestador
Un orquestador hace cuatro cosas, y solo estas cuatro:
1. Descomponer el objetivo. Convertir una meta de alto nivel en un conjunto concreto de subtareas. Esto es un problema de planificación, no de ejecución. El orquestador decide qué debe pasar, no cómo hacerlo.
2. Enrutar tareas a los trabajadores correctos. Emparejar cada subtarea con un agente capaz de hacerla. Esto requiere conocer las herramientas y capacidades de cada trabajador — no en detalle, pero sí lo suficiente para delegar correctamente.
3. Gestionar el estado del flujo de trabajo. A medida que los trabajadores devuelven resultados, el orquestador decide qué significan para el plan restante. A veces un resultado cambia el plan por completo. A veces confirma el siguiente paso. El orquestador tiene la visión completa.
4. Sintetizar la salida final. Los resultados de los trabajadores son parciales. El orquestador los ensambla en una respuesta coherente y decide cuándo se ha cumplido el objetivo.
Nota lo que está ausente: el orquestador no llama APIs, no ejecuta código, no busca en la web. Razona sobre el trabajo y lo enruta. El momento en que un orquestador empieza a ejecutar, pierde el foco que lo hace bueno en la coordinación.
Construyendo uno desde cero
Aquí tienes un orquestador mínimo en Python. Planifica por adelantado, delega en trabajadores tipados y sintetiza los resultados:
import json
def orchestrator(goal: str, workers: dict) -> str:
# Paso 1: planificar
plan_prompt = f"""
Goal: {goal}
Available workers: {list(workers.keys())}
Return a JSON array of steps: [{{"task": "...", "worker": "..."}}]
Return JSON only, no explanation.
"""
plan = json.loads(llm(plan_prompt))
# Paso 2: ejecutar cada paso, recopilar resultados
results = []
for step in plan:
worker = workers[step["worker"]]
result = worker.run(step["task"])
results.append({"task": step["task"], "result": result})
# Paso 3: sintetizar
synthesis_prompt = f"""
Original goal: {goal}
Worker results: {json.dumps(results, indent=2)}
Synthesise a final answer from these results.
"""
return llm(synthesis_prompt)
Hay tres cosas de este fragmento que vale la pena analizar.
El orquestador llama a llm() dos veces — una para planificar y otra para sintetizar — pero nunca para ejecutar. La ejecución está completamente delegada. Si te encuentras agregando una llamada a una herramienta directamente dentro del bucle del orquestador, detente y pregúntate si un trabajador debería ser el dueño de eso.
El plan es un objeto de primera clase — una lista de pasos tipados, no una cadena de pensamiento implícita. Esto significa que puedes inspeccionarlo, registrarlo, reproducirlo y replanificar desde cualquier punto cuando algo sale mal.
Los resultados se conservan verbatim antes de la síntesis. El orquestador no resume temprano. Le da al paso de síntesis la imagen completa, dejando que el modelo decida qué es relevante. El resumen prematuro es donde se pierde contexto.
Replanificar cuando la realidad diverge
Un plan fijo falla en el momento en que un trabajador devuelve un resultado inesperado. Un orquestador real necesita decidir: ¿esto cambia el plan restante?
def orchestrator_with_replan(goal: str, workers: dict) -> str:
plan = initial_plan(goal, workers)
completed = []
while plan:
step = plan.pop(0)
result = workers[step["worker"]].run(step["task"])
completed.append({"task": step["task"], "result": result})
# verificar si el resultado justifica replanificar
replan_prompt = f"""
Remaining plan: {plan}
Latest result: {result}
Does this result change what should happen next?
If yes, return a revised plan as JSON. If no, return null.
"""
revision = llm(replan_prompt)
if revision and revision != "null":
plan = json.loads(revision)
return synthesise(goal, completed)
Este es el patrón planificar-y-ejecutar hecho concreto. El orquestador trabaja desde una lista de verificación pero revisa después de cada resultado si esa lista sigue teniendo sentido. El bucle termina cuando el plan está vacío, no cuando se ha ejecutado un número fijo de pasos.
Qué aportan los frameworks
Escribir un orquestador desde cero te da control y comprensión, pero los casos de uso en producción introducen problemas que el fragmento anterior no maneja: estado que necesita persistir entre reinicios, flujos que necesitan ramificaciones y bucles, y depuración cuando algo falla tres niveles adentro. Ahí entran los frameworks.
LangGraph
LangGraph representa un flujo de trabajo como un grafo dirigido — los nodos son agentes o funciones, las aristas son transiciones entre ellos, y un StateGraph centralizado mantiene el estado compartido en toda la ejecución.
from langgraph.graph import StateGraph, END
from typing import TypedDict
class WorkflowState(TypedDict):
goal: str
plan: list
results: dict
final_answer: str
graph = StateGraph(WorkflowState)
graph.add_node("planner", planner_node)
graph.add_node("researcher", researcher_node)
graph.add_node("coder", coder_node)
graph.add_node("synthesiser", synthesiser_node)
graph.add_conditional_edges(
"planner",
route_to_worker, # función que lee el estado y elige el siguiente nodo
{"research": "researcher", "code": "coder"}
)
graph.add_edge("researcher", "synthesiser")
graph.add_edge("coder", "synthesiser")
graph.add_edge("synthesiser", END)
graph.set_entry_point("planner")
app = graph.compile()
El modelo basado en grafos justifica su complejidad porque hace explícitas las ramificaciones y los ciclos. La arista condicional de arriba no está escondida dentro de un prompt — es código. Puedes leer el grafo y saber exactamente qué rutas existen. LangGraph también trae checkpointers que persisten el estado en disco o en una base de datos, de modo que un flujo que falla a mitad de camino puede reanudarse desde el último checkpoint en lugar de empezar de cero.
El tradeoff honesto: un flujo simple que tomaría 40 líneas en Python puro toma cerca de 120 en LangGraph. Pagas en boilerplate. Recibes auditabilidad, reanudabilidad y control de flujo explícito.
AutoGen
AutoGen toma un enfoque diferente. En lugar de un grafo, modela la orquestación como paso de mensajes entre agentes. Un manager de GroupChat decide qué agente habla a continuación según el contexto, y los agentes transmiten sus respuestas para que todos compartan el mismo historial de conversación.
from autogen import AssistantAgent, UserProxyAgent, GroupChat, GroupChatManager
planner = AssistantAgent("planner", system_message="Break goals into tasks.")
researcher = AssistantAgent("researcher", system_message="Search and retrieve facts.")
coder = AssistantAgent("coder", system_message="Write and run Python code.")
critic = AssistantAgent("critic", system_message="Review outputs for errors.")
group_chat = GroupChat(
agents=[planner, researcher, coder, critic],
messages=[],
max_round=12
)
manager = GroupChatManager(groupchat=group_chat)
planner.initiate_chat(manager, message="Build a data pipeline for X.")
El GroupChatManager es el orquestador aquí. AutoGen v0.4 reconstruyó esto alrededor de un modelo de actores donde cada agente corre de forma independiente y se comunica mediante mensajes tipados — un diseño más limpio para flujos verdaderamente concurrentes.
La transmisión de contexto completo es la apuesta arquitectónica clave de AutoGen: cada agente ve toda la conversación, lo que permite que agentes especialistas detecten problemas antes en la cadena. El costo es que la ventana de contexto se llena más rápido en flujos largos.
CrewAI
CrewAI usa un modelo basado en roles. Defines un equipo de agentes con roles nombrados, asignas tareas y dejas que el framework maneje la delegación. Es config-first en lugar de code-first — la mayor parte del setup vive en YAML:
from crewai import Agent, Task, Crew, Process
researcher = Agent(role="Researcher", goal="Find accurate information",
backstory="Expert at web research", tools=[search_tool])
writer = Agent(role="Writer", goal="Produce clear summaries",
backstory="Experienced technical writer")
research_task = Task(description="Research topic X", agent=researcher)
write_task = Task(description="Write a summary of the research", agent=writer)
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, write_task],
process=Process.sequential
)
crew.kickoff()
Process.hierarchical cambia a un modelo de orquestador donde un agente manager enruta tareas dinámicamente en lugar de seguir una secuencia fija. CrewAI es el camino más rápido de idea a prototipo funcionando, pero los equipos reportan consistentemente tocar su techo a los 6–12 meses cuando los flujos crecen más allá de patrones secuenciales o jerárquicos simples — momento en el que suele seguir una migración a LangGraph.
La guía más honesta: empieza desde cero para entender el patrón, y luego adopta un framework cuando necesites lo que ofrece específicamente — no porque los frameworks sean el default. LangGraph es el default de producción actual para equipos que necesitan flujos largos, reanudables y con control de flujo explícito. CrewAI es la elección correcta cuando necesitas algo funcionando esta semana y puedes aceptar el techo. AutoGen es más fuerte cuando los agentes realmente necesitan debatir y revisar en lugar de ejecutar un plan fijo.
Los modos de falla del orquestador
El orquestador es el punto único de falla en un sistema multi-agente, lo que significa que sus modos de falla son costosos.
Sobre-delegación. El orquestador envía una tarea a un trabajador que está subespecificada. El trabajador devuelve basura. El orquestador sintetiza la basura. Construye esquemas de tareas — descripciones tipadas de lo que un trabajador espera — y valídalos antes del despacho, no después.
Rigidez del plan. Un plan inicial que no se reevalúa cuando los resultados divergen ejecutará con confianza hacia una respuesta equivocada. Incorpora una revisión de replanificación después de cualquier resultado que introduzca información nueva.
Fallos silenciosos de trabajadores. Los trabajadores que fallan silenciosamente devuelven None o una cadena vacía, que el orquestador puede sintetizar como si fuera salida real. Los trabajadores deberían fallar ruidosamente con errores tipados que el orquestador pueda inspeccionar y enrutar alrededor.
Sobrecarga de contexto. El orquestador acumula resultados de cada trabajador. En un flujo largo, su contexto se llena con salidas crudas. Pasa resúmenes, no transcripciones completas, a menos que el contenido crudo sea genuinamente necesario para la síntesis.
El valor del orquestador es su ignorancia deliberada de los detalles. No sabe cómo buscar en la web, ejecutar código o llamar una API. Sabe qué necesita hacerse y quién debería hacerlo. Esa separación — razonar sobre el trabajo versus hacer el trabajo — es lo que permite que los sistemas multi-agente escalen más allá de lo que cualquier agente individual podría manejar solo.
El bucle sigue siendo el corazón. El orquestador es el cerebro que decide cuántos bucles iniciar, qué deberían hacer y cuándo detenerse.
“El orquestador no llama APIs, no ejecuta código, no busca en la web. Razona sobre el trabajo y lo enruta.”
Sigue explorando estos temas en el blog de DojoFullStack: si te interesa la orquestación de agentes, la coordinación multi-agente y la automatización con IA, tenemos más guías prácticas para desarrolladores y emprendedores LATAM.