Módulo 4: Fan-out paralelo y agregación
Módulo 4: Fan-out paralelo y agregación
Descripción
El Módulo 3 construyó un pipeline: tres etapas en un orden fijo, donde la salida de una es la entrada obligatoria de la siguiente. Ese orden fijo se justifica cuando las etapas dependen genuinamente entre sí —no puedes confirmar una reserva sin haber cotizado antes—. Pero la lección 07 de ese módulo dejó una pregunta pendiente, señalada a propósito: ¿qué pasa cuando dos partes de una tarea no dependen una de la otra? Si un socio te pide "cotiza Focus pro 3h y dime la política de cancelación", nada en la segunda mitad de esa frase necesita el resultado de la primera — son dos preguntas distintas, que un pipeline encadenaría sin necesidad, gastando una etapa detrás de la otra cuando podrían resolverse a la vez.
Este módulo construye el patrón que resuelve exactamente eso: fan-out paralelo. Varias
sub-tareas genuinamente independientes se reparten —entre varios agentes, o dentro de un mismo
agente— y sus resultados se agregan al final, en una sola respuesta. Vas a construir el
mecanismo que reconoce cuándo dos sub-tareas son independientes, el que las despacha en un orden
fijo enumerado como caso base, la extensión con concurrencia real (ThreadPoolExecutor) que
respeta el mismo determinismo de salida que ya exige toda la guía, y la medición —contando
rondas, no reloj real— de cuánto ahorra correr en paralelo frente a correr en secuencia.
Regla dura de este módulo (léela antes de seguir)
Se mantiene la misma línea de los Módulos 1 a 3: la decisión de cada agente —qué tool llamar, qué
responder— no se ejecuta. Sigue siendo un guion escrito a mano, rotulado como concepto
(claude-sonnet-5). Lo que sí se ejecuta, de verdad, con Python 3.14 y su librería estándar, es
la orquestación completa del fan-out: el reconocimiento de qué sub-tareas son independientes, el
despacho —secuencial primero, después con concurrent.futures.ThreadPoolExecutor—, la agregación de
resultados, y el conteo del costo de coordinación. Una regla nueva, específica de este módulo:
la salida impresa NUNCA depende del orden en que terminan los hilos. Los resultados se
acumulan en un diccionario keyed por nombre de agente, y se ordenan (alfabéticamente, por
nombre de agente) antes de imprimir o agregar cualquier cosa. En ningún momento de este módulo se
afirma "el agente X terminó primero" — eso no es determinista, y esta guía no construye nada que
dependa de algo que no lo es.
Dónde estamos en el ecosistema
agent-fundamentals-and-tool-calling-guide (ya completa)
-> construyó UN agente: tools, protocolo, el bucle, multi-tool, memoria, robustez
multi-agent-orchestration-guide (esta guía)
├── Módulo 1: Por qué multi-agente (y cuándo no) (completo)
│ → El criterio de decisión, medido con números reales
├── Módulo 2: El patrón supervisor/router (completo)
│ → Ruteo determinista vs. ruteo por decisión del modelo
├── Módulo 3: Pipelines secuenciales (completo)
│ → El segundo patrón: orden fijo, sin ninguna decisión de ruteo
├── Módulo 4: Fan-out paralelo y agregación ← ESTÁS AQUÍ
│ → El tercer patrón: sub-tareas independientes, repartidas y agregadas
├── Módulo 5: Handoff y delegación
├── Módulo 6: Estado compartido y el patrón blackboard
├── Módulo 7: Orquestando el sistema completo de Reservo
└── Módulo 8: Proyecto — el sistema multi-agente de Reservo
Este módulo no re-explica el patrón supervisor (M2) ni el pipeline (M3) — los reusa como el contraste que le da sentido al fan-out: un supervisor decide a quién delegar; un pipeline encadena sin decidir, en un orden fijo; el fan-out de este módulo reparte trabajo genuinamente independiente y lo agrega, sin que ninguna sub-tarea espere a otra.
Analogía: dos mandados que no dependen entre sí
Piensa en pedirle a alguien dos favores en la misma frase: "¿me compras pan y me revisas el correo?". Ninguno de los dos mandados necesita que el otro termine primero — comprar pan no depende de revisar el correo, ni al revés. Si la persona hiciera los dos en secuencia —primero todo el viaje a la panadería, después, recién ahí, sentarse a revisar el correo— tardaría la suma de los dos tiempos. Si en cambio los reparte —manda a alguien más a comprar el pan mientras ella misma revisa el correo— el tiempo total es el del mandado más largo, no la suma de los dos.
Esa es la diferencia completa entre un pipeline (M3) y el fan-out de este módulo. El pipeline es la receta de la lección 01 del Módulo 3: pasos que sí dependen entre sí, en un orden que no se puede alterar. El fan-out es la lista de mandados que no dependen entre sí: el orden en que se reparten no cambia el resultado, y repartirlos en vez de encadenarlos ahorra tiempo real.
El caso que acompaña el módulo: la petición compuesta de Reservo
Los Módulos 2 y 3 dejaron tres especialistas terminados y probados: booking_agent (cotizar,
reservar, cancelar), policy_agent (search_docs, el stub de política), y pricing_agent
(comparar precios). Este módulo no agrega ningún especialista nuevo — reusa exactamente los
mismos tres. Lo que agrega es una forma distinta de repartirles trabajo:
Petición compuesta: "Cotiza Focus pro 3h y dime la política de cancelación."
Sub-tarea A -> booking_agent : "Cotiza Focus pro 3h."
Sub-tarea B -> policy_agent : "¿Cuál es la política de cancelación?"
Ninguna de las dos necesita el resultado de la otra.
A diferencia del pipeline de Reservo (cotizar → validar política → confirmar, donde la etapa 3
literalmente necesita el price_cents de la etapa 1), acá las dos sub-tareas son preguntas
completamente separadas que, por coincidencia de redacción, llegaron en la misma frase. El módulo
construye, en orden:
- Cómo reconocer que dos sub-tareas son independientes (lección 02).
- El caso base: despacharlas en un orden fijo, una después de la otra (lección 03).
- Cómo agregar sus resultados de forma determinista (lección 04).
- La variante donde el fan-out ocurre dentro de un solo agente —
pricing_agentcomparando tres salas— contrastada con el fan-out entre agentes que construyen las lecciones 03-04 (lección 05). - La extensión con concurrencia real,
ThreadPoolExecutor, sin perder el determinismo de salida (lección 06). - La medición del ahorro, contando rondas de coordinación, no reloj real (lección 07).
Frontera con lo que ya viste (y con lo que viene)
Con el supervisor (M2) y el pipeline (M3) frescos, la distinción de fondo queda completa:
- Un supervisor decide QUIÉN trabaja. Cada petición se lee y se rutea a un especialista —uno solo, normalmente—. El Módulo 2 entero mide el costo de esa decisión.
- Un pipeline fija el ORDEN, sin decidir nada. La secuencia de etapas está escrita antes de que exista ninguna petición concreta, y cada etapa depende genuinamente de la anterior.
- Un fan-out reparte trabajo SIMULTÁNEO entre sub-tareas que ya sabes que son independientes. No hay ningún orden que respetar entre ellas —el Módulo 3, lección 07, ya lo señaló sobre el propio pipeline de Reservo, sin construirlo—; este módulo lo construye.
Y hacia adelante, una distinción que este módulo deja pendiente a propósito: cuando el propio agente que ya está trabajando decide, a mitad de la tarea, transferirle el control a otro especialista —sin que ningún coordinador externo lo haya decidido desde el principio—, eso no es fan-out. Es handoff, y lo construye el Módulo 5. La diferencia importa: acá, todas las sub-tareas se identifican antes de que ningún agente empiece a trabajar; en el handoff, la necesidad de otro especialista aparece durante el trabajo de uno que ya está en curso.
Prerequisitos
Conocimiento requerido:
- ✅ Módulo 1 completo de esta guía: el criterio de decisión y el costo de coordinar, medido.
- ✅ Módulo 2 completo:
SPECIALISTS,run_specialist, los tres especialistas de Reservo. - ✅ Módulo 3 completo: la distinción entre "decidir" (supervisor) y "encadenar sin decidir" (pipeline) — la base sobre la que este módulo agrega "repartir sin decidir orden".
- ✅
agent-fundamentals-and-tool-calling-guide: el protocolotool_use/tool_result,run_agent_parallel,dispatch_parallel, y tool calls en paralelo dentro de un agente (Módulo 5 de esa guía) — la base técnica que este módulo escala a agentes completos.
Recomendado:
- ✅ Haber corrido tú mismo el ejemplo de
pricing_agentdel Módulo 2, lección 05 — este módulo lo retoma en la lección 05 para contrastarlo, sin cambiarle una línea.
NO requerido:
- ❌ No necesitas una API key ni conexión a internet: la decisión de cada agente sigue siendo concepto, escrita a mano.
- ❌ No necesitas medir tiempo real de reloj — este módulo mide "rondas" de coordinación, un conteo
discreto, nunca
time.time().
Entorno:
- ✅ Python 3.14.0 con su librería estándar (
dataclasses,concurrent.futures). Nada que instalar.
Roadmap del módulo
Lección 01 — Introducción al módulo (esta)
El patrón fan-out, la analogía de los dos mandados, y la frontera con pipeline (M3) y handoff (M5).
Lección 02 — Identificando sub-tareas independientes
Qué hace que dos sub-tareas sean genuinamente independientes —ejecutado: correrlas en dos órdenes distintos y confirmar que el resultado no cambia—, contrastado con la dependencia real de un pipeline.
Lección 03 — El caso base: orden fijo enumerado
SubTask, el mecanismo de reconocer sub-tareas (concepto, con extracción ejecutada), y
run_fanout_sequential: el despacho en un orden fijo y determinista, sin concurrencia todavía.
Lección 04 — Agregando resultados de forma determinista
Historial completo por sub-tarea, la síntesis final (concepto) que combina ambos resultados, y el conteo del costo de coordinación del caso base.
Lección 05 — Fan-out dentro de un solo agente: la variante con pricing_agent
pricing_agent comparando tres salas — fan-out dentro de un turno, ya construido en el Módulo
2 — contrastado explícitamente con el fan-out entre agentes de las lecciones 03-04.
Lección 06 — Concurrencia real con ThreadPoolExecutor
La extensión: cada sub-tarea corre en su propio hilo. La salida agregada nunca depende de qué hilo terminó primero — se ejecuta varias veces para probarlo.
Lección 07 — Midiendo el ahorro de latencia en rondas
La pieza que cierra el módulo: cuántas rondas de coordinación ahorra correr en paralelo frente a correr en secuencia, con una fórmula que generaliza a cualquier número de sub-tareas.
Lección 08 — Mini-proyecto: fan-out en Reservo
Tres escenarios nuevos —dos sub-tareas, tres sub-tareas, y un caso que NO es fan-out entre agentes— corridos de punta a punta, con su costo citado.
Mapa de progresión
Lección 01 (esta) → El patrón, la analogía, la frontera con M3/M5
Lección 02 → Qué hace independientes a dos sub-tareas, ejecutado
Lección 03 → El caso base: SubTask, run_fanout_sequential
Lección 04 → Agregación determinista, costo del caso base
Lección 05 → La variante dentro de un agente, contrastada
Lección 06 → Concurrencia real, determinismo bajo hilos reales
Lección 07 → El ahorro medido en rondas, no en reloj
Lección 08 → Proyecto: fan-out de Reservo, tres escenarios
Dificultad: ⭐⭐ ──────────────────▶ ⭐⭐⭐
Qué lograrás en este módulo
Al completar las 8 lecciones, podrás:
- Reconocer cuándo dos sub-tareas son genuinamente independientes —y confirmarlo ejecutando, no solo intuyendo—, distinguiéndolo de una dependencia real como la de un pipeline.
- Construir
SubTaskyrun_fanout_sequential, el caso base con orden fijo enumerado. - Agregar resultados de varios agentes de forma determinista, con una síntesis final que combina lo que cada uno produjo.
- Distinguir fan-out DENTRO de un agente (tool calls en el mismo turno, ya construido en
agent-fundamentals) de fan-out ENTRE agentes (lo nuevo de este módulo). - Extender el caso base con concurrencia real (
ThreadPoolExecutor), sin perder el determinismo de la salida agregada. - Medir el ahorro de latencia en rondas de coordinación, no en reloj real, con una fórmula que generaliza a cualquier número de sub-tareas.
- Reconocer cuándo el fan-out NO aplica — sub-tareas que solo parecen independientes por cómo están redactadas, pero comparten una dependencia real.
El antes y después
ANTES del módulo:
→ "Repartir trabajo entre agentes" es simplemente "correr varios al mismo tiempo, sin pensarlo"
→ El orden en que terminan los hilos es parte de lo que se puede reportar
→ Paralelizar siempre ahorra "tiempo", medido con un cronómetro
→ Cualquier petición con "y" en el medio se puede repartir sin revisar sus partes
DESPUÉS del módulo:
→ Repartir exige confirmar PRIMERO que las sub-tareas son independientes -- si no lo son, no es
fan-out, es una dependencia disfrazada de lista
→ El orden de finalización de los hilos NUNCA es parte de la salida -- solo el contenido agregado,
ordenado por una clave fija, es reproducible
→ El ahorro se mide en RONDAS de coordinación (un conteo discreto, ejecutado) -- nunca con
time.time() ni ningún reloj real
→ Una petición con "y" puede ser fan-out (dos preguntas separadas) o una dependencia secuencial
(cotiza-y-luego-resérvala) -- hay que mirar el CONTENIDO, no la conjunción
Trampas a evitar al cursar este módulo
1. "Cualquier petición con dos partes se puede repartir en fan-out"
No. "Cotiza Focus pro 3h y resérvala" tiene dos partes conectadas por "y", pero la segunda necesita el precio que calculó la primera — es exactamente el patrón dependiente del pipeline (M3), no fan-out. La lección 02 construye la prueba ejecutada para distinguir los dos casos.
2. "Correr cosas con ThreadPoolExecutor ya garantiza que el resultado sea reproducible"
Al revés — sin cuidado, es la forma más fácil de introducir no-determinismo: el orden en que los hilos terminan varía. La lección 06 muestra, ejecutando varias veces, que la salida agregada de este módulo es idéntica corrida tras corrida, precisamente porque nunca depende de ese orden.
3. "El ahorro de fan-out se mide igual que el del pipeline (menos llamadas al modelo)"
No — la lección 07 lo aclara con números: el fan-out de este módulo gasta exactamente las mismas llamadas al modelo que hacerlo en secuencia (no elimina ninguna decisión, como sí hacía el pipeline frente al supervisor). Lo que ahorra son rondas — el tiempo de coordinación —, no llamadas.
4. "El fan-out dentro de un agente (pricing_agent) y el de este módulo son el mismo mecanismo"
Son parientes, no lo mismo. La lección 05 lo distingue con precisión: uno reparte tool_use dentro
del turno de un único agente (ya construido, agent-fundamentals M5); el otro reparte agentes
completos, cada uno con su propio historial — lo que este módulo construye de cero.
Cómo trabajar este módulo
- Ejecuta la lección 02 tú mismo, con especial atención al contraste final. Ver, con tus propios ojos, que reordenar sub-tareas independientes no cambia nada —y que reordenar etapas dependientes SÍ rompe algo— es lo que hace que el resto del módulo se sienta justificado.
- No te saltes la lección 06 pensando que "ya viste concurrencia" en
agent-fundamentals. Ahí la concurrencia era entre tool calls dentro de un turno; acá es entre agentes completos, con historiales separados — el riesgo de no-determinismo es real y esta lección lo confronta de frente, corriendo el código varias veces. - La lección 07 es la que sostiene el criterio de "cuándo vale la pena paralelizar". Practicarla con distintos números de sub-tareas antes del mini-proyecto hace que la lección 08 se sienta como aplicar un criterio, no memorizar un ejemplo.
Tiempo estimado:
Lección 01 (esta) → 15 min lectura
Lección 02 → 20 min + correr la demo
Lección 03 → 25 min + correr la demo
Lección 04 → 25 min + correr la demo
Lección 05 → 20 min + correr la demo
Lección 06 → 30 min + correr la demo varias veces
Lección 07 → 25 min + correr la demo
Lección 08 → 30 min + aplicar el patrón completo
Total: ~3.2 horas
Evidencia de éxito
Antes de avanzar al Módulo 5 (Handoff y delegación), deberías poder:
- ✅ Explicar, con tus propias palabras, qué hace que dos sub-tareas sean independientes, y confirmarlo ejecutando (no solo describiendo) que el orden no altera el resultado.
- ✅ Construir
SubTask,run_fanout_sequentialyrun_fanout_parallel, con la salida agregada siempre ordenada por nombre de agente. - ✅ Distinguir, en un ejemplo concreto, fan-out dentro de un agente de fan-out entre agentes.
- ✅ Citar de memoria el resultado de la lección 07: por qué el fan-out no ahorra llamadas al modelo, pero sí ahorra rondas de coordinación, y cuánto.
- ✅ Reconocer una petición que solo PARECE fan-out pero en realidad tiene una dependencia disfrazada de lista.
Resumen
- Este módulo construye el tercer patrón de los cinco que anticipó el Módulo 1: fan-out paralelo — sub-tareas genuinamente independientes, repartidas y agregadas, sin ningún orden que respetar entre ellas.
- Regla dura: la decisión de cada agente sigue siendo concepto (
claude-sonnet-5); el reconocimiento de independencia, el despacho —secuencial y paralelo—, la agregación y el conteo de costo se ejecutan de verdad con Python 3.14. La salida agregada NUNCA depende del orden de finalización de los hilos. - El caso reusa, sin cambios, los tres especialistas de Reservo — la petición compuesta "cotiza
Focus pro 3h y dime la política de cancelación" se reparte entre
booking_agentypolicy_agent;pricing_agentaporta la variante de fan-out dentro de un solo agente. - Frontera clara con lo que ya viste y con lo que sigue: el supervisor (M2) decide quién trabaja; el pipeline (M3) fija el orden sin decidir; el fan-out (este módulo) reparte trabajo simultáneo; el handoff (M5) es un agente en curso que cede el control a mitad de tarea, no un reparto decidido desde el principio.
Siguiente lección: 02 — Identificando sub-tareas independientes. Construimos la prueba ejecutada que distingue una sub-tarea genuinamente independiente de una dependencia disfrazada de lista.
Recursos adicionales
- Anthropic — Building effective agents — El patrón "parallelization" descrito ahí (sectioning y voting sobre sub-tareas independientes) es, con otro vocabulario, exactamente el fan-out de este módulo.
- Anthropic — Multi-agent research system — Un caso real de Anthropic donde repartir sub-tareas independientes entre sub-agentes redujo el tiempo de una investigación compleja.
- Python —
concurrent.futures— El módulo detrás deThreadPoolExecutoryas_completed, la base técnica de la concurrencia real que construye la lección 06. - Python 3.14 — What's New — La versión con la que se ejecuta toda la orquestación de esta guía.