En estas notas te explico cómo orquestar agentes con la arquitectura Workflow de Strands Agents utilizando paralelismo y join points, ejemplificado con la construcción de un entregable para preparar y gestionar reuniones como Solutions Architect. Testado de dos maneras: con Gemma4 + Ollama (local) y con la API de Gemini.
Introducción
La idea principal es experimentar con el patrón de orquestación workflow sin gastar en suscripciones pagas de tokens, dejando el deploy en AWS para otra instancia. Durante el desarrollo y testing me encontré con algunos inconvenientes de features no implementadas y descubrí proveedores de modelos soportados al día de hoy.
🗒 El caso de uso que elegí para representar el patrón de orquestación Workflow es un copiloto para Solutions Architects que prepara y cierra discovery meetings con clientes: investiga la empresa, mapea a los stakeholders, genera preguntas de discovery fundamentadas en ese contexto, resume la reunión una vez ocurrida, y sintetiza todo en un entregable de seguimiento (email + próximos pasos + gaps a resolver).
La idea del diseño se sacó del playground de PartyRock de Jeff Escott
En las secciones siguientes cubro los conceptos claves del patrón, el diseño e implementación del caso de uso, los hallazgos reales de ejecución con ambos modelos, y las evaluaciones con strands-agents-evals.
Conceptos Claves
➡ Workflow de Agentes es un patrón de orquestación coordinado en una secuencia definida.
Cada Agente realiza una serie de tareas definidas. El desarrollador puede descomponer tareas complejas en componentes manejables y distribuirlos entre agentes especializados. Cada workflow tiene un control explícito del orden de ejecución de las tareas, las dependencias y el flujo de información entre tareas.
Workflow es apropiado:
- Cuando se tiene un proceso complejo pero repetible que se desea encapsular en una herramienta única, confiable y reutilizable, un
Workflowes un grafo de tareas definido por el desarrollador que un agente puede ejecutar como una acción única y potente. - Si tenemos complejidad en los procesos con múltiples pasos, resolución de tareas con distintas etapas secuenciales.
- Procesos que requieren subagentes especializados en cada etapa o un seguimiento detallado en cada paso
- Ejecutar tareas independientes en paralelo, mientras hay gestión de independencias. De esta manera optimizamos recursos.
- Si tenemos que reintentar pasos por error tareas especificas sin reiniciar todo el flujo.
Para clarificar las diferencias entre patrones de orquestación, te muestro una comparativa entre workflow, graph y swarm. La mayor diferencia a considerar es cómo se determina la ruta de ejecución :
| Workflow | Graph | Swarm | |
|---|---|---|---|
| Concepto fundamental | Un grafo de tareas predefinido (DAG) ejecutado como una herramienta única y no conversacional. | Un flowchart estructurado y definido por el desarrollador, donde un agente decide qué camino tomar. | Un equipo dinámico y colaborativo de agentes que se pasan las tareas de forma autónoma. |
| Estructura | Un desarrollador define todas las tareas y sus dependencias en el código. | Un desarrollador define todos los nodos (agentes) y aristas (transiciones) con antelación. | Un desarrollador proporciona un pool de agentes. Los propios agentes deciden el camino. |
| Flujo de ejecución | Determinista y paralelo. El flujo está determinado por el grafo de dependencias. Las tareas independientes se ejecutan en paralelo. | Controlado pero dinámico. El flujo sigue los bordes del grafo, pero la decisión de un LLM en cada nodo determina el camino (path). | Secuencial y autónomo. Un agente ejecuta una tarea y luego cede el control a otro agente adecuado en el enjambre. |
| ¿Permite ciclo? | NO. | SÍ. | SÍ. |
| Mecanismos de compartir estados | La herramienta captura automáticamente los outputs de las tareas y los pasa como inputs a las tareas dependientes. | Se pasa un objeto de estado compartido a todos los agentes, quienes pueden leerlo y modificarlo libremente. | Todos los agentes disponen de un contexto compartido o memoria de trabajo, que contiene la solicitud original, el historial de tareas y el conocimiento de los agentes anteriores. |
| Historial de conversaciones | Contexto específico de la tarea. Una tarea recibe un resumen curado de los resultados relevantes de sus dependencias, no el historial completo. | Transcripción completa. Todo el historial de diálogo forma parte del estado compartido, lo que proporciona a cada agente un contexto completo y abierto. | Transcripción compartida. El contexto compartido proporciona un historial completo de los handoffs de agentes y el conocimiento aportado por agentes anteriores, disponible para el agente actual. |
| Control del comportamiento | La solicitud del usuario puede activar un workflow predefinido, pero no puede alterar su estructura interna. | La información que introduce el usuario en cada paso puede influir directamente en la ruta que seguirá el grafo a continuación. | La indicación inicial del usuario define el objetivo, pero a partir de ahí el Swarm corre de forma autónoma. |
| Escalabilidad | Escala bien en operaciones repetibles y complejas. | Escala bien en procesos complejos(muchas ramificaciones, condiciones). | Escala en función del número de agentes especializados en el equipo y de la complejidad de la tarea colaborativa. |
| Manejo de errores |
Sistémico. Un fallo en una tarea detendrá todas las tareas dependientes posteriores. Es probable que todo el workflow entre en estado failed. |
Controlable. Un desarrollador puede definir aristas de "error" explícitas para dirigir el flow a un nodo de error-handling si falla un paso. | Agent-driven. Un agente puede decidir transferir la tarea a un especialista en manejo de errores. El sistema se basa en tiempos de espera y límites de transferencia para evitar bucles infinitos. |
➡ Conceptos genéricos de Workflow agénticos
Para la implementación del caso de uso, solo usé la workflow tool, una de las ~45 tools nativas disponibles en strands-agents-tools.
Componentes claves en la arquitectura:
⭐Definición y distribución de tareas. Cada tarea necesita una descripción especifica de qué debe lograr el agente, una asignación que haga matching con el agente adecuado según sus capacidades, y un nivel de prioridad que determine qué se ejecuta primero cuando hay margen de elección.
⭐Gestión de dependencias. Algunas tareas deben ejecutarse en un orden específico (dependencias secuenciales), mientras que las tareas independientes pueden correr en simultáneo (ejecución paralela). En la ejecución paralela tener en cuenta que el modelo de IA que se utilice debe permitirlo.
Los join points son los puntos donde varios caminos paralelos convergen antes de continuar. En el caso de uso elegido, los dos join points son discovery_questions (espera a company_research + stakeholder_mapping) y follow_up_actions (espera a discovery_questions + meeting_summary).
⭐Flujo de información. El mapeo de input/output conecta la salida de un agente con la entrada de otro, la preservación de contexto mantiene la información relevante a lo largo de todo el workflow, y la gestión de estado permite trackear el progreso general del workflow.
Parámetros básicos del workflow:
action → create, start, status (indica si hubo error o no), list, delete.
workflow_id → identificador único del workflow.
tasks → lista de tareas con sus correspondientes propiedades
Algunas propiedades dentro de cada task:
task_id : Obligatorio. Identificador o nombre de la tarea.
description: Obligatorio. se completa con un resumen de lo que hace la tarea, es un pequeño prompt.
dependencies: Opcional. Encadenas una tarea con la otra para que no se ejecute si las dependencias aún no culminaron.
system_prompt: Obligatorio.
priority: Propiedad numérica que indica prioridad en la ejecución por el modelo.
tools: Opcional. Pero si no lo especificás puede que en la inferencia el modelo decida usar cualquier tool nativa y entre en un loop infinito.
Si no querés que la tarea ejecute ninguna tool se puede especificar cómo "tools": NO_TOOLS, esto no es un valor oficial del SDK, es un workaround verificado empíricamente: se pasa un nombre de tool que no existe en el registry, forzando al sub-agente a correr sin tools. Pasar "tools": [] no funciona porque es falsy en Python y el workflow lo ignora heredando todo el toolset del agente padre.
➡ Topologías de Secuencias y Workflow
En Strands Agents, "Workflow" puede referirse a dos cosas distintas: una arquitectura secuencial que se implementa manualmente encadenando agentes en código Python, o la Workflow Tool (strands-agents-tools), que gestiona dependencias, paralelismo y pasaje de contexto automáticamente.
Workflow Secuencial
En la topología secuencial, cada agente es un objeto Python independiente y el desarrollador conecta las salidas manualmente. Es la forma más simple de encadenar agentes, pero no hay gestión automática de dependencias ni paralelismo.
from strands import Agent
# Create specialized agents
agent1 = Agent(system_prompt="You are Agent 1. Perform the first step.", callback_handler=None)
agent2 = Agent(system_prompt="You are Agent 2. Perform the second step.", callback_handler=None)
agent3 = Agent(system_prompt="You are Agent 3. Perform the final step.")
# Sequential workflow processing
def process_workflow(input_data):
step1_result = agent1(f"Process: {input_data}")
step2_result = agent2(f"Process: {step1_result}")
final_result = agent3(f"Process: {step2_result}")
return final_result
Workflow Tool
La Workflow Tool gestiona dependencias, paralelismo y pasaje de contexto automáticamente. El desarrollador define la estructura (tasks + dependencies) y la tool se encarga del resto: qué corre en paralelo, qué espera, y qué contexto inyecta a cada tarea dependiente.
from strands import Agent
from strands_tools import workflow
# Create an agent with workflow capability
agent = Agent(tools=[workflow])
# Create a multi-agent workflow
agent.tool.workflow(
action="create",
workflow_id="generic_workflow",
tasks=[
{
"task_id": "task1",
"description": "Description of the first task",
"system_prompt": "You perform the first task.",
"priority": 5
},
{
"task_id": "task2",
"description": "Description of the second task",
"dependencies": ["task1"],
"system_prompt": "You perform the second task.",
"priority": 3
},
{
"task_id": "task3",
"description": "Description of the third task",
"dependencies": ["task2"],
"system_prompt": "You perform the third task.",
"priority": 2
}
]
)
# Execute workflow (parallel processing where possible)
agent.tool.workflow(action="start", workflow_id="generic_workflow")
# Check results
status = agent.tool.workflow(action="status", workflow_id="generic_workflow")
➡ Evaluaciones (Evals)
Strands cuenta con una marco oficial para realizar evaluaciones de resultados simples hasta el análisis de interacciones de multiagentes complejos.
Para evaluar el workflow usé strands-agents-evals con el mismo provider configurado en .env (Gemini u Ollama) como juez. En producción la práctica estándar es usar un modelo más capaz como judge (ej. el workflow corre con gemini-2.0-flash y el judge usa gemini-2.5-pro), pero para este proyecto educativo se unifica por simplicidad.
Combiné dos tipos de evaluación:
LLM-as-a-judge — el modelo evalúa la calidad del output final:
| Evaluador | ¿Qué evalúa? | Escala |
|---|---|---|
OutputEvaluator |
Verifica que follow_up_actions tenga las 3 secciones obligatorias (email, next steps, gaps) y que el contenido esté fundamentado en los datos reales de la reunión. |
0 / 0.5 / 1 |
HelpfulnessEvaluator(1) |
Evalúa si el entregable final es útil para un Solutions Architect — no solo si tiene el formato correcto. (Es una instancia de OutputEvaluator con rubric propio, no una clase separada del SDK!) | 0 / 0.5 / 1 |
(1)HelpfulnessEvaluator del SDK requiere trazas OpenTelemetry que la Workflow Tool no provee. Se reemplazó por un segundo OutputEvaluator con rubric de helpfulness.
Cuando usé HelpfulnessEvaluator del SDK, el score devolvió 0.0 con el error: "Trace parsing requires actual_trajectory to be a Session object, got NoneType". El evaluador espera un objeto Session de Strands (que debe contener trazas OpenTelemetry de la ejecución del agente), no solo el texto del output.
Métricas deterministas — evalúan propiedades estructurales del patrón, sin llamadas a LLM:
| Métrica | ¿Qué mide? |
|---|---|
task_completion_rate |
Fracción de tasks que completaron con status="completed" en el JSON persistido. |
join_point_coverage |
Verifica que ambos join points produjeron output no vacío, confirma que la inyección de contexto funcionó. |
required_sections |
Check determinista de las 3 secciones del entregable (email, next steps, gaps). |
stakeholder_coverage |
Verifica que cada stakeholder del input aparece en discovery_questions, confirma que el contexto de stakeholder_mapping llegó al join point #1. |
keyword_presence |
Presencia de keywords de dominio (con soporte de sinónimos via `\ |
Descripción de la solución
📦 Repositorio GitHub: github.com/reinalau/strands-workflow
Para la prueba de concepto utilicé dos opciones locales de ejecución que permitió analizar sus resultados, pero se puede utilizar solo una:
a. Ollama + el pequeño modelo gemma4:e2b-it-qat (que pesa poco mas de 4gb) ejecutando en Docker.
En caso que quieras ejecutar realmente las task que son independientes en paralelo, hay que indicarselo a ollama (sino el comportamiento por default es secuencial) :
{% raw %}
# Start the Ollama server with a persistent volume
docker run -d --name ollama -p 11434:11434 -v ollama_data:/root/.ollama ollama/ollama
# Start the Ollama server en parallel
# docker run -d --name ollama -p 11434:11434 -v ollama_data:/root/.ollama -e OLLAMA_NUM_PARALLEL=3 ollama/ollama
# Download the model
docker exec -it ollama ollama pull gemma4:e2b-it-qat
# Test that the model responds
# If the container has already been created and
# is currently stopped, simply use: docker start ollama
docker exec -it ollama ollama run gemma4:e2b-it-qat
# Verify that the model is running
docker exec -it ollama ollama ps
b. Api de gemini. La api key se puede generar de manera gratuita desde aquí y utilizar algunos de estos modelos (experimentar con los que te permita):
gemini-2.5-flash-lite
gemini-2.5-flash
gemini-3.5-flash
gemini-3.5-flash-lite
El código está en Python y la estructura del proyecto se diseñó de manera que sea más explicativo su lógica, todo el detalle lo encontrás en el Readme.md del proyecto:
strands-workflow/
├── README.md
├── requirements.txt # strands-agents[litellm,ollama], strands-agents-tools, pydantic, pytest
├── .env.example # MODEL_PROVIDER, GEMINI_API_KEY, OLLAMA_MODEL_ID
├── .gitignore
├── pytest.ini # pythonpath=. so tests/ and evals/ resolve src./config imports
│
├── src/
│ ├── __init__.py
│ ├── config.py # get_model_config(): single global provider (Gemini or Ollama) via MODEL_PROVIDER env var
│ ├── main.py # Entry point: builds tasks, creates+starts the workflow, assembles the final .md
│ │
│ ├── agents/
│ │ ├── __init__.py
│ │ └── prompts.py # System prompts per task_id (company_research, stakeholder_mapping, etc.)
│ │
│ ├── workflow/
│ │ ├── __init__.py
│ │ ├── builder.py # build_tasks(): builds the task list — task_id, dependencies, priority
│ │ ├── deliverable.py # Reads the persisted workflow JSON and assembles the final .md
│ │ └── instrumentation.py # Timing hook: logs START/END + duration per task
│ │
│ ├── models/
│ │ └── schemas.py # Pydantic: DiscoveryMeetingInput
│ │
│ └── utils/
│ ├── logging_config.py # Tees stdout+stderr to logs/, optional DEBUG level
│ └── report_export.py # Writes the final .md to outputs/
│
├── tests/
│ ├── __init__.py
│ ├── test_config.py # get_model_config(): provider resolution + error if API key is missing
│ ├── test_workflow_structure.py # Validates the workflow is well-formed: no cycles, existing dependencies, correct join points
│ ├── test_schemas.py # Pydantic validation of DiscoveryMeetingInput
│ ├── test_report_export.py # Generates the .md from a mocked workflow JSON
│ └── test_instrumentation.py # Verifies install_task_timing_hooks() logs START/END with duration, without calling a real LLM
│
├── examples/
│ └── sample_input.json # Sample input to run the workflow quickly
│
├── evals/
│ ├── __init__.py
│ ├── evaluate_workflow.py # strands-evals runner: OutputEvaluator + HelpfulnessEvaluator + deterministic metrics
│ ├── metrics.py # Domain metrics: task completion rate, join point coverage, required sections, stakeholder coverage, keyword presence
│ └── test_cases/
│ └── workflow_eval_cases.json # Input + expected properties per eval case
│
├── logs/
│ └── .gitkeep # logs/run_<timestamp>.log, gitignored otherwise
│
└── outputs/
└── .gitkeep # .md generated per run, gitignored otherwise
Ejecución Local
Una vez que tengas 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 vas a encontrar en requirements.txt :
strands-agents[litellm,ollama]==1.52.0
strands-agents-tools==0.8.6
ollama==0.5.1
pydantic==2.13.4
pytest==8.3.4
strands-agents-evals>=1.0.0
python-dotenv==1.0.1
pip install -r requirements.txt
En .env se necesitan estos valores. Notar que depende el modelo que elegimos podemos intercambiar el MODEL_PROVIDER:
# Select ONE provider: "ollama" or "gemini"
MODEL_PROVIDER=ollama
# --- Gemini (only required if MODEL_PROVIDER=gemini) ---
# Free tier key: https://aistudio.google.com/apikey
GEMINI_API_KEY=YourApiKey
GEMINI_MODEL_ID=gemini-3.5-flash-lite
# --- Ollama (only used if MODEL_PROVIDER=ollama) ---
# Must have the Ollama daemon running locally: https://ollama.com
# Pull the model first: ollama pull gemma4:e2b-it-qat
OLLAMA_MODEL_ID=gemma4:e2b-it-qat
OLLAMA_HOST=http://localhost:11434
# --- Workflow persistence ---
# Redirects strands_tools' internal workflow state JSON into logs/ instead of
# the default ~/.strands/workflows/ — keeps everything from one run together.
STRANDS_WORKFLOW_DIR=logs/workflows
cp .env.example .env
En src/config.py está la configuración de ejecución, en este caso de uso utilicé parámetros como la temperature y max_tokens (limita los tokens que pueden gastar los agentes).
→ Si optas por ejecutar con el provider ollama hay que tener en cuenta el valor de la ventana de contexto: num_ctx.
→ Si optas por ejecutar con la apikey de Gemini, en el SDK de Strands, el dispatcher create_model que usa workflowinternamente soporta estos modelos solo vía litellm ("model_provider": "litellm")
Las pruebas están organizadas en tests/ en un solo nivel: ejecución rápida y determinista. Validan la estructura del workflow (sin ciclos, dependencias existentes, join points correctos), el schema del input con Pydantic, la exportación del .md desde un JSON mockeado, y los hooks de timing (sin llamadas al LLM).
python -m pytest
Ejecución del Workflow principal. En examples/sample_input.txtestá el input de ejemplo en un formato json:
{
"company_name": "...",
"known_challenges": "...",
"stakeholders": "...",
"raw_meeting_notes": "..."
}
Se puede ejecutar contra el modelo ollama local o vía api de gemini comentado más arriba.
python -m src.main
🎯 Un bug real: durante las pruebas descubrí que usando Gemini con paralelismo real puede hacer crashear el proceso de forma intermitente (y no es un problema de Windows, ocurre también en Linux). La causa es un bug conocido y abierto en LiteLLM: el cliente HTTP que usa para el streaming de Gemini es un singleton global que no distingue entre los distintos event loops que crea cada tarea del workflow al correr en paralelo.
Solución aplicada: limitar la ejecución a un hilo por vez cuando el proveedor es Gemini, vía la env var STRANDS_WORKFLOW_MAX_THREADS=1 de Strands.
El log de las llamadas del workflow está en logs/. Contiene la data cruda que permite verificar si hubo paralelismo, cuanto tiempo tarda cada task, etc.
El resultado final
Es un documento markdown con diferentes secciones como:
Company Research - Stakeholder Mapping - Meeting Summary - Discovery Questions - Follow-up Deliverable and Gap
Se guarda en outputs/discovery_meeting_yyyymmdd_hhmmss.md.
Por último, hice una evaluación de pruebas sobre el workflow multiagente real con el concepto de "Evals". Ejecuta el workflow completo contra el modelo configurado, corre los evaluadores LLM-as-judge y las métricas deterministas, muestra los resultados en consola y guarda el reporte completo en outputs/eval_report_<timestamp>.json.
python -m evals.evaluate_workflow
📝 Nota: La explicación del código fuente y paso a paso de la ejecución se encuentra detallado en el README.md en github .
Conclusiones y lecciones aprendidas
El patrón Workflow en Strands resuelve en lineas generales lo que indica su documentación. Dependency resolution, paralelismo real y context passing automático entre join points funcionaron correctamente y sin código extra de orquestación. El DAG se ejecuta en el orden correcto y los join points reciben el contexto inyectado de sus dependencias sin intervención manual.
La Workflow Tool de Strands está en maduración activa.
pause/resumeestán documentados como Advanced Features pero no implementados — al ejecutarlos devuelven"🚧 Action '...' is not yet implemented", verificado contra el código fuente instalado. El workflow puede quedar colgado indefinidamente si una task termina enerror(el loop interno gira para siempre esperando dependientes que nunca se vuelven "ready"). El resultado de cada task no se expone via API... hay que leer el JSON persistido directamente.Algunos comportamientos requieren workarounds no oficiales. La herencia de tools en sub-agentes no está documentada. Por defecto cada sub-agente hereda todo el toolset del padre, incluida la
workflowtool, lo que con modelos agénticos lleva a llamadas recursivas reales."tools": [](lista vacía) no funciona como restricción porque es falsy en Python — la solución esNO_TOOLS(["__no_tools__"]), un placeholder que fuerzafiltered_toolsa quedar vacío. No encontré hooks públicos para observar timing por task, se requiere monkeypatch sobreWorkflowManager.execute_task.Los hooks de Strands tienen un mecanismo
LimitToolCounts(viaBeforeToolCallEvent) que permite cancelar una tool cuando se invoca más veces de lo permitido, útil como guardrail contra loops. Sin embargo, no aplica al bug de herencia de la Workflow Tool, ese mecanismo actúa sobre el agente padre, pero los sub-agentes creados internamente por el framework son instancias separadas sobre las que no hay control para registrar hooks desde afuera. Por eso"tools": NO_TOOLSsigue siendo la solución correcta.Concurrencia del workflow tool con Gemini. Cuando hay tasks en paralelo + LiteLLM/aiohttp, se produce un
RuntimeError: loop is not the running loopintermitente. Cada task corre en su propio thread con su propioasyncio.run(), y el cierre del connector de aiohttp cae en el loop equivocado. Se solucionó colocando una variables de entorno de StrandsSTRANDS_WORKFLOW_MAX_THREADS=1, limitando la concurrencia. (esto no sucede con Ollama-Gemma4 via Docker).
Recursos
Curso Fundamentos (hacelos!)
Building AI Agent Harnesses with Strands Agents
Building AI Agent Harnesses – Video CourseDocumentación Strands Agents
Agent Workflows
Evals



Top comments (0)