Siguiendo con la serie de artículos sobre la orquestación de multiagentes en Strands, en estas notas le toca turno a Swarm o Enjambre explicado con un ejemplo gamificado de cinco agentes especializados, donde cada uno es responsable de diseñar una dimensión distinta de un videojuego. Testeado de dos maneras con Gemma4 + Ollama y con la api de Gemini.
Introducción
Para desarrollar y comprender agentes con el framework Strands Agents te propongo hacer un curso de fundamentos o leer su documentación (que es extensa). Los fundamentos son importantes! Es primordial conocer cómo dar complejidad a nuestros agentes, no esperar delegar toda la responsabilidad del diseño a la IA (recursos al pie de estas notas).
Para la prueba de concepto utilicé dos opciones locales de ejecución que me permitió analizar sus resultados:
Ollama + el pequeño modelo gemma4:e2b-it-qat (que pesa poco mas de 4gb) ejecutando en Docker.
Con la Api de gemini. La api key se puede generar de manera gratuita y utilizar el modelo "Gemini 2.5 Flash"
🎮 El caso de uso que tomé para desarrollar el patrón Swarm es la creación de un Game Design Document (GDD) a partir de una premisa: "roguelike de cocina en un castillo maldito..."
Participan cinco agentes especializados, cada uno responsable de una dimensión diferente del diseño del videojuego: mecánicas, narrativa, niveles, lore (historias, mitos, reglas, datos del pasado) y experiencia de juego. Todos colaboran para transformar una premisa simple en un Game Design Document (GDD) coherente.
A diferencia de un pipeline lineal, los agentes pueden detectar fricciones entre sus decisiones (una mecánica que contradice la narrativa, una regla de lore que rompe el balance) y reabrir la conversación con el agente correspondiente hasta llegar a un resultado consensuado.
Conceptos Claves
➡ Swarm es un patrón de orquestación de agentes colaborativo, donde múltiples agentes trabajan juntos como un equipo para resolver tareas complejas. A diferencia de los sistemas multiagente tradicionales, ya sean secuenciales o jerárquicos, un enjambre permite la coordinación autónoma entre agentes con contexto y memoria de trabajo compartidos.
Swarm es apropiado cuando:
- La secuencia de pasos no se puede predefinir: de antemano no sabés qué agente necesitará intervenir, porque depende del contenido que se vaya generando.
- Puede haber correcciones o retrocesos: el problema requiere ciclos de ida y vuelta entre especialistas (ejemplo: una decisión de un agente invalida el trabajo de otro).
- Hay múltiples dimensiones de expertise en tensión: cada agente defiende una perspectiva distinta y el valor está justamente en que negocien entre sí, no en que trabajen aislados uno de otro.
- Se necesita razonamiento colectivo emergente: la solución final surge de la interacción entre agentes, no de que un solo agente (o un humano) coordine todo desde arriba.
❗ No es el patrón ideal cuando el flujo de trabajo es fijo y predecible.
➡ Memoria / Contexto
Poseen memoria de trabajo compartida y contexto completo. Cuando el enjambre se ejecuta, el historial completo de mensajes (propuestas, objeciones y razonamientos de de los agentes anteriores) se transmite al nuevo agente que toma el control (handoff).
Payload del Handoff: además del historial acumulado, cuando un agente ejecuta la herramienta de handoff, puede adjuntar un mensaje o nota explícita (message / context) explicando por qué le transfiere la tarea y qué espera que haga.
➡ Nodos vs. Agentes
En Swarm, cada agente se registra como un "nodo" (SwarmNode) que envuelve al Agent. Esta distinción importa porque el swarm no razona en términos de "quién es quién" sino de "qué nodo ejecuta a continuación". El resultado final expone esto como node_history, una lista de nodos ejecutados, no una conversación: es lo que permite reconstruir después la topología real que se ejecutó, sin haberla tenido que diseñar de antemano.
➡ El mecanismo de Handoff es tool-calling, no un cambio de contexto mágico
Vale la pena aclarar explícitamente que handoff_to_agentno es una función especial del framework fuera del paradigma de LLMs: es una tool más, inyectada automáticamente por Strands en cada agente del swarm, con el mismo mecanismo de function-calling que cualquier otra tool. La consecuente es la fiabilidad del patrón Swarm depende enteramente de qué tan bien el modelo subyacente hace tool-calling. No es una limitación de Strands, es una dependencia estructural del patrón. Por eso, acompañando este artículo el caso de uso se testeo de dos maneras: con el pequeño modelo Gemma4 y con el modelo Gemini 2.5 Flash.
➡ Topología emergente vs. predefinida (el argumento central de "por qué Swarm")
En Swarm, la topología es un resultado, no una entrada. Se descubre recién en la ejecución, y puede variar entre corridas con la misma premisa (ejemplo: tengo 5 agentes y sólo intervienen 4). Esto es una ventaja de adaptabilidad y un riesgo no-determinista al mismo tiempo.
Se necesita indicar donde comienza el enjambre y cada punto de decisión lo toma el propio agente en base al contexto acumulado. Aquí un topología de ejemplo del caso de uso:
mechanic_designer
↓
level_architect ←──────────┐
↓ │
playtest_simulator ─────────┤ (iteration 1: objection → adjustment)
↓ │
level_architect ──────────→┘
↓
playtest_simulator ─────────┐ (iteration 2: objection → adjustment)
↓ │
level_architect ←───────────┘
↓
narrative_weaver
↓
lore_keeper
↓
playtest_simulator ─────────┐ (iteration 1: objection → adjustment)
↓ │
level_architect ←───────────┘
↓
playtest_simulator
↓
[VERDICT: NO FRICTION DETECTED] → END
➡ Guardrails como parte del diseño
max_handoffs: Máximo de transferencias entre agentes permitidos en todo el swarm antes de forzar el corte. Es el límite global de "pasos" de la ejecución completa.
max_iterations: Máximo de iteraciones del swarm (ejecuciones de nodo) permitidas. En la práctica actúa junto con max_handoffs como segunda red de seguridad contra ejecuciones descontroladas.
execution_timeout: Tiempo total en segundos que puede durar la ejecución completa del swarm, sumando todos los agentes y handoffs. Si se supera, el swarm corta con Status.FAILED aunque esté en medio de un turno.
node_timeout: Tiempo máximo (en segundos) que un agente individual puede tardar en un solo turno (una llamada al modelo + su respuesta). Protege contra que un nodo puntual se cuelgue sin afectar el límite global.
repetitive_handoff_detection_window: Tamaño de la "ventana" (cantidad de handoffs recientes) que Strands analiza para detectar patrones repetitivos. Con 6, mira los últimos 6 handoffs para decidir si hay un loop.
min_unique_agents: Dentro de esa ventana, cantidad mínima de agentes distintos que deben haber participado para considerar el flujo "sano". Si en los últimos 6 handoffs participaron menos de 3 agentes únicos (ej. solo 2 rebotando entre sí), Strands lo detecta como ping-pong y corta el swarm.
Todos los guardrails anteriores no son opcionales para un swarm en producción, son necesarios para limitar la autonomía de los agentes y que el costo de tokens, tiempo o dinero en APIs pagas no se descontrole.
➡ La importancia del diseño de prompts
Strands provee el mecanismo de handoff (la tool, el hook, los guardrails), pero no decide cuándo usarlo, eso está en el system prompt de cada agente. En este caso de uso, cada uno de los 5 prompts define explícitamente tres cosas: el rol y criterios de evaluación del agente, a quién y bajo qué condición debe transferir el control, y reglas de convergencia para evitar que vuelva a objetar lo mismo una vez resuelto ("Convergence rule").
Esta última fue necesaria escribirla ya que en las primeras ejecuciones algunos agentes re-abrían el mismo desacuerdo varias veces con distintas palabras, generando ciclos improductivos que ni los guardrails ni el hook podían distinguir de una negociación legítima. La solución fue de prompt, instruir explícitamente a cada agente a aceptar una corrección después de la primera vuelta, salvo que aparezca un motivo nuevo. Los guardrails protegen contra loops estructurales; esto resuelve los loops semánticos.
➡ Hooks como punto de extensión para mitigar limitaciones del modelo elegido
Strands permite engancharse al ciclo de ejecución del agente mediante hooks, interceptando eventos como una llamada a herramienta exitosa. En el caso de uso se implementó StopAfterHandoffHook, que fuerza al agente a detener su turno inmediatamente después de un handoff exitoso.
Este mecanismos sirve para cuando el prompt no alcanza para garantizar un comportamiento, evita que el modelo llame a la herramienta de handoff más de una vez por turno. Es una intervención determinista a nivel de código, sin depender del prompt engineering.
➡ Evaluaciones (Evals)
Existe el package strands-agents-evals que provee evaluadores reutilizables (por salida, por trayectoria de tool-calls, por trazas de ejecución, etc). En este caso de uso se implementaron dos evaluadores:
- Evaluación por Trayectoria (Trajectory Evaluation). En vez de exigir una secuencia exacta de handoffs, valida propiedades estructurales de la ejecución.
- que el punto de entrada haya sido el correcto.
- que haya participado una cantidad mínima de agentes únicos, y que los agentes indispensables (como
mechanic_designer) hayan intervenido.
- Evaluación por Salida (Output Evaluation). Verifica que el GDD final contenga las palabras clave esperadas del dominio del juego (ej. "curse", "ingredient", "castle"), como chequeo básico de que el contenido generado es relevante a la premisa de origen.
⭐ Este enfoque refleja una diferencia clave frente a patrones más deterministas como agents-as-tools donde se puede evaluar contra un resultado esperado preciso (si se delegó al agente correcto); en cambio en Swarm hay que evaluar la forma del comportamiento, no una secuencia fija.
Descripción de la solución
📦 Repositorio GitHub: github.com/reinalau/strands-swarm
Como comenté más arriba, utilicé 2 opciones de modelos para la ejecución y analizar el comportamiento, pero podes usar solo una de ellas:
a. Docker Desktop, luego descargué la imagen de Ollama e instalé gemma4:e2b-it-qat (que pesa poco mas de 4GB). Todos los modelos gemma los podés encontrar aquí
# Start the Ollama server with a persistent volume
docker run -d --name ollama -p 11434:11434 -v ollama_data:/root/.ollama ollama/ollama
# Download the model
docker exec -it ollama ollama pull gemma4:e2b-it-qat
# Test that the model responds
docker exec -it ollama ollama run gemma4:e2b-it-qat
# Verify that the model is running
docker exec -it ollama ollama ps
b. Generé una api key de gemini de capa gratuita, desde aquí. Te permite usar el modelo Gemini 2.5 Flash.
El código del multiagente está en Python y la estructura del proyecto se diseñó de manera que sea más explicativo su lógica:
strands-swarm/
├── README.md
├── requirements.txt
├── .env.example
├── .gitignore
│
├── src/
│ ├── __init__.py
│ ├── main.py # entry point, builds and runs the swarm
│ ├── config.py # model provider config, timeouts, swarm limits
│ │
│ ├── agents/
│ │ ├── __init__.py
│ │ ├── _common.py # shared model factory + prompt loader
│ │ ├── _hooks.py # StopAfterHandoffHook
│ │ ├── mechanic_designer.py
│ │ ├── narrative_weaver.py
│ │ ├── level_architect.py
│ │ ├── lore_keeper.py
│ │ └── playtest_simulator.py
│ │
│ ├── prompts/
│ │ ├── mechanic_designer.md
│ │ ├── narrative_weaver.md
│ │ ├── level_architect.md
│ │ ├── lore_keeper.md
│ │ └── playtest_simulator.md
│ │
│ ├── swarm/
│ │ ├── __init__.py
│ │ └── build_swarm.py # instantiates agents + configures Swarm (max_handoffs, etc.)
│ │
│ └── output/
│ ├── __init__.py
│ └── gdd_builder.py # consolidates node_history/results into the final GDD
│
├── evals/
│ ├── __init__.py
│ ├── eval_cases.py # EvalCase definitions (premise, expected agents, keywords)
│ └── run_evals.py # trajectory + output evaluators over the swarm
│
├── examples/
│ └── example_premise.txt # "cooking roguelike in a cursed castle"
│
├── outputs/
│ └── .gitkeep # generated GDDs land here after each run
│
├── logs/
│ └── .gitkeep # execution logs / handoff events
│
└── tests/
├── __init__.py
└── test_swarm_flow.py
Ejecución Local
Una vez que tenemos el repositorio del código fuente clonado y el docker de ollama con el modelo gemma 4 o la api key de gemini generada, pasamos armar el ambiente.
Los requerimientos que vamos a encontrar en requirements.txt:
strands-agents[ollama]
strands-agents[gemini]
strands-agents-evals
python-dotenv
pytest
pip install -r requirements.txt
En .env necesitamos:
OLLAMA_HOST=http://localhost:11434
MODEL_NAME=gemma4:e2b-it-qat
MODEL_PROVIDER=gemini # ollama
GEMINI_API_KEY=tuapikeyOpcional
GEMINI_MODEL_NAME=gemini-2.5-flash
LOG_LEVEL=INFO
MODEL_TEMPERATURE=0.5
MODEL_MAX_TOKENS=3500
MODEL_NUM_CTX=4096
MAX_HANDOFFS=12
MAX_ITERATIONS=12
EXECUTION_TIMEOUT=5000
NODE_TIMEOUT=1500
REPETITIVE_HANDOFF_DETECTION_WINDOW=6
REPETITIVE_HANDOFF_MIN_UNIQUE_AGENTS=3
ENTRY_POINT_AGENT=mechanic_designer
LOG_DIR=logs
OUTPUT_DIR=outputs
cp .env.example .env
Las pruebas están organizadas en un enfoque de 2 niveles (tests/):
Nivel 1 (Rápidas y deterministas): Valida la estructura del swarm, la configuración de nodos/límites, la extracción de texto y la consolidación del GDD sin realizar llamadas reales al modelo. Usa cadenas simuladas como "Cooking roguelike in a cursed castle." o "Test Premise".
python -m pytest
Nivel 2 (Test de integración end-to-end): Ejecuta el swarm completo contra el modelo real (Gemma4 en Ollama o Gemini). Está deshabilitado por default, pytest lo salta automáticamente, y se activa así en esta ejecución:
RUN_INTEGRATION_TESTS=1 python -m pytest -m integration
📝 Nota: el comportamiento de activación/desactivación explícita para tests que dependen de un LLM real es una práctica común cuando se integran estos flujos a pipelines de CI/CD, donde ejecutar llamadas reales a un modelo en cada commit sería muy lento.
Procedemos a ejecutar el Swarm principal. Se puede ejecutar contra el modelo ollama local o vía api de gemini. Se configura en .env , tener en cuenta de cambiar MODEL_PROVIDER a "gemini" u "ollama". También recomiendo revisar valores default de src/config.py.
python -m src.main
El log de las llamadas entre agentes se visualiza en la carpeta logs/.
El resultado final del intercambio de mensajes entre los agentes del enjambre es el Game Design Document y se guarda en un documento markdown en la carpeta outputs/.
⭐ Te invito analizar la topología de handoffs después de las ejecuciones para entender si los agentes necesitan ajustes ya sea de prompts, hooks o parámetros de configuración.
Por último, hacemos una evaluación de pruebas sobre el agente real con el concepto de "Evals". Esto es importante para todos los agentes futuros que construyas, tomar la documentación de Strands y descubrir cómo y para qué tenemos que evaluar nuestros agentes.
Se desactiva el recolector de telemetría OpenTelemetry: OTEL_SDK_DISABLED=true para evitar cuelgues en un entorno local con el modelo en ollama (si internamente no alcanza un endpoint se cuelga indefinidamente).
export OTEL_SDK_DISABLED="true"
python -m evals.run_evals
📝 Nota-2: La explicación del código y paso a paso de la ejecución se encuentra detallado en el README.md en el repositorio de github.
Conclusiones
El patrón *Swarm * cede el control del flujo al propio modelo. Una corrida puede resolverse en 4 pasos o en 11, en un minuto o en dos horas, según decisiones que toman los agentes que intervienen.
Con un modelo chico experimenté loops de handoff, ping-pong entre agentes, transferencias narradas en texto en lugar de ejecutadas y para resolverlo tuve que agregar controles explícitos: límites de iteración, detección de repetición y un hook que corta el turno apenas el handoff se confirma.
La autonomía de un swarm no reemplaza los guardarraíles de ingeniería, los vuelve indispensables.
⭐ Y como trabajo futuro hay que pensar cómo implementar un proyecto multiagente Swarm productivo. El presente proyecto es educativo y sirve para entender el funcionamiento del patrón, pero es insuficiente para producción. AWS ya tiene un camino natural para eso: Amazon Bedrock AgentCore, un runtime serverless que ejecuta agentes Strands con aislamiento por sesión, observabilidad, memoria administrada y escalado automático. Queda para un próximo artículo explorar esa migración.

Top comments (0)