← Todo el material
Curso Intermedio 23 de agosto de 2026

El Harness — Clase 2: ingeniería de contexto

La ventana de contexto no es un buzón donde vaciar información: es un presupuesto que se asigna. Segunda clase de la serie, dedicada a la capa de Contexto: cómo repartir tokens por sección, cómo aprovechar el prompt caching de verdad, cuándo compactar el historial y cómo podar resultados de tools antes de que se coman el agente.

☰ Índice del contenido

En la Clase 1 recorrimos las 7 capas del harness como un mapa. De la capa de Contexto dijimos una sola frase, y era esta:

“La información correcta en el momento correcto. Elegir qué ve el modelo en cada turno es media batalla.”

Esa frase suena obvia hasta que la miras de cerca. Porque la pregunta real no es “¿cabe?” — hoy casi todo cabe: las ventanas de contexto llegan a un millón de tokens. La pregunta real es cuánto de lo que cabe deberías meter, en qué orden, y qué haces cuando la conversación lleva cuarenta turnos y el agente empezó a ignorar tus reglas.

Un agente en producción no tiene un problema de espacio. Tiene un problema de asignación. Cada turno alguien decide qué entra a la ventana: las instrucciones permanentes, las definiciones de las 42 tools, el historial completo, los tres documentos que recuperó el buscador y los 8.000 tokens de JSON que devolvió la API de calendario. Si nadie diseñó ese reparto, lo está decidiendo el azar — y el azar cobra por token.

Esta clase es sobre diseñar ese reparto.

La tesis de la clase

El contexto no es un buzón donde vaciar información: es un presupuesto que se asigna. Que quepa en la ventana no significa que deba estar en la ventana — cada token que metes compite con los demás por la atención del modelo, y los que sobran no son neutros: te cuestan plata y te degradan la respuesta.

1. El límite técnico ya lo cubrimos: aquí va la ingeniería

Antes de seguir, un deslinde. Por qué una ventana larga cuesta cara y por qué el modelo “pierde” cosas en la mitad es un tema técnico, de arquitectura del Transformer, y está desarrollado en Transformers — Módulo 10: contexto largo y KV cache. Ahí se explica el costo cuadrático de la atención, qué es la KV cache (la memoria interna que evita recalcular tokens ya procesados) y el fenómeno lost in the middle: la información que queda a la mitad de un contexto largo se recupera peor que la del principio o el final.

No lo repito. Doy por sabido el límite y me paro encima de él: dado que el contexto largo cuesta y degrada, ¿cómo repartes el que sí vas a usar? Eso es ingeniería de contexto.

Un deslinde más, para que no se cruce con otra clase: cómo se guarda información entre sesiones —memoria de largo plazo, embeddings, promoción de datos— es la capa de Memoria, y está en Fundamentos: memoria de agentes. De hecho, ahí hay una tabla que lista cuatro estrategias de memoria de corto plazo (últimos N turnos, presupuesto de tokens, resumen incremental, híbrida) en una línea cada una. Esta clase es el desarrollo completo de esa tabla. Lo que allá era una fila, aquí es código.

2. La ventana no es un espacio: son cinco espacios distintos

El error de base es imaginar la ventana de contexto como una caja vacía. No lo es. Todo lo que le mandas al modelo en un turno cae en una de cinco secciones, y cada una tiene naturaleza distinta: cambia con distinta frecuencia, se puede cachear o no, se puede podar o no, y vale distinto cuando hay que tirar algo por la borda.

┌──────────────────────────────────────────────────────────┐
│              VENTANA DE CONTEXTO (1 turno)               │
├──────────────────────────────────────────────────────────┤
│ 1 │ SYSTEM PROMPT      rol, reglas, formato   [estable]  │
├───┼──────────────────────────────────────────────────────┤
│ 2 │ DEFINICIONES TOOLS nombre + schema x N    [estable]  │
├───┼──────────────────────────────────────────────────────┤
│ 3 │ HISTORIAL          turnos previos         [crece]    │
├───┼──────────────────────────────────────────────────────┤
│ 4 │ DOCUMENTOS         chunks recuperados     [variable] │
├───┼──────────────────────────────────────────────────────┤
│ 5 │ RESULTADOS TOOLS   JSON crudo de APIs     [explota]  │
├───┴──────────────────────────────────────────────────────┤
│     RESERVA DE SALIDA   lo que el modelo va a escribir   │
└──────────────────────────────────────────────────────────┘

Fíjate en la última fila: la reserva de salida. Es el error de principiante más caro. La ventana de contexto es compartida entre lo que entra y lo que sale. Si llenas 199.000 tokens de una ventana de 200.000, el modelo tiene 1.000 tokens para responder y te va a cortar la respuesta a la mitad. Siempre se reserva el espacio de salida antes de repartir el resto.

Y estas son las cinco secciones con su comportamiento:

#SecciónNaturaleza¿Se cachea?¿Se poda?Prioridad al descartar
1System promptEstable entre llamadasSí, idealCasi nuncaÚltima (es lo que define al agente)
2Definiciones de toolsEstable si el set es fijoSí, va primeroSí, por fasePenúltima
3HistorialCrece cada turnoParcialmente (prefijo)Sí, compactandoMedia — los turnos viejos primero
4Documentos recuperadosCambia cada consultaRara vezSí, por relevanciaAlta si hay muchos chunks
5Resultados de toolsImpredecible, puede explotarNoSiemprePrimera (el JSON crudo sobra)

La columna que más gente ignora es “¿Se cachea?”. Y es la que más plata mueve. Vamos a ella.

3. Prompt caching de verdad

El prompt caching es la capacidad de decirle al proveedor: “esta parte del prompt es la misma que la vez pasada, no la vuelvas a procesar desde cero”. En la API de Anthropic se marca poniendo cache_control en un bloque de contenido:

system=[
    {
        "type": "text",
        "text": INSTRUCCIONES_LARGAS,
        # Marca el punto de corte del caché. "ephemeral" = TTL corto
        # (5 minutos por defecto). Todo lo que está ANTES de esta
        # marca, incluido este bloque, es el prefijo cacheable.
        "cache_control": {"type": "ephemeral"},
    }
]

Tres cosas que hay que entender bien, porque la mayoría de las implementaciones se equivoca en las tres.

3.1 El caché es por prefijo, no por bloque

No cacheas “el system prompt”. Cacheas todo lo que va desde el inicio de la petición hasta la marca. El orden en que la API arma la petición es fijo:

   tools ──► system ──► messages

Entonces, si pones la marca en el system prompt, quedan cacheados las tools y el system. Si pones la marca en el primer mensaje, queda cacheado todo lo anterior más ese mensaje.

Y aquí viene la consecuencia práctica: si cambia un solo carácter en cualquier punto anterior a la marca, el caché entero se pierde. No se pierde “un pedazo”. Se pierde desde el punto del cambio hacia adelante.

     LLAMADA 1                      LLAMADA 2
┌──────────────────┐          ┌──────────────────┐
│ tools   (5.000)  │ escribe  │ tools   (5.000)  │ lee
│ system  (2.000)  │ ──► CACHÉ│ system  (2.000)  │ ──► CACHÉ
├──────────────────┤          ├──────────────────┤
│ historial (800)  │          │ historial (950)  │ procesa
└──────────────────┘          └──────────────────┘  normal

    7.000 tokens al precio de escritura, una sola vez.
    En la llamada 2 se releen baratos y solo pagas los 950.

           ┌─── cambias UNA palabra del system ───┐
           ▼                                      ▼
┌──────────────────┐                    ┌──────────────────┐
│ tools   (5.000)  │  el prefijo ya no  │ 7.000 tokens de  │
│ system  (2.001)  │  coincide ──►      │ nuevo a precio   │
└──────────────────┘                    │ de escritura     │
                                        └──────────────────┘

3.2 Estable primero, variable después

De lo anterior sale la regla de oro del orden: lo que no cambia va arriba; lo que cambia cada llamada va abajo. Si metes la fecha de hoy, el nombre del usuario o un contador de turnos dentro del system prompt, estás poniendo algo variable en la zona estable, y matas el caché de todo lo que está debajo.

Ojo: un timestamp o un contador en el system prompt invalida el prefijo cacheado en cada llamada. Es el error más caro y más silencioso de todos: no lanza ningún error, no rompe nada, la respuesta sale igual de bien — simplemente pagas el prefijo completo a precio de escritura, turno tras turno, para siempre. Si tu agente tiene un "Hoy es {datetime.now()}" arriba, ese agente nunca ha usado caché ni un solo turno.

Otros invalidadores silenciosos del mismo tipo: un uuid4() de sesión en las instrucciones, un json.dumps() de configuración sin sort_keys=True (el orden de las claves puede variar entre ejecuciones), un bloque de system que aparece solo bajo cierta condición, o un set de tools que se arma distinto según el usuario.

La solución es de una línea: el dato variable no va en el system prompt, va en el último mensaje de usuario — después de la marca de caché, donde no le hace daño a nadie.

Dos facilidades recientes de la API de Anthropic simplifican esto. Puedes poner una sola marca cache_control a nivel de la petición y la API coloca el punto de corte en el último bloque cacheable y lo va corriendo a medida que la conversación crece. Y en los modelos más nuevos puedes agregar una instrucción de sistema a mitad de conversación como un mensaje con role: system dentro de messages, en vez de editar el system de arriba: el prefijo cacheado no se toca.

3.3 La aritmética

Los precios exactos dependen del modelo y del proveedor, y cambian; los del catálogo actual están en Fundamentos: modelos de IA y costos. Lo que sí es estructural y conviene tener en la cabeza es la forma de la aritmética en la API de Anthropic:

OperaciónCosto relativo al input normalCuándo ocurre
Escritura de caché (TTL 5 min)1,25×Primera llamada con ese prefijo
Escritura de caché (TTL 1 hora)2×Primera llamada, si pides TTL largo
Lectura de caché~0,1× (en los modelos más nuevos baja hasta 0,025×–0,05×)Cada llamada posterior que acierte
Input normal (sin caché)1×Todo lo que va después de la marca

Con esos números, escribir el caché se paga solo a partir de la segunda llamada con TTL de 5 minutos, y de la tercera con TTL de una hora. Para un agente conversacional —donde el mismo prefijo de tools y system se repite en cada turno de la sesión— el caché no es una optimización opcional: es la diferencia entre un costo lineal y un costo casi plano.

Cuidado: el caché tiene un mínimo de tokens para activarse, y ese mínimo depende del modelo (entre 512 y 4.096 tokens de prefijo según la familia; revísalo en la tabla de la documentación de tu proveedor). Si tu system prompt tiene 300 tokens, marcarlo no hace nada — ni error, ni ahorro. Verifica siempre en la respuesta de la API los campos de uso: te dicen cuántos tokens se escribieron al caché, cuántos se leyeron y cuántos se procesaron normal. Si el contador de lectura sale en cero turno tras turno, tu caché no está funcionando y estás pagando de más sin saberlo.

4. Presupuesto de tokens por sección

Ahora sí, el corazón práctico: asignar explícitamente cuántos tokens le toca a cada sección, en vez de dejar que la primera que llegue se lleve todo.

Un ejemplo ilustrativo con una ventana de 200.000 tokens (los números son un reparto de diseño, no una medición):

SecciónPresupuesto%Qué pasa si se pasa
Reserva de salida8.0004%Se reserva primero, no se toca
System prompt2.0001%Se reescribe, no se recorta en caliente
Definiciones de tools12.0006%Se reduce el set de tools expuestas
Historial40.00020%Se compacta (sección 5)
Documentos recuperados24.00012%Se bajan los chunks por relevancia
Resultados de tools14.0007%Se podan (sección 6)
Colchón sin asignar100.00050%Margen para picos y para no rozar el límite

El colchón del 50% no es desperdicio: es la respuesta a la lección del Módulo 10 de Transformers. Llenar la ventana degrada la respuesta antes de que la ventana se llene. El presupuesto no busca aprovechar el máximo; busca operar cómodo bien por debajo.

Y esto se implementa con un constructor de prompt que reparte y descarta con prioridad explícita:

from dataclasses import dataclass, field


@dataclass
class Seccion:
    """Una parte del contexto, con su cuota y su prioridad."""
    nombre: str
    presupuesto: int          # tokens máximos que puede ocupar
    prioridad: int            # 1 = se descarta primero, 5 = se salva
    bloques: list = field(default_factory=list)


def contar_tokens(texto: str) -> int:
    """Aproximación rápida: ~4 caracteres por token en español.

    Para presupuestar en producción usa el endpoint de conteo de
    tokens del proveedor o el tokenizador real del modelo: esta
    heurística sirve para diseñar el reparto, no para facturar.
    """
    return len(texto) // 4


def construir_contexto(secciones: list[Seccion], limite: int,
                       reserva_salida: int) -> list[dict]:
    """Arma el contexto respetando cuotas y descartando por prioridad.

    Regla 1: la reserva de salida se descuenta ANTES de repartir.
    Regla 2: cada sección se recorta a su propia cuota.
    Regla 3: si aun así no cabe, se sacrifican las secciones de
             menor prioridad completas, no a medias.
    """
    disponible = limite - reserva_salida
    salida, usado = [], 0

    # Primero recortamos cada sección a su cuota individual.
    recortadas = []
    for sec in secciones:
        acumulado, dentro = 0, []
        for bloque in sec.bloques:
            costo = contar_tokens(bloque["text"])
            if acumulado + costo > sec.presupuesto:
                break            # lo que sobra de ESTA sección se cae
            dentro.append(bloque)
            acumulado += costo
        recortadas.append((sec, dentro, acumulado))

    # Luego, si el total sigue sin caber, sacrificamos secciones
    # enteras empezando por la prioridad más baja.
    total = sum(c for _, _, c in recortadas)
    for sec, dentro, costo in sorted(recortadas, key=lambda x: x[0].prioridad):
        if total <= disponible:
            break
        total -= costo
        dentro.clear()
        print(f"[contexto] sacrificada la sección '{sec.nombre}'")

    # Finalmente ensamblamos en el orden estable ► variable.
    for sec, dentro, _ in recortadas:
        for bloque in dentro:
            salida.append(bloque)
            usado += contar_tokens(bloque["text"])

    print(f"[contexto] {usado}/{disponible} tokens usados")
    return salida

Lo importante del código no es el detalle: es que la decisión de qué se cae está escrita en alguna parte. En la mayoría de los agentes que fallan por contexto, esa decisión no existe — simplemente se concatena todo y se reza.

5. Compactación del historial

El historial es la única sección que crece sola. Cada turno agrega la pregunta del usuario, la respuesta del modelo y, si hubo tools, la llamada y su resultado. Un agente que trabaja media hora acumula decenas de miles de tokens sin que nadie haya decidido nada.

Compactar es reemplazar un tramo de historial viejo por un resumen y seguir. La mecánica:

    turnos 1..N acumulándose
            │
            ▼
   ┌────────────────────┐   no    ┌──────────────────┐
   │ ¿historial > 70%   │ ──────► │ seguir normal    │
   │  de su cuota?      │         └──────────────────┘
   └─────────┬──────────┘
             │ sí
             ▼
   ┌────────────────────────────────────────────┐
   │ separar: tramo VIEJO  |  últimos K turnos  │
   └─────────┬──────────────────────────────────┘
             ▼
   ┌────────────────────────────────────────────┐
   │ resumir el tramo viejo en campos fijos     │
   └─────────┬──────────────────────────────────┘
             ▼
   ┌────────────────────────────────────────────┐
   │ historial = [resumen] + últimos K turnos   │
   └─────────┬──────────────────────────────────┘
             │
             └──────► vuelve al ciclo normal

Dos decisiones de diseño valen más que el código.

Cuándo disparar. No cuando el historial llena su cuota, sino antes — al 70% u 80%. Compactar es una llamada extra al modelo: si esperas al 100%, la compactación misma no cabe.

Qué se pierde. Todo resumen pierde algo; la pregunta es si pierde lo importante. Y aquí está la diferencia que casi nadie hace: un resumen en prosa pierde datos operativos; un resumen estructurado en campos, no.

EnfoqueQué conservaRiesgo
Resumen en prosa libreEl “de qué se hablaba”El modelo omite IDs, rutas, decisiones ya tomadas
Resumen en campos fijosObjetivo, hechos, decisiones, pendientes, IDsMás rígido, pero nada crítico se cae en silencio
Solo últimos N turnosEl contexto inmediatoSe pierde el objetivo original de la sesión
Híbrido (campos + últimos K)AmbosNinguno relevante; es el estándar razonable

El resumen estructurado se consigue pidiendo campos explícitos:

PLANTILLA_RESUMEN = """Resume la conversación anterior en estos campos.
Si un campo no aplica, escribe "ninguno". No inventes datos.

OBJETIVO: qué pidió el usuario originalmente, en una frase.
HECHOS: datos concretos establecidos (IDs, nombres, rutas, cifras).
DECISIONES: qué se decidió y descartó, con su razón.
PENDIENTES: qué falta por hacer.
RESTRICCIONES: reglas que el usuario impuso durante la sesión.
"""


def compactar(historial: list[dict], cliente, modelo: str,
              conservar_ultimos: int = 6) -> list[dict]:
    """Sustituye el historial viejo por un resumen estructurado.

    Conserva íntegros los últimos `conservar_ultimos` turnos: son
    los que el modelo necesita literales para no perder el hilo
    inmediato. Todo lo anterior se comprime.
    """
    if len(historial) <= conservar_ultimos:
        return historial

    viejo = historial[:-conservar_ultimos]
    reciente = historial[-conservar_ultimos:]

    transcripcion = "\n".join(
        f"{m['role']}: {m['content']}" for m in viejo
    )

    respuesta = cliente.messages.create(
        model=modelo,
        max_tokens=1024,
        system=PLANTILLA_RESUMEN,
        messages=[{"role": "user", "content": transcripcion}],
    )
    resumen = respuesta.content[0].text

    # El resumen entra como turno de usuario marcado, para que el
    # modelo lo distinga de lo que dijo el usuario de verdad.
    cabecera = {
        "role": "user",
        "content": f"[RESUMEN DE LA CONVERSACIÓN PREVIA]\n{resumen}",
    }
    return [cabecera] + reciente

Ojo: compactar invalida el caché del historial desde el punto de corte, porque acabas de reescribir mensajes que antes eran fijos. Es un costo real y esperado. Por eso no se compacta cada dos turnos: se compacta cuando el ahorro sostenido compensa la reescritura puntual.

Si trabajas con LangGraph, esta lógica vive junto a los checkpointers — está en LangGraph — Módulo 6: persistencia y memoria, que además tiene un aviso específico sobre historiales largos.

6. Poda de resultados de tools

Esta es la sección que menos gente vigila y la que más rápido revienta un agente.

Cuando el modelo llama a una tool, el resultado vuelve crudo. Una API de calendario devuelve, por cada evento, el ID, los timestamps en tres formatos, el estado, el organizador, cada asistente con su correo, su estado de respuesta y su rol, los enlaces de conferencia, los recordatorios, el color, la visibilidad y una docena de campos más. Miles de tokens. De los cuales al modelo le sirven la hora, el título y con quién.

Y eso se acumula: cada llamada deja su resultado en el historial, y en el turno veinte el agente arrastra veinte respuestas crudas que ya no le sirven a nadie.

 RESPUESTA CRUDA DE LA TOOL        LO QUE EL MODELO NECESITA
┌─────────────────────────┐      ┌─────────────────────────┐
│ id, etag, status, link  │      │                         │
│ created, updated, title │      │  10:00  Revisión Q3     │
│ start{dateTime, zone}   │ ──►  │         con Ana, Luis   │
│ end{dateTime, zone}     │      │                         │
│ attendees[{email, ...}] │      │  15:30  1:1 con Marcela │
│ organizer, reminders    │      │                         │
│      ~4.200 tokens      │      │       ~40 tokens        │
└─────────────────────────┘      └─────────────────────────┘

Cuatro estrategias, según el caso:

EstrategiaCómo funcionaCuándo usarla
Proyección de camposLa tool devuelve solo las claves de una lista blancaSiempre que conozcas el schema de la API
Truncado con marcaCortar a N tokens y avisar [...truncado, M items más]Listas largas y homogéneas
Resumen por modeloUn modelo barato resume el resultado antes de insertarloTexto libre grande (páginas, documentos)
Vaciado retroactivoReemplazar resultados viejos por [resultado descartado]Resultados ya usados que nadie volverá a leer

La proyección de campos es la que más rinde y la más fácil:

CAMPOS_UTILES = {
    "calendario_listar": ["summary", "start", "end", "attendees"],
    "correo_buscar": ["from", "subject", "date", "snippet"],
    "crm_buscar": ["id", "nombre", "etapa", "monto"],
}

LIMITE_ITEMS = 20


def podar_resultado(nombre_tool: str, resultado: list[dict]) -> dict:
    """Deja solo los campos útiles y acota la cantidad de ítems.

    Devuelve también cuántos ítems se omitieron: el modelo necesita
    SABER que hay más, o asumirá que la lista está completa y
    responderá con seguridad sobre datos que nunca vio.
    """
    campos = CAMPOS_UTILES.get(nombre_tool)
    if campos is None:
        return {"items": resultado[:LIMITE_ITEMS], "omitidos": 0}

    recortados = [
        {k: item.get(k) for k in campos if k in item}
        for item in resultado[:LIMITE_ITEMS]
    ]
    return {
        "items": recortados,
        "omitidos": max(0, len(resultado) - LIMITE_ITEMS),
    }

Cuidado: al truncar, dile siempre al modelo que truncaste. Un resultado recortado en silencio es peor que uno grande: el modelo cree que vio la lista completa y afirma con total seguridad que “no hay más reuniones esa semana”. Truncar sin avisar no ahorra tokens, fabrica alucinaciones.

El caso equivalente en RAG —cuántos fragmentos recuperados meter y en qué orden— está resuelto en RAG — Módulo 9: generación y prompting, incluido el código para mitigar el lost in the middle poniendo lo más relevante en los extremos. Aquí solo agrego la regla general: el orden dentro del contexto importa tanto como el contenido, y lo más importante nunca va enterrado al medio.

7. Selección dinámica de tools

Un agente con 42 tools paga las 42 definiciones en cada llamada, aunque en esa fase de la conversación solo pueda usar tres. Con schemas detallados, eso puede ser fácilmente el bloque más grande del prefijo.

La respuesta obvia —“expón solo las tools de esta fase”— tiene una trampa: las definiciones de tools van primero en el orden de la petición, así que cambiar el set invalida todo el caché. Cambiar tools para ahorrar tokens y perder el caché por eso puede salir más caro que no hacer nada.

Por eso la selección dinámica se hace por fases largas, no turno a turno:

Fase del agenteTools expuestasCuándo cambia
ExploraciónBúsqueda, lectura, consulta (solo lectura)Al confirmar un plan
EjecuciónEscritura, creación, envíoAl terminar la tarea
ReporteLectura del log, formato de salidaAl cerrar la sesión

Tres cambios de set en una sesión completa es asumible: pagas tres escrituras de caché. Tres cambios por turno no lo es.

Existe además un enfoque donde las definiciones se agregan al final en vez de reemplazar el set inicial: al no tocar el prefijo, el caché sobrevive. En la API de Anthropic esto llegó en septiembre de 2026 como beta (la cabecera inline-tools-2026-09-15): permite sumar o cambiar una tool a mitad de conversación sin editar tools. Si tu proveedor lo ofrece, es la mejor de las dos opciones.

8. Context rot: cuando el agente deja de obedecer

Este fenómeno tiene nombre desde hace poco —context rot, la podredumbre del contexto: lo midió un estudio de Chroma en 2025 sobre 18 modelos y lo adoptó Anthropic en su guía de ingeniería de contexto— y es el que más consultas de soporte genera.

El síntoma: al turno 3 el agente respeta perfectamente tus reglas del system prompt. Al turno 25, empezó a ignorarlas. No cambió el prompt. No cambió el modelo. Simplemente entre las instrucciones y el momento de responder ahora hay 30.000 tokens de conversación, resultados de tools y documentos — y las instrucciones quedaron enterradas arriba, lejos, compitiendo por atención contra todo lo demás.

   TURNO 3                        TURNO 25
┌──────────────┐              ┌──────────────┐
│ instrucciones│              │ instrucciones│ ◄── lejos
├──────────────┤              ├──────────────┤
│ historial    │              │              │
└──────┬───────┘              │  historial   │
       │ cerca                │  + tools     │
       ▼                      │  + docs      │
  responde bien               │              │
                              └──────┬───────┘
                                     ▼
                              ignora la regla

Los síntomas son reconocibles y cada uno tiene su mitigación:

SíntomaCausa probableMitigación
Deja de respetar el formato de salidaInstrucciones enterradasRecordatorio de formato en el último mensaje
Repite una tool que ya ejecutóEl resultado viejo quedó ilegible entre ruidoPodar resultados y mantener un estado explícito
Olvida una restricción del usuarioSe compactó y el resumen la perdióCampo RESTRICCIONES en el resumen estructurado
Contradice algo que dijo hace 10 turnosEse tramo quedó en la zona media del contextoCompactar más agresivo; subir lo crítico al final
Calidad que baja de forma sostenidaContexto simplemente demasiado largoBajar cuotas; cerrar y reabrir sesión con resumen

La mitigación general más efectiva es barata y suena tonta: repetir las 2 o 3 reglas críticas en el último mensaje, justo antes de que el modelo responda. Cuesta 50 tokens, va después de la marca de caché (así que no invalida nada) y aprovecha que el final del contexto es la zona de mejor atención.

Y lo mejor de todo: el context rot se mide. La métrica de degradación por turno está en Evals — Módulo 7: evals multiturno, con una regla que resume esta clase entera: si la calidad cae sistemáticamente después del turno 6, tu problema es la gestión del historial. Si no lo mides, estás adivinando.

Errores comunes

  • No reservar espacio de salida. Llenas la ventana hasta el borde y el modelo corta la respuesta a media frase. La reserva se descuenta primero, siempre, antes de repartir cualquier otra cosa.
  • Un datetime.now() o un contador en el system prompt. Invalida el prefijo cacheado en cada llamada. No falla, no avisa: solo cobra. Todo dato variable va después de la marca de caché, en el último mensaje.
  • Marcar caché en un prefijo demasiado corto. Bajo el mínimo del modelo, la marca no hace nada. Verifica los contadores de uso de la respuesta: si el de lectura sale cero turno tras turno, no hay caché.
  • Insertar resultados de tools crudos. Miles de tokens de JSON de los que veinte importan, acumulándose turno a turno. Proyecta campos con lista blanca en la propia tool, no después.
  • Truncar sin avisar. El modelo asume que vio la lista completa y afirma cosas falsas con seguridad. Todo recorte lleva su marca de “hay N más”.
  • Compactar en prosa libre. El resumen suena bien y perdió el ID del ticket, la ruta del archivo y la restricción que puso el usuario. Resumen en campos fijos, siempre.
  • Compactar tarde. Si esperas a que el historial llene su cuota, la llamada de compactación ya no cabe. Se dispara al 70-80%.
  • Cambiar el set de tools cada turno. Ahorras tokens de definición y pierdes el caché completo del prefijo. Cambia por fases largas, no por turno.
  • Confundir “cabe” con “conviene”. Que la ventana sea de un millón de tokens no es una invitación a llenarla. La degradación empieza mucho antes del límite.

La frase para llevarse

“La ventana de contexto no se llena: se presupuesta. Cada token que metes sin haberlo decidido lo decidió el azar — y el azar cobra por token.”

Qué viene

Ya sabemos elegir qué ve el modelo. La Clase 3 va a la capa de Gobernanza y autonomía: qué se le permite hacer. Clasificación de acciones por riesgo, control de permisos por tool (RBAC — quién puede ejecutar qué), aprobación asíncrona para las acciones que no se pueden deshacer, log de auditoría y revocación en caliente cuando un agente en producción empieza a comportarse mal.

Porque un agente con contexto perfecto y sin gobernanza es un agente muy bien informado haciendo exactamente lo que no debía.

Siguiente: El Harness — Clase 3: gobernanza y autonomía

Material relacionado

HarnessContextoAgentes IAPrompt cachingTokens

Preguntas sobre este curso

Regístrate gratis para hacer preguntas y guardar tu progreso.

✉ Entérate de lo nuevo

Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.