Lección 6: Práctica: cablear la gestión de contexto sobre el arnés
Objetivos de aprendizaje:
- Cablear el seguimiento del uso de tokens sobre un bucle de arnés guiado por
stop_reason: acumular con response.usage, decidir si el contexto se acerca al límite de la ventana
- Convertir el
compact() de la Lección 4 en un mecanismo disparado por umbral: fijar la proporción de disparo, pensar cómo deberían reiniciarse messages y el contador de uso después del disparo
- Cablear las notas estructuradas en este flujo de compactación para que
NOTES.md se lea de vuelta cada vez que una ventana nueva reinicia, y ejecutar una tarea que excede la capacidad de una sola ventana
Requisitos: Terminaste la Lección 4 sobre compactación y notas, la Lección 5 sobre aislamiento con subagentes, y tienes a mano el bucle del arnés del curso 7 de esta serie, «Fundamentos del arnés de agente: bucles y control» | Anterior: Lección 5 <<
Primero, verlo en marcha
Las primeras cinco lecciones fueron todas principios: por qué el contexto es un recurso finito, cómo funciona la compactación, cómo funcionan las notas, cómo aíslan los subagentes. Esta lección suelda los dos primeros — compactación y notas — dentro del bucle del arnés que escribiste en el curso 7 de esta serie. Primero mira cómo se ve en marcha, y después desarmamos el código.
Abajo hay un registro de ejecución real (usando un cliente de prueba que simula respuestas del modelo en varios turnos, para que una tarea larga quepa en unas pocas líneas de registro; la implementación del cliente de prueba aparece al final de esta lección). La tarea es aquel escenario familiar de la Lección 4: arreglar un bug de carrera por concurrencia en un servicio de pedidos. Para disparar la compactación en pocos turnos, la demostración fija a propósito una ventana de contexto diminuta:
Léelo línea por línea: las primeras tres llamadas empujan tokensUsed de 400 a 1050 a 1850; después de que los resultados de herramientas de la tercera ronda se anexan al historial, el valor acumulado cruza el umbral fijado, así que el arnés no espera a que la ventana reviente de verdad — dispara proactivamente una llamada de compactación (llamada 4; stop_reason ya no importa porque esta respuesta nunca entra al bucle principal, su salida se usa directamente para reiniciar messages); tokensUsed vuelve a cero; las dos rondas siguientes cuentan desde cero en la ventana nueva hasta que el modelo cierra. El proceso entero tiene un solo artefacto visible para el usuario — esa respuesta final; la compactación y las notas ocurren fuera de escena. El resto de esta lección es construir, línea por línea, el código detrás de ese registro.
I. Cablear el seguimiento del uso de tokens sobre el bucle
El paso uno es directo: saber cuántos tokens llevas usados, porque ese es el requisito previo para decidir si compactar. Ya usaste este campo cuando escribiste la válvula de presupuesto (válvula 2) en el curso 7 de esta serie — response.usage trae input_tokens y output_tokens de esta llamada, y cada vez que recibes una respuesta los sumas a un acumulador:
La válvula TOKEN_BUDGET del curso 7 usaba este valor acumulado para una sola cosa: parar al llegar al techo. Esta lección hace otra cosa: compactar proactivamente en una proporción mucho más temprana y seguir trabajando, en lugar de parar. Ambas usan el mismo acumulador; lo que pasa después del disparo es completamente distinto — una frena, la otra toma aire.
¿De qué tamaño es la ventana misma? Esa es una constante que defines con tu propio criterio de ingeniería:
No hay respuesta estándar sobre cuánto debería valer COMPACT_RATIO — es un criterio de ingeniería, no una cláusula de especificación. Fíjalo demasiado alto y, para cuando te des cuenta de que «toca compactar», la ventana podría estar tan apretada que ni siquiera puedas enviar la petición siguiente; fíjalo demasiado bajo y la compactación interrumpirá la tarea antes y más seguido de lo que debería, desperdiciando llamadas al modelo. Como regla práctica, dejar alrededor de un 30 % de aire (es decir, un umbral de 0.7) suele funcionar; el número concreto habría que ajustarlo según el tamaño real de la ventana de tu modelo y el volumen de resultados de herramientas por turno.
II. Compactación disparada por umbral: cablear el compact() de la Lección 4 al bucle
Con la mecánica fijada, el paso siguiente es cablearla en el cuerpo del bucle. Recuerda la conclusión de la Lección 4: la compactación no mete un resumen de vuelta en la conversación vieja para seguir apretando — reinicia una ventana nueva con el resumen, abandonando por completo el messages viejo1. Cableado al bucle, eso significa reemplazar messages en bloque en el momento correcto:
Tres posiciones determinan si este código es correcto:
- Dónde va la revisión: inmediatamente después de que los resultados de herramientas de esta ronda se anexan a
messages, antes del siguiente client.messages.create. Si va demasiado temprano (revisar antes de anexar), se pierde los resultados de herramientas recién producidos; si va demasiado tarde (anexar después de revisar), envías contenido ya por encima del umbral en una petición extra.
- Después de compactar,
messages se reemplaza en bloque, no se le anexa: el valor de retorno de compact() se asigna directamente a messages, y el arreglo viejo con sus decenas de idas y vueltas de herramientas se descarta — esa es la frontera entre «reiniciar» y «seguir amontonando sobre la conversación vieja».
tokensUsed debe volver a cero: la ventana nueva arranca desde un resumen, así que el uso debería contarse a partir de ese resumen y no seguir arrastrando el valor acumulado de la ventana vieja. Saltarse este paso es una trampa común; los ejercicios de esta lección la diagnostican específicamente.
compact() en sí reutiliza la implementación de la Lección 4; COMPACT_INSTRUCTION y los principios de clasificación (preservar decisiones arquitectónicas, bugs sin resolver y detalles clave de implementación; descartar resultados de herramientas redundantes) siguen igual1. La sección siguiente le agrega una capacidad nueva: al reiniciar, leer no solo el resumen sino también NOTES.md.
III. Notas estructuradas como respaldo: NOTES.md se lee de vuelta durante la compactación
La compactación es pasiva y a posteriori: resume «lo que queda en la ventana en el momento del disparo». La Lección 4 ya explicó que las notas son un seguro activo escrito sobre la marcha: el agente escribe decisiones y problemas en NOTES.md, fuera de la ventana, en el momento en que ocurren1. La manera de cablear las dos cosas es directa: cuando una ventana nueva reinicia, además de leer el resumen, leer también NOTES.md de vuelta — así, aunque la clasificación del resumen de esta ronda se haya equivocado, las notas siguen teniendo un respaldo independiente.
Primero, dale al agente una herramienta para escribir notas:
El input de update_notes es el contenido completo de la nota que se va a guardar, y la implementación lo escribe en bloque — esta es la semántica más simple: el agente mantiene un corpus de notas completo y cada actualización significa «este es el estado actual», sin ninguna fusión incremental que atender. Cablea un requisito en el prompt del sistema: «Cada vez que tomes una decisión importante, descubras un problema nuevo o completes una fase, llama a update_notes para actualizar las notas antes de continuar», tal como en la Lección 4.
Después viene el paso nuevo de esta lección: cuando compact() genera el resumen, además lee NOTES.md dentro del mensaje de reinicio:
Cuando la ventana nueva despierta, tiene dos materiales: el resumen del propio modelo, y las notas que el agente escribió a mano. El primero puede perder detalle por la clasificación del resumen; las segundas no pierden nada — eso es el «cuanto más diligentes las notas, más leves las consecuencias de que la compactación pierda algo» de la Lección 4, en forma de código.
IV. Armarlo todo: una tarea que excede la capacidad de una sola ventana
Tres componentes — seguimiento del uso, compactación disparada por umbral, lectura y escritura de NOTES.md — cableados dentro del mismo runAgent producen el código completo detrás del registro de apertura:
Esta es la fuente completa del registro de apertura: tres llamadas a herramientas empujan tokensUsed de 400 a 1050 a 1850, cruzando la línea del umbral 2000 * 0.7 = 1400; se llama a compact(), messages se reemplaza en bloque, el contador vuelve a cero; luego dos rondas más en la ventana nueva, y el modelo cierra. Solo hubo una compactación en toda la ejecución, pero si la tarea continuara y volviera a tocar el umbral, la misma lógica dispararía una segunda y una tercera — a shouldCompact no le importa de qué ventana se trata, solo mira el uso de la ventana actual. Eso es lo que significa «ejecutar una tarea que excede la capacidad de una sola ventana»: la longitud total de la tarea no está acotada por la capacidad de ninguna ventana en particular, solo por «una inferencia continua sin interrupciones».
Para ejecutar una verificación completa, engancha un cliente de prueba que simule respuestas del modelo en varios turnos (cámbialo por new Anthropic() en llamadas reales; el código de runAgent no cambia ni un carácter):
El valor de verificar con un cliente de prueba así es que fija «si el modelo llamará herramientas este turno, cuántos tokens usó» como cantidades conocidas, de modo que si la compactación se dispara y en qué turno, si tokensUsed vuelve a cero, si NOTES.md se escribe y luego se lee de vuelta — todo se puede comprobar con aserciones en lugar de entrecerrar los ojos ante salidas de llamadas reales y adivinar.
Sentido de la proporción: no toda tarea necesita esta maquinaria
Después de cablear todo esto, es fácil quedarse con una impresión falsa: que de ahora en adelante, escribir agentes debería incluir por defecto el paquete de seguimiento del uso, compactación por umbral y notas estructuradas. Vuelve al sentido de la proporción establecido en la Lección 2: considerar agregar complejidad solo cuando puede mejorar los resultados de forma demostrable2. Para tareas que terminan en una docena de turnos, la compactación y las notas son ambas piezas superfluas — arranca desde el bucle pelado más las válvulas de control básicas del curso 7 de esta serie, y agrega esta capa solo cuando de verdad choques con el límite de la ventana o veas síntomas de amnesia del tipo «la ventana nueva no sabe qué hizo la vieja».
Llegados a este punto, todo lo que este curso enseñó de la Lección 1 a la Lección 6 — presupuesto de atención, altitud de los prompts del sistema, recuperación justo a tiempo, compactación y notas, aislamiento con subagentes — converge en la misma conclusión: lo que el modelo debería ver en cada turno es siempre un criterio de ingeniería que revisas una y otra vez, no una configuración que dejas puesta de una vez y para siempre.
Resumen
- Cablear el seguimiento del uso sobre el arnés solo necesita un acumulador: cada vez que recibes una respuesta, sumas
response.usage.input_tokens + response.usage.output_tokens; comparte los mismos datos con la válvula TOKEN_BUDGET del curso 7, pero la acción posterior al disparo es distinta — la válvula de presupuesto para al tocar el techo; el umbral de esta lección compacta y sigue trabajando.
- La compactación disparada por umbral cablea el
compact() de la Lección 4 al cuerpo del bucle: la revisión va en «los resultados de herramientas de esta ronda ya anexados, antes de que salga la petición siguiente»; tras el disparo, messages se reemplaza en bloque con el resultado de la compactación — es un reinicio, no un anexado1; tokensUsed debe reiniciarse en sincronía, o caes en una tormenta de compactaciones repetidas.
- Las notas y la compactación se cablean juntas en esta lección: la herramienta
update_notes escribe sobre la marcha — eso es persistir notas fuera de la ventana de contexto1 — y compact() lee NOTES.md dentro del mensaje de reinicio además de generar el resumen; el resumen puede perder contenido por la clasificación, las notas se leen de vuelta como copia sin pérdidas. «Leer una sola vez en momentos críticos como el reinicio de la ventana, no meterlas en el prompt del sistema de cada turno» es la disyuntiva de ingeniería de esta lección, basado en el principio del presupuesto de atención (cada nuevo token agota ese presupuesto1).
- Una verificación real de punta a punta muestra: tres llamadas a herramientas empujan el uso de 400 a 1850, cruzan el umbral y disparan una compactación, el contador se reinicia, y luego dos rondas más para cerrar — la longitud total de la tarea ya no está acotada por la capacidad de una sola ventana, solo por «una inferencia continua sin interrupciones».
- No trates esta maquinaria como configuración por defecto: agrégala solo cuando la complejidad extra pueda mejorar los resultados de forma demostrable2; para tareas que terminan en unos pocos turnos, el bucle pelado más las válvulas de control básicas del curso 7 de esta serie alcanzan.