Overflow de contexto: cuando tu agente olvida sus reglas
Tu agente supera todos los tests. Cinco turnos de conversación, caso feliz, resultados correctos. Lo despliegas a producción.
Tres días después, el agente procesa un pago de €8.000 sin pedir aprobación humana. La regla estaba en el system prompt: "Requiere confirmación para importes superiores a €5.000". El agente no la ignoró intencionadamente. Sencillamente, ya no la tenía en su contexto.
El problema que ningún test corto detecta
Un LLM tiene una ventana de contexto finita. Claude puede procesar hasta 200.000 tokens, lo que suena enorme. El problema no es el límite absoluto: es lo que ocurre cuando se acerca a él.
En un agente de larga duración —una sesión de soporte de 40 minutos, un flujo de trabajo de onboarding en múltiples pasos, un agente de facturación que procesa 30 registros seguidos— la ventana se llena. Cuando eso ocurre, los LLMs truncan los mensajes más antiguos. Sin avisar. Sin errores. Sin logs de advertencia por defecto.
Lo que puede caer:
- El system prompt completo, donde viven tus restricciones de negocio críticas
- Los resultados de herramientas de turnos anteriores, que hacen que el agente no recuerde qué ya ejecutó
- El contexto del usuario, que obliga al agente a hacer preguntas que ya fueron respondidas hace veinte turnos
El modelo no sabe que tiene el contexto truncado. Sigue respondiendo con confianza, ahora sin las restricciones que diseñaste.
Las tres señales de overflow en producción
1. El agente repite acciones que ya ejecutó
El resultado de la herramienta que confirmó la acción se perdió. El agente vuelve a invocarla porque, desde su perspectiva, aún no la ha completado.
En un agente de correo: envía el mismo recordatorio dos veces al mismo cliente. En un agente de base de datos: intenta crear un registro que ya existe.
2. El agente ignora restricciones que tenía
Restricciones que pusiste en el system prompt —"nunca borres registros", "requiere aprobación para importes altos", "no envíes datos a IPs externas"— desaparecen cuando ese prompt queda fuera de la ventana activa.
Este es el escenario más peligroso porque el agente no falla con un error. Falla ejecutando algo que debería haber bloqueado.
3. El agente responde como si no conociera el contexto anterior
Pregunta información que el usuario ya proporcionó. Repite un resumen del problema que ya se había resuelto. Se comporta como si la conversación acabara de empezar.
Cómo detectarlo antes de que cause daño
La primera línea de defensa es contar tokens antes de cada llamada. Anthropic proporciona una API específica para esto:
import Anthropic from '@anthropic-ai/sdk';
import type { MessageParam } from '@anthropic-ai/sdk/resources';
const client = new Anthropic();
async function checkContextUsage(
messages: MessageParam[],
systemPrompt: string
): Promise<{ tokens: number; percentFull: number; dangerZone: boolean }> {
const { input_tokens } = await client.messages.countTokens({
model: 'claude-opus-4-8',
system: systemPrompt,
messages,
});
const MAX_CONTEXT = 200_000;
const percentFull = (input_tokens / MAX_CONTEXT) * 100;
return {
tokens: input_tokens,
percentFull,
dangerZone: percentFull > 70, // actúa antes de que sea urgente
};
}
El umbral del 70% no es arbitrario. Quieres activar la estrategia de reducción antes de que el modelo empiece a truncar, no después.
Tres estrategias de gestión del contexto
Ventana deslizante (sliding window)
Descarta los mensajes más antiguos, manteniendo siempre los más recientes. Es la estrategia más simple, pero tiene un riesgo: si cortas en mitad de un par tool call / tool result, el modelo recibe una llamada a herramienta sin su resultado, lo que produce comportamientos impredecibles.
La regla: siempre mantén los pares tool_use / tool_result juntos. Nunca separes una invocación de su respuesta.
function applySlidingWindow(
messages: MessageParam[],
maxMessages = 20
): MessageParam[] {
if (messages.length <= maxMessages) return messages;
// Encontrar el primer mensaje seguro para truncar
// (nunca cortar en mitad de un par tool_use/tool_result)
let startIndex = messages.length - maxMessages;
while (
startIndex < messages.length &&
messages[startIndex].role === 'tool'
) {
startIndex++;
}
return messages.slice(startIndex);
}
Summarización periódica
Cuando el contexto supera el umbral, comprimes el historial anterior en un resumen estructurado y lo insertas como un nuevo mensaje de sistema. El modelo conserva la semántica de lo que ocurrió sin el peso de cada turno individual.
El riesgo aquí es perder precisión en el resumen. Para flujos críticos, el resumen debe incluir explícitamente el estado de cada acción: qué se ejecutó, qué resultado tuvo, qué sigue pendiente.
Estado separado de la conversación (el patrón recomendado)
Este es el cambio de arquitectura que resuelve el problema de raíz.
En lugar de confiar en que el modelo recuerde lo que ocurrió leyendo el historial, mantienes el estado del agente en una estructura separada que persiste en base de datos. Al inicio de cada turno, inyectas el estado actual como un mensaje de contexto fresco. La conversación solo necesita los últimos N mensajes porque el estado real vive fuera de ella.
interface AgentState {
taskId: string;
completedActions: string[];
pendingActions: string[];
constraints: string[]; // reglas críticas de negocio
userContext: Record<string, unknown>;
}
async function runAgentTurn(
state: AgentState,
recentMessages: MessageParam[],
userMessage: string
): Promise<{ response: string; updatedState: AgentState }> {
// Las restricciones críticas siempre están presentes, sin importar la longitud
const stateContext: MessageParam = {
role: 'user',
content: [
{
type: 'text',
text: `[ESTADO ACTUAL]\n${JSON.stringify(state, null, 2)}`,
},
],
};
// Solo los últimos 10 mensajes de conversación
const contextWindow: MessageParam[] = [
stateContext,
...recentMessages.slice(-10),
{ role: 'user', content: userMessage },
];
const response = await client.messages.create({
model: 'claude-opus-4-8',
max_tokens: 4096,
system: SYSTEM_PROMPT,
messages: contextWindow,
});
// Actualizar el estado en BD después de cada turno
const updatedState = await updateStateFromResponse(state, response);
await db.agentStates.upsert({ taskId: state.taskId, state: updatedState });
return { response: extractText(response), updatedState };
}
Con este patrón, las restricciones de negocio —"nunca procesar importes superiores a €5.000 sin aprobación"— están en state.constraints y se inyectan en cada turno. El context overflow deja de ser un vector de fallo.
Lo que esto cambia en producción
Un agente construido con estado separado puede tener conversaciones de 200 turnos con el mismo nivel de seguridad que una de 5. Las restricciones críticas nunca se pierden. El historial de acciones completadas siempre está disponible. El modelo no necesita recordar: lee el estado fresco en cada turno.
Este patrón complementa directamente los patrones descritos en los artículos sobre agentes stateful y Human-in-the-Loop: son tres capas de la misma arquitectura robusta. El estado separado resuelve el overflow. HITL resuelve las decisiones inciertas. La máquina de estados resuelve las transiciones inválidas.
Cómo lo implementamos en producción
En los proyectos de integración IA para empresas que construimos en DAILYMP, el diseño de la gestión de contexto forma parte del primer sprint, no de una iteración posterior cuando el agente ya ha fallado en producción.
El proceso:
- Estimamos el volumen de tokens por turno según el dominio
- Definimos el umbral de intervención (habitualmente el 70%)
- Elegimos la estrategia según la criticidad del flujo: sliding window para conversaciones de soporte, estado separado para cualquier flujo con acciones irreversibles
- Instrumentamos token counting para tener métricas de uso en el tiempo
El resultado: agentes que mantienen su comportamiento especificado independientemente de la longitud de la sesión.
Si tienes un agente de automatización en producción que empieza a comportarse de forma errática en sesiones largas, el context overflow es el primer candidato a investigar.
¿Tu agente tiene comportamiento inconsistente en sesiones largas? Cuéntame qué ocurre →