Arquitecturas Multi-Agente en 2026: Router, Pipeline y Blackboard con Código Real
TL;DR
- Router + Workers: un despachador elige qué agente ejecuta la tarea. Ideal para tareas heterogéneas con dominios distintos.
- Pipeline: los agentes se ejecutan en secuencia, cada uno transforma o enriquece el output del anterior. Perfecto para flujos lineales con pasos bien definidos.
- Blackboard: los agentes comparten un espacio de estado y contribuyen cuando pueden. El patrón más flexible pero también el más difícil de debuggear.
- No existe un “patrón ganador”. La elección depende de la variabilidad de entrada, la necesidad de orden, y cuánto state compartido necesitas.
Contexto
Si ya tienes un agente single-loop funcionando en producción (ReAct, tool calling, el básico), el siguiente salto natural es multi-agente: dividir el trabajo entre varios LLMs especializados.
El problema no es poner varios agentes a hablar. El problema es orquestarlos sin que se vuelva un caos de llamadas recursivas y state inconsistente.
En 2026, LangGraph domina en producción, CrewAI manda en prototipado rápido, y AutoGen sigue siendo el favorito de academia. Pero independientemente del framework, todos implementan variaciones de tres patrones fundamentales. Entenderlos te permite elegir framework (o escribir el tuyo) con criterio.
Patrón 1: Router + Workers
Un agente central (router) recibe la petición del usuario, decide qué worker es el más adecuado, y le delega la tarea completa. El worker devuelve el resultado, y el router lo entrega al usuario.
from dataclasses import dataclass
from typing import Literal
@dataclass
class Message:
role: str
content: str
class Router:
"""Despacha tareas al agente especializado correcto."""
def __init__(self, workers: dict):
self.workers = workers # {"code": CodeAgent(), "research": ResearchAgent(), ...}
def classify(self, user_input: str) -> str:
# Un LLM decide qué worker usar
prompt = f"""Eres un router. Clasifica esta petición en una categoría:
- code: tareas de programación, debugging, code review
- research: búsqueda, análisis, comparación
- writing: redacción, corrección, traducción
Petición: {user_input}
Responde SOLO con la categoría."""
# LLM call aquí — devuelve "code", "research" o "writing"
return llm_call(prompt).strip().lower()
def run(self, user_input: str) -> str:
category = self.classify(user_input)
worker = self.workers.get(category)
if not worker:
return "No tengo un agente para esa tarea."
return worker.execute(user_input)
class CodeAgent:
def execute(self, task: str) -> str:
return llm_call(f"Eres un experto programador. Resuelve: {task}")
class ResearchAgent:
def execute(self, task: str) -> str:
return llm_call(f"Eres un investigador. Analiza: {task}")
Cuándo funciona
- Tareas mutuamente excluyentes: no necesitas que dos agentes colaboren en la misma tarea.
- Poca state compartida: cada worker opera de forma independiente.
- Quieres observabilidad simple: una petición → un worker → una respuesta.
Cuándo falla
- La tarea necesita múltiples agentes en secuencia (ej.: investigar → escribir → revisar).
- El router malclasifica con frecuencia → necesitas un fallback o retry logic.
- Workers con capacidades solapadas → el router no sabe a cuál enviar.
Frameworks que lo implementan bien
- OpenAI Swarm: literalmente es este patrón, nada más. Handoffs entre agentes.
- LangGraph: con un nodo supervisor que enruta a nodos worker.
- Google ADK: el agente “coordinador” nativo hace de router.
Patrón 2: Pipeline (Sequential)
Los agentes se ejecutan en orden fijo. Cada uno recibe el output del anterior, lo transforma o enriquece, y pasa el resultado al siguiente.
from typing import Any
import json
class Pipeline:
"""Ejecuta agentes en secuencia, pasando state entre ellos."""
def __init__(self, steps: list):
self.steps = steps # [DraftAgent(), ReviewAgent(), PolishAgent()]
def run(self, initial_input: str) -> dict:
state = {"input": initial_input, "draft": None, "review": None, "final": None}
for step in self.steps:
state = step.execute(state)
return state
class DraftAgent:
def execute(self, state: dict) -> dict:
prompt = f"Escribe un borrador técnico sobre: {state['input']}"
state["draft"] = llm_call(prompt)
return state
class ReviewAgent:
def execute(self, state: dict) -> dict:
prompt = f"""Revisa este texto y señala problemas:
- Error técnico
- Falta de evidencia
- Tono inapropiado
Texto: {state['draft']}"""
state["review"] = llm_call(prompt)
return state
class PolishAgent:
def execute(self, state: dict) -> dict:
prompt = f"""Reescribe este texto aplicando las correcciones:
Texto original: {state['draft']}
Correcciones: {state['review']}"""
state["final"] = llm_call(prompt)
return state
# Uso
pipeline = Pipeline([DraftAgent(), ReviewAgent(), PolishAgent()])
result = pipeline.run("Arquitecturas multi-agente en 2026")
print(result["final"])
Cuándo funciona
- Flujos deterministas donde el orden importa y es predecible.
- Cada paso agrega valor tangible al output del anterior.
- Necesitas auditabilidad: puedes inspeccionar el state en cada punto del pipeline.
Cuándo falla
- Un paso falla → todo el pipeline se para. Necesitas retry/timeout por paso.
- No hay branching: si la tarea necesita caminos distintos según condiciones, un pipeline plano no basta.
- Latencia acumulativa: N llamadas a LLM en secuencia, cada una con sus segundos.
Frameworks que lo implementan bien
- LangGraph: con
add_node+add_edgelineales. - CrewAI:
Taskobjects encadenados concontext=[]. - Cualquier framework: es el patrón más simple de implementar.
Patrón 3: Blackboard (Shared State)
Un espacio de estado compartido (el “blackboard”) que todos los agentes pueden leer y escribir. Un controller decide qué agentes se ejecutan, en qué orden, y cuándo parar.
Es el patrón más potente y el más peligroso.
from typing import Callable
import time
class Blackboard:
"""Estado compartido entre agentes."""
def __init__(self):
self.data = {}
self.log = []
def write(self, key: str, value: Any, agent_name: str):
self.data[key] = value
self.log.append({"agent": agent_name, "key": key, "time": time.time()})
def read(self, key: str) -> Any:
return self.data.get(key)
def snapshot(self) -> dict:
return dict(self.data)
class BlackboardController:
"""Decide qué agentes ejecutar según el estado actual."""
def __init__(self, agents: list, blackboard: Blackboard, max_rounds: int = 5):
self.agents = agents
self.bb = blackboard
self.max_rounds = max_rounds
def is_done(self) -> bool:
return self.bb.read("final_answer") is not None
def run(self, question: str):
self.bb.write("question", question, "user")
for round in range(self.max_rounds):
if self.is_done():
break
for agent in self.agents:
if agent.can_contribute(self.bb):
agent.execute(self.bb)
return self.bb.read("final_answer")
class ResearchAgent:
name = "researcher"
def can_contribute(self, bb: Blackboard) -> bool:
return bb.read("question") and not bb.read("research_done")
def execute(self, bb: Blackboard):
q = bb.read("question")
research = llm_call(f"Investiga y proporciona datos sobre: {q}")
bb.write("research_data", research, self.name)
bb.write("research_done", True, self.name)
class AnalystAgent:
name = "analyst"
def can_contribute(self, bb: Blackboard) -> bool:
return bb.read("research_done") and not bb.read("analysis_done")
def execute(self, bb: Blackboard):
data = bb.read("research_data")
analysis = llm_call(f"Analiza estos datos y saca conclusiones: {data}")
bb.write("analysis", analysis, self.name)
bb.write("analysis_done", True, self.name)
class WriterAgent:
name = "writer"
def can_contribute(self, bb: Blackboard) -> bool:
return bb.read("analysis_done") and not bb.read("final_answer")
def execute(self, bb: Blackboard):
analysis = bb.read("analysis")
answer = llm_call(f"Escribe una respuesta clara basada en: {analysis}")
bb.write("final_answer", answer, self.name)
# Uso
bb = Blackboard()
agents = [ResearchAgent(), AnalystAgent(), WriterAgent()]
controller = BlackboardController(agents, bb)
result = controller.run("¿Qué patrón multi-agente usar en producción?")
Cuándo funciona
- Problemas complejos donde múltiples perspectivas enriquecen la solución.
- No sabes de antemano qué agentes van a contribuir (el
can_contributelo decide dinámicamente). - Necesitas extensibilidad: agregar un agente nuevo es tan simple como añadirlo a la lista.
Cuándo falla
- Debuggear es un infierno: el state muta en orden no determinista. Dos ejecuciones pueden dar resultados distintos.
- Agents que sobreescriben la key de otro → conflictos de state.
- Sin un
max_roundsrobusto, puede entrar en bucles infinitos. - Requiere más ingeniería: el controller, las condiciones de contribución, la resolución de conflictos.
Frameworks que lo implementan bien
- LangGraph: con
StateGraphy nodos que leen/escriben un state compartido. Es el más cercano a este patrón en producción. - AutoGen: el patrón de “group chat” donde los agentes contribuyen a una conversación compartida.
- Ningún framework lo hace trivial: el controller y la lógica de
can_contributecasi siempre es código custom.
Tabla comparativa
| Criterio | Router + Workers | Pipeline | Blackboard |
|---|---|---|---|
| Complejidad | Baja | Baja | Alta |
| Flexibilidad | Media (solo branching) | Baja (lineal) | Alta (dinámico) |
| Debugabilidad | Alta | Alta | Baja |
| Latencia | 1 LLM (router) + 1 LLM (worker) | N LLMs en secuencia | Variable, depende de rounds |
| State compartido | No | Sí, secuencial | Sí, concurrente |
| Mejor para | Tareas independientes | Flujos deterministas | Problemas complejos/abiertos |
| Error handling | Retry al worker | Retry por paso | Requiere controller robusto |
Cómo elegir en la práctica
Empieza con Router si:
- Tienes 3+ tipos de tareas distintas y un LLM las puede clasificar bien.
- No necesitas state entre agentes.
Usa Pipeline si:
- El flujo es lineal y predecible (draft → review → publish).
- Quieres poder inspeccionar el output de cada paso.
Solo usa Blackboard si:
- El problema es genuinamente complejo y los agentes necesitan compartir state rico.
- Tienes tiempo para implementar un controller sólido con logging exhaustivo.
- Has probado Router y Pipeline y se te quedan cortos.
Regla práctica: si no puedes explicar por qué necesitas Blackboard en una frase, probablemente no lo necesitas.
El anti-patrón: agentes hablando sin control
Lo que NO deberías hacer:
# ❌ Esto parece multi-agente pero es un agente recursivo
# que se llama a sí mismo hasta que decide parar.
def agent_loop(task):
result = llm_call(f"Resuelve: {task}")
if "NEEDS_HELP" in result:
return agent_loop(result) # recursión sin control
return result
Este patrón (llamar al LLM en bucle hasta que “decida” parar) es tentador porque es simple. Pero:
- No hay límite de iteraciones por defecto.
- El LLM puede entrar en loops sutiles (reformular la misma petición 20 veces).
- El coste de tokens escala linealmente con cada iteración.
- No hay observabilidad: no sabes qué pasó en la iteración 7.
Si vas a hacer algo parecido, como mínimo: añade max_iterations, logea cada paso, y define un criterio de parada explícito.
Fuentes
- LangGraph: Multi-agent patterns — documentación oficial de patrones multi-agente.
- Microsoft AutoGen: Agent patterns — patrones conversacionales y de grupo.
- Presenc AI: Multi-Agent Orchestration Frameworks 2026 — comparativa honesta de frameworks.
- OpenAI Swarm — implementación minimal del patrón router+handoff.