volver al blog

Compactación de contexto: qué tirar cuando al agente se le acaba la ventana

agentescontextollm

El agente que se olvidó de por qué estaba ahí

Le pides a un agente que migre un módulo. Trabaja bien durante 30 turnos: lee ficheros, ejecuta los tests, corrige. En el turno 40 repite un cambio que ya había hecho. En el 50 te pregunta qué módulo era.

No se ha vuelto tonto. Se ha quedado sin sitio. Y el mecanismo que debía salvarlo, la compactación, acaba de tirar justo lo que hacía falta.

Compactar contexto no es resumir la conversación. Es decidir qué puede olvidar el agente sin dejar de ser capaz de terminar. Son dos problemas distintos y solo el segundo importa.

El contexto no es una cosa, son cuatro

Antes de comprimir conviene saber qué se está comprimiendo. En un agente en marcha la ventana se reparte, más o menos, en cuatro capas:

  • El encargo. La instrucción original y las restricciones que trae. Ocupa poco.
  • Las observaciones. Salidas de herramientas: ficheros leídos, stdout de los tests, respuestas HTTP. Es lo que crece sin control.
  • El razonamiento. Lo que el modelo se dijo a sí mismo entre llamada y llamada.
  • El estado. Qué se ha hecho ya, qué falló y por qué, qué queda.

Un resumen genérico las trata igual y las aplasta con el mismo peso. Ahí está el error. El encargo no se resume nunca. El estado se reescribe, no se resume. Las observaciones se tiran casi enteras. Y el razonamiento antiguo suele ser el primer candidato a la papelera: sirvió para tomar una decisión que ya está tomada y registrada en el estado.

Lo que ocupa el 80 % es lo que menos vale

Mide tu propio historial antes de diseñar nada. En los agentes de código que he instrumentado, la mayor parte de los tokens acumulados son salidas de herramientas. Y de esas, buena parte son ficheros que el agente leyó una vez, no volvió a consultar y siguen ahí 20 turnos después.

Ese es el hallazgo incómodo: casi nunca hace falta un resumidor más listo. Hace falta un recolector de basura.

La regla que más rendimiento da es también la más simple: una observación caduca; una decisión, no. Si el agente leyó config.py en el turno 8 y el fichero sigue igual, guardar sus 400 líneas no aporta nada por encima de la frase “config.py define DATABASE_URL y tres flags de features”. Y si el fichero ha cambiado, la copia vieja es peor que no tener nada: es una mentira que el modelo se va a creer.

Un compactador con presupuesto, no con umbral

La implementación ingenua dispara la compactación al llegar al 90 % de la ventana. Funciona hasta que un solo pytest -v de 60.000 tokens te lleva del 60 % al desbordamiento sin pasar por el 90 %.

Mejor idea: presupuesto por categoría, evaluado en cada turno.

from dataclasses import dataclass

@dataclass
class Fragmento:
    tipo: str            # "encargo" | "estado" | "razonamiento" | "observacion"
    turno: int
    tokens: int
    contenido: str
    resumen: str | None = None   # se calcula al entrar, no cuando sobra

# Reparto de la ventana útil, en tanto por uno. Suma 0.85 a propósito:
# el 15 % restante es el hueco para la respuesta del modelo.
PRESUPUESTO = {"encargo": 0.05, "estado": 0.15,
               "razonamiento": 0.20, "observacion": 0.45}

COSTE_RESUMEN = 60  # tokens que ocupa la versión degradada de un fragmento

def compactar(historial: list[Fragmento], ventana: int) -> list[Fragmento]:
    salida = []
    for tipo, cuota in PRESUPUESTO.items():
        limite = int(ventana * cuota)
        # Lo reciente vale más, así que se recorre de atrás hacia delante.
        gastado = 0
        for f in reversed([x for x in historial if x.tipo == tipo]):
            if gastado + f.tokens <= limite:
                salida.append(f)                    # cabe entero
                gastado += f.tokens
            elif f.resumen and gastado + COSTE_RESUMEN <= limite:
                salida.append(degradar(f))          # cabe su resumen
                gastado += COSTE_RESUMEN
            # Si no cabe ni el resumen, se cae del contexto. Sin drama.
    return sorted(salida, key=lambda f: f.turno)

Tres detalles, que es donde está el oficio:

  • La compactación se aplica antes de llamar al modelo, no después de que falle. Recuperarse de un error de contexto excedido cuesta un turno entero y desorienta al agente.
  • El resumen de una observación se calcula cuando entra, no cuando estorba. Si esperas a que estorbe, ya no te queda presupuesto para la llamada que genera ese resumen.
  • El presupuesto no suma 1. Reservar hueco para la salida no es opcional: una ventana llena al 100 % es una ventana sin respuesta.

Qué se rompe cuando compactas mal

Los fallos de compactación no se manifiestan como errores. Se manifiestan como un agente que trabaja peor, y eso es más difícil de diagnosticar.

El bucle. El agente repite una acción porque el registro de que ya la hizo se fue con el resumen. Se evita manteniendo el estado fuera del historial: una lista de acciones completadas que se reescribe en cada turno y nunca se resume.

La restricción evaporada. “No toques la carpeta legacy/” estaba en el turno 3 y ya no está. Por eso el encargo tiene cuota propia y se copia literal.

La confianza en datos rancios. El modelo cita un fichero como si acabara de leerlo cuando en realidad lee un resumen de hace 30 turnos. La cura es barata: en el resumen, incluir el turno de origen. “Leído en el turno 8” cambia cómo lo trata el modelo.

Las cuatro estrategias, comparadas

Ventana deslizanteResumen recursivoPresupuesto por categoríaMemoria externa
Qué conservaLos N últimos turnosUn resumen que se re-resumeLo reciente de cada capaLo que el agente decida releer
Coste extraNingunoUna llamada por compactaciónUn resumen barato por observaciónEscrituras y lecturas de fichero
Cómo fallaPierde el encargoDeriva: el resumen del resumen mienteMal calibrado, ahoga una capaEl agente no sabe qué releer
Cuándo usarlaChats cortosConversaciones sin herramientasAgentes con muchas herramientasTareas de horas

La cuarta columna merece un apunte. Volcar las observaciones a disco y dejar en contexto solo un índice (“informe.csv, 4.200 filas, columnas: fecha, importe, proveedor”) es la única estrategia que escala de verdad a tareas largas. También es la que más depende del criterio del modelo, porque le traslada a él la decisión de qué recuperar. Funciona bien combinada con la tercera, no en su lugar.

Por dónde empezar

No montes un sistema de compactación hasta tener el síntoma medido. La ruta corta es esta:

  1. Instrumenta antes de optimizar. Registra tokens por categoría en cada turno de tus trazas reales. Si el 80 % son observaciones, ya sabes dónde está tu problema y no necesitas nada sofisticado.
  2. Empieza por tirar, no por resumir. Descarta las observaciones repetidas y las lecturas de ficheros que no han cambiado. Es una comparación de hashes y suele recuperar más ventana que cualquier resumidor.
  3. Saca el estado del historial. Una estructura aparte que se reescribe en cada turno. Es el cambio que más bucles elimina.
  4. Mide en tareas completadas, no en tokens ahorrados. La métrica que importa es cuántos encargos termina el agente sin ayuda. Un compactador que ahorra un 40 % de contexto y baja esa cifra es un compactador roto.

Y la lección de fondo, que vale más allá de esto: la ventana de contexto no es un problema de almacenamiento, es un problema de gestión de producto. Estás decidiendo qué merece la pena recordar. Trátalo con ese criterio y la mitad de las decisiones técnicas se vuelven obvias.