DanLevy.net

Deja de construir agentes inestables: usa flujos de trabajo y memoria

Patrones deterministas para modelos no deterministas.

Los LLM tienen esta extraña propiedad: son brillantes para entender matices pero terribles para seguir recetas. Dale a un modelo potente un problema vago y razonará entre posibilidades. Dale una secuencia precisa de pasos, y podría saltarse el paso 3 porque el paso 5 “se sintió más relevante”.

Esto no es un error del modelo. Es una característica fundamental de los sistemas probabilísticos que intentan resolver problemas deterministas.

He visto equipos lidiar con este desajuste. Construyen un agente para gestionar reembolsos de clientes, le dan una docena de herramientas y esperan que ejecute de forma fiable un proceso de negocio. A veces funciona perfectamente. A veces alucina aprobaciones que nunca ocurrieron. A veces se queda atascado pidiendo la misma información tres veces.

La solución no son mejores instrucciones. Es saber cuándo dejar de pedirle al LLM que “piense” y empezar a decirle que “obedezca”.


Cuando lo Determinista Supera a lo Creativo

Piensa en lo que sucede cuando necesitas procesar un ticket de soporte. La lógica empresarial real se ve algo así:

  1. Obtener los detalles del ticket desde la base de datos
  2. Verificar si el usuario es elegible para un reembolso (reglas de política)
  3. Verificar que la transacción existe y no ha sido reembolsada previamente
  4. Calcular el monto del reembolso
  5. Procesar la reversión del pago
  6. Actualizar el estado del ticket
  7. Enviar correo de confirmación

Podrías pasarle esto a un LLM como un ejercicio de llamada a herramientas. En mi experiencia, eso es buscarse problemas. El modelo podría decidir que los pasos 2 y 3 son “básicamente lo mismo” y saltarse uno. O podría procesar el reembolso antes de verificar la elegibilidad porque el usuario parecía molesto.

Los flujos de trabajo existen exactamente para este escenario. No son emocionantes, pero de eso se trata.

Construyendo un Planificador de Actividades Climáticas

Aquí hay un ejemplo práctico que muestra el patrón. Necesitamos datos meteorológicos duros y factuales combinados con sugerencias creativas de actividades. La obtención del clima nunca debe ser creativa, pero las sugerencias sí.

src/mastra/workflows/activity-planner.ts
import { createWorkflow, createStep } from '@mastra/core/workflows';
import { Agent } from '@mastra/core/agent';
import { z } from 'zod';
// Step 1: Fetch weather data (Deterministic)
const fetchWeather = createStep({
id: 'fetch-weather',
description: 'Fetches weather forecast for a given city',
inputSchema: z.object({
city: z.string(),
}),
outputSchema: z.object({
location: z.string(),
temperature: z.number(),
conditions: z.string(),
precipitationChance: z.number(),
}),
execute: async ({ inputData }) => {
const coordinates = await geocodeCity(inputData.city);
const params = new URLSearchParams({
latitude: String(coordinates.latitude),
longitude: String(coordinates.longitude),
current: 'temperature_2m,weather_code',
daily: 'precipitation_probability_mean',
timezone: 'auto',
});
const weather = await fetch(`https://api.open-meteo.com/v1/forecast?${params}`)
.then(r => r.json());
return {
location: inputData.city,
temperature: weather.current.temperature_2m,
conditions: getWeatherCondition(weather.current.weather_code),
precipitationChance: weather.daily.precipitation_probability_mean[0],
};
},
});
// Step 2: Agent suggests activities (Creative)
const activityPlanner = new Agent({
id: 'activity-planner-agent',
name: 'Activity Planner',
instructions: `You are a local activities expert. Based on weather conditions, suggest 3-5 appropriate activities.
- For rain (>50% precipitation), prioritize indoor activities
- For extreme temperatures, consider climate-appropriate options
- Always include one adventurous and one relaxing option`,
model: 'openai/gpt-5.5',
});
const planActivities = createStep({
id: 'plan-activities',
description: 'Uses AI to suggest activities based on weather',
inputSchema: z.object({
location: z.string(),
temperature: z.number(),
conditions: z.string(),
precipitationChance: z.number(),
}),
outputSchema: z.object({
activities: z.string(),
}),
execute: async ({ inputData }) => {
const prompt = `Weather in ${inputData.location}: ${inputData.temperature}°C...`;
const response = await activityPlanner.generate(prompt);
return { activities: response.text };
},
});
// The Pipeline
export const activityPlannerWorkflow = createWorkflow({
id: 'activity-planner',
inputSchema: z.object({ city: z.string() }),
outputSchema: z.object({ activities: z.string() }),
})
.then(fetchWeather)
.then(planActivities)
.commit();

geocodeCity() es código de aplicación ordinario o una llamada a la API de Mapas; no es una decisión del modelo. El LLM nunca toca la API del clima. Obtiene datos reales como entrada, luego hace lo que realmente sabe hacer: sugerencias contextuales. Si le das la vuelta y dejas que el agente obtenga datos del clima, eventualmente obtendrás un pronóstico soleado cuando en realidad está lloviendo.

Cuándo considerar flujos de trabajo:


El Problema de la Ventana de Contexto del Que Nadie Habla

Hay un patrón que sigo viendo. Alguien construye un chatbot. Funciona genial durante las pruebas. Luego en producción, los usuarios tienen conversaciones más largas y de repente el bot se pierde.

El desarrollador revisa los registros y se da cuenta de que están enviando todo el historial de la conversación con cada solicitud. Los 47 mensajes. Están quemando tokens y espacio de contexto para información que en su mayoría es irrelevante.

Peor aún, existe un fenómeno que los investigadores llaman “perdido en el medio”: los modelos rinden peor cuando la información relevante está enterrada en un contexto largo. El modelo literalmente no ve el bosque por los árboles.

Enviar el historial completo de la conversación da una falsa sensación de seguridad. Le estás dando al modelo “toda la información”. Pero en realidad le estás dificultando que se concentre en lo que importa.

Mensajes Recientes, Memoria de Trabajo y Recuerdo

El sistema de memoria de Mastra separa varias tareas que los equipos suelen mezclar. El historial de mensajes recientes mantiene los últimos turnos disponibles con lastMessages. La memoria de trabajo almacena hechos estructurados persistentes como preferencias del usuario, objetivos y estado del proyecto. El recuerdo semántico busca en mensajes anteriores por significado cuando la consulta actual parece relacionada. La memoria observacional va un paso más allá para conversaciones largas: comprime el historial antiguo en observaciones densas.

src/mastra/agents/memory-agent.ts
import { Agent } from '@mastra/core/agent';
import { ModelRouterEmbeddingModel } from '@mastra/core/llm';
import { Memory } from '@mastra/memory';
import { LibSQLStore, LibSQLVector } from '@mastra/libsql';
export const memoryAgent = new Agent({
id: 'memory-agent',
name: 'Memory Agent',
instructions: 'You are a helpful assistant with perfect recall of our conversations.',
model: 'openai/gpt-5.5',
memory: new Memory({
storage: new LibSQLStore({
id: 'memory-agent-store',
url: 'file:./mastra.db',
}),
vector: new LibSQLVector({
id: 'memory-agent-vector',
url: 'file:./mastra.db',
}),
embedder: new ModelRouterEmbeddingModel('openai/text-embedding-3-small'),
options: {
lastMessages: 20,
workingMemory: {
enabled: true,
},
semanticRecall: {
topK: 5,
messageRange: 2,
scope: 'resource',
},
observationalMemory: true,
},
}),
});

Una nota operativa: observationalMemory: true usa actualmente google/gemini-2.5-flash como modelo observador por defecto. Eso significa que este agente, respaldado por OpenAI, también necesita acceso a modelos de Google, incurre en uso de modelo separado y puede enviar el historial de la conversación a un segundo proveedor. En producción, configura el modelo de memoria observacional explícitamente y evalúa la elección con la misma revisión de credenciales, costes, residencia y retención de datos que el modelo principal del agente.

Así es como se ve esto en la práctica. Un usuario pregunta: «¿Cuál era ese restaurante italiano que recomendaste el mes pasado?»

Sin recuerdo semántico ni observaciones, el agente ve los últimos 20 mensajes. La recomendación del restaurante era el mensaje 487 de 506. Ha desaparecido. El agente dice «No tengo esa información».

Con recuerdo semántico:

  1. La consulta se vectoriza: [0.234, -0.567, 0.891, ...]
  2. La vectorización se compara con los mensajes históricos
  3. El mensaje 487 («Recomiendo Trattoria Bella – su carbonara es increíble») obtiene una similitud de 0.89
  4. Ese mensaje se inyecta en el contexto actual
  5. El agente responde: «Recomendé Trattoria Bella. Su carbonara fue lo que me llamó la atención».

El agente parece tener una memoria perfecta usando solo una fracción de la ventana de contexto. Esto no es solo ingeniería inteligente: es funcionalmente necesario en cuanto las conversaciones superan unas pocas decenas de mensajes.


Coordinación mediante Agentes Supervisores

A veces necesitas a la vez estructura y flexibilidad. Los workflows puros son demasiado rígidos. Los agentes puros son demasiado impredecibles.

Los agentes supervisores te dan un coordinador que decide qué agente especializado, workflow o herramienta debe encargarse del siguiente paso. Piensa en ello como un balanceador de carga inteligente para capacidades de IA.

const researchAgent = new Agent({
id: 'research-agent',
description: 'Gathers facts and returns sourced research notes.',
model: 'openai/gpt-5-mini',
});
const writingAgent = new Agent({
id: 'writing-agent',
description: 'Turns research notes into clear, structured prose.',
model: 'openai/gpt-5-mini',
});
export const coordinatorAgent = new Agent({
id: 'coordinator-agent',
name: 'Research Coordinator',
instructions: `You coordinate researchers, writers, tools, and workflows.
- Delegate fact gathering to research-agent
- Delegate final prose to writing-agent
- Use weatherTool for current weather data
- Use activityPlannerWorkflow for location-based planning
Always produce comprehensive, well-structured responses.`,
model: 'openai/gpt-5.5',
// Available primitives
agents: { researchAgent, writingAgent },
workflows: { activityPlannerWorkflow },
tools: { weatherTool },
// Supervisor state and delegation traces need somewhere durable to land.
memory: new Memory({
storage: new LibSQLStore({ id: 'supervisor-store', url: 'file:./supervisor.db' }),
}),
});

Cuando consultas a este supervisor, analiza la solicitud y redirige según corresponda:

Este patrón escala mejor que intentar meter todo en un mega-agente único. Los agentes especializados desarrollan experiencia focalizada. El coordinador se encarga del enrutamiento. Cada pieza hace lo que se le da bien.


Poniéndolo Todo Junto

Los sistemas reales de IA en producción necesitan arquitectura, no solo prompts. Estás construyendo sistemas distribuidos donde algunos nodos resultan ser LLMs.

Los flujos de trabajo te dan garantías cuando necesitas que las cosas salgan exactamente bien. La memoria te da contexto sin quemar tu presupuesto de tokens. Los agentes supervisores te permiten componer complejidad a partir de partes más simples.

Nada de esto es glamoroso. Pero después de ver suficientes «agentes totalmente autónomos» fallar en producción, he aprendido a valorar más la confiabilidad aburrida que la imprevisibilidad emocionante.

Tu experiencia puede variar, pero en mi experiencia, los sistemas que realmente se lanzan y se mantienen funcionando son los que tratan a los LLMs como componentes de una arquitectura más grande, no como cajas mágicas que lo resuelven todo.

Recursos

Lee la Serie

  1. Enrutamiento de LLMs
  2. Seguridad y Salvaguardas
  3. Integraciones MCP y de Herramientas
  4. Flujos de Trabajo y Memoria (Este artículo)