Compress II: compaction
implementarás un nodo de compaction que preserva de forma explícita decisiones, estado y problemas abiertos. Demostrarás, con un caso de control, que la compaction ingenua rompe lo que la tuya no. Importa porque tirar el historial pierde decisiones, y el agente no sabe que las perdió.
3.1El problema
En N1·L2 le pusiste a Magallanes una ventana deslizante. La curva subió. Cerraste el ticket.
Ahora subes el sweep a 50 documentos inyectados y Magallanes vuelve a las andadas. Re-lee documentos que ya había leído. Su sección 2 contradice lo que escribió en la sección 1. Es el mismo run muerto de el-mito-de-la-ventana-infinita, ahora con la ventana limpia.
Aquí está la trampa. El trimming de L2 funcionó porque tiró lo viejo. Pero lo que tiró no era ruido: eran las decisiones tempranas. Qué doc_ids ya había leído. Qué subtemas daba por cerrados. Qué ángulo había elegido para el informe. La ventana deslizante no distingue un documento agotado de una decisión que aún rige el trabajo. Los borra a los dos.
El loop ReAct append-only —cada turno apila su resultado y nada se va— produce "unbounded context growth… prohibitive inference memory costs and reasoning degradation" (ACON, corpus A.6). El trimming corta ese crecimiento por longitud. Pero en un encargo long-horizon como el de Magallanes, cortar por longitud corta justo lo que el agente necesita recordar.
Tirar no basta. Lo que no se puede tirar hay que destilarlo. Eso es compaction, y es esta lección.
3.2Qué vas a poder hacer
Al terminar serás capaz de:
- Implementar un nodo de compaction en el grafo de Magallanes: umbral → resumen con preservación explícita → reemplazo del historial.
- Escribir un prompt de preservación que conserve decisiones, estado del informe, subtemas pendientes y doc_ids ya leídos.
- Demostrar con un A/B controlado que la compaction ingenua rompe (repite, se contradice) lo que la tuya no.
- Predecir qué fallo de la taxonomía de N0 aparecerá tras compactar con un prompt dado, y corregir el peor.
Necesitas saber antes:
- De N1·L2 (Compress I: trimming): qué pierde exactamente el trimming —decisiones tempranas, el encargo, doc_ids ya leídos— y en qué tramo de la curva se nota. Lo recuperamos en 3.3.
- De N1·L1 (Cuatro palancas): qué decía el plan de intervención —qué síntoma trataba esta palanca. También en 3.3.
- El esqueleto congelado de Magallanes: el grafo de LangGraph con
MessagesState, sus 3 tools (buscar,leer,escribir_seccion) y el reduceradd_messages.
3.3Recupera
Antes de seguir, responde de memoria. Esto reactiva L1 y L2 y engancha lo nuevo.
- De L2: el trimming con
strategy='last'tira lo anterior a la ventana. ¿Qué tres cosas concretas pierde en un encargo long-horizon? - De L1: en el plan de intervención, ¿a qué palanca asignaste el síntoma de distraction (repetir búsquedas ya hechas) y por qué iba primera?
- El trimming borra mensajes del estado con un objeto concreto. ¿Cuál, y qué exige para funcionar?
Comprueba tu respuesta
- Pierde las decisiones tempranas (qué ángulo eligió, qué subtemas cerró), el encargo original si no lo blindas con
include_system, y los doc_ids ya leídos (sin esa memoria, vuelve a leerlos). · fuente: N1·L2. - A compress — distraction es historial saturado, y compress retiene "only the tokens required" (Lance Martin, corpus B.2). Iba primera por el principio de orden: compress sobre el historial es la palanca más barata. · fuente: N1·L1 (corpus B.2, B.3).
RemoveMessage(id=m.id), delangchain_core.messages. Exige el reduceradd_messagesen el state key —MessagesStateya lo trae. Sin ese reducer, no borra nada. · fuente: corpus F.1, api-nivel1 §2.
3.4El concepto
Qué es la compaction
Aquí está la palanca. La compaction es resumir el historial y reiniciar el contexto con el resumen. En vez de tirar lo viejo (trimming), lo destilas a un mensaje denso y reemplazas los mensajes originales por ese resumen.
La analogía: son las actas de una reunión larga. No guardas la grabación de tres horas; guardas las decisiones tomadas, los pendientes y quién hace qué. La grabación pesa y se relee mal; las actas caben en una página y bastan para seguir trabajando. Dónde falla la analogía: unas actas humanas las relee una persona que ya estuvo en la reunión y rellena los huecos. El agente solo tiene el resumen — lo que el acta no diga, para él no existió.
Esa última frase es el centro de la lección, así que la subrayo. Lo que el resumen no preserva, muere. Y el agente no sabe que lo perdió. No hay error ni aviso. El agente vuelve a leer un documento ya leído, o contradice una decisión que ya no recuerda. La compaction mal hecha no falla ruidosamente: degrada en silencio.
Anthropic distingue la compaction de la summarization lossy indiscriminada. La compaction quita lo redundante o recuperable y conserva lo que no se puede reconstruir. El "tool result clearing" —borrar resultados de tools ya cumplidos— es "one of the safest, lightest-touch forms of compaction" (corpus B.4, paráfrasis parcial).
Qué preservar (y qué descartar)
Esta es la decisión de diseño. Un buen prompt de compaction no dice "resume"; dice qué conservar. La guía de Anthropic recomienda preservar el estado, los próximos pasos y los aprendizajes (corpus B.4). Trasladado a Magallanes, el prompt de preservación lista cuatro cosas:
- Decisiones tomadas — el ángulo del informe, los subtemas que ya dio por cerrados, las fuentes que descartó y por qué.
- Estado del informe — qué secciones existen y qué afirma cada una (para no contradecirse).
- Subtemas pendientes — lo que falta investigar, para no darlo por hecho.
- doc_ids ya leídos — la lista de documentos incorporados, para no volver a leerlos.
Y qué descartar: los tool outputs ya incorporados al informe. Un documento que leer() trajo entero y que ya está resumido en una sección no necesita seguir en el contexto. Es recuperable —el doc_id sigue en la lista— y redundante. Eso es lo que la compaction debe soltar.
Nota de honestidad: esta lista de "qué preservar" es una paráfrasis de confianza media del corpus (B.4 ⚠️), no verbatim de Anthropic. La idea —preservar decisiones y estado, descartar lo redundante— está verificada; la redacción de los cuatro puntos es decisión de ingeniería del curso para Magallanes.
Referencias de producción (cada una con su etiqueta)
La compaction no es un truco de curso. Vive en producción, pero conviene citar cada caso con su grado de certeza.
- Claude Code (la CLI) ejecuta auto-compact en torno al ~95% de la ventana. Esto es comportamiento observado —lo cita Lance Martin, lo confirma un issue del repo— no doc oficial de Anthropic (corpus B.4, §8.15). La heurística comunitaria de compactar antes (~60-70%) es solo eso: una heurística, sin respaldo oficial.
- API de Anthropic, compaction server-side (beta
compact-2026-01-12, estrategiacompact_20260112): el umbral por defecto es 150.000 tokens de input, con un mínimo configurable de 50.000 (corpus B.4, api-nivel1 §5). Lo verás funcionar en 3.5. - Caso del cookbook oficial: en procesamiento de tickets, la compaction redujo el input de 204.416 → 82.171 tokens (~59,8%). Rangos de umbral recomendados por tipo de workflow: 5k-20k (procesamiento iterativo), 50k-100k (multifase), 100k-150k (historial sustancial) · fuente: corpus B.4.
El riesgo lossy, normalizado
Normaliza esto, porque es el error que más cuesta ver. "Resume la conversación hasta ahora" parece inocuo. Es la versión con corbata de tirar el historial. Un resumen genérico conserva el tono y pierde lo específico: justo los doc_ids, justo la decisión de la sección 1. Suena responsable y rompe igual que el trimming plano.
La diferencia entre una compaction que funciona y una que destruye no está en el código del nodo: está en el prompt. Por eso el lab del nivel es un A/B entre dos prompts.
Lo que NO es la compaction
La compaction no es comprimir cada mensaje individualmente para que ocupe menos. Eso sería summarization lossy por igual a todo —borraría la decisión de la sección 1 con la misma indiferencia que un tool output agotado. Tampoco es trimming con más pasos: el trimming corta por longitud sin leer; la compaction lee, decide qué importa y lo destila. La distinción operativa: trimming descarta, compaction destila.
3.5Míralo funcionar
Vamos a montar el nodo de compaction a mano sobre el grafo de Magallanes. Es código con varias piezas: un chequeo de umbral, una llamada al LLM con el prompt de preservación, y un reemplazo del historial con RemoveMessage.
Antes de leerlo línea a línea, lee el bloque entero una vez de corrido. Lo difícil no es ninguna línea suelta; es ver el flujo. El nodo decide si toca compactar, pide un resumen, y devuelve un borrado masivo más el resumen. Primero hazte una idea de la forma.
El umbral: cuándo compactar
El nodo se inserta en el grafo entre el modelo y las tools. En cada paso comprueba el tamaño del historial. Si supera un umbral, compacta; si no, deja el estado intacto.
1# gestion_historial.py — nodo de compaction sobre el grafo congelado de Magallanes (N0·L3)
2from langchain_core.messages import RemoveMessage, SystemMessage, HumanMessage
3
4UMBRAL_COMPACTACION = 60_000 # tokens de historial; valor a justificar con la curva del sweep
5
6def necesita_compactar(state) -> bool:
7 # token_counter del esqueleto del lab: cuenta los tokens del historial actual.
8 tokens = contar_tokens(state["messages"])
9 return tokens >= UMBRAL_COMPACTACIONDos cosas de este chequeo:
UMBRAL_COMPACTACIONno es un número mágico. Lo eliges con el presupuesto de N0·L3 y lo justificas con la curva del sweep —como elegistemax_tokensen L2. El cookbook de Anthropic da rangos (5k-150k según el workflow, corpus B.4); el tuyo lo fijas midiendo.contar_tokenses la función de conteo del esqueleto del lab. No la inventes: es la misma que alimenta el sweep.
El prompt de preservación: el corazón del nodo
Aquí se gana o se pierde la lección. El prompt no dice "resume"; instruye qué conservar y qué descartar, ítem a ítem.
1PROMPT_PRESERVACION = """\
2Resume la investigación de Magallanes hasta ahora. El resumen REEMPLAZA al historial:
3lo que no incluyas, se pierde para siempre. Conserva, de forma explícita:
4
51. DECISIONES TOMADAS: el ángulo del informe, los subtemas ya cerrados,
6 las fuentes descartadas y por qué.
72. ESTADO DEL INFORME: qué secciones existen y qué afirma cada una
8 (para no contradecirlas después).
93. SUBTEMAS PENDIENTES: lo que falta investigar.
104. DOC_IDS YA LEÍDOS: la lista completa de documentos ya incorporados
11 (para no volver a leerlos).
12
13Descarta: el texto íntegro de los documentos ya resumidos en una sección.
14No llames a ninguna tool mientras escribes este resumen; responde solo con texto.
15"""La última línea no es opcional. La doc oficial de Anthropic documenta un riesgo real: durante el resumen, el modelo intenta llamar tools. La instrucción exacta que lo evita: "Do not call any tools while writing this summary; respond with text only." (api-nivel1 §5, corpus B.4). Sin ella, tu paso de compaction puede dispararse en una tool call.
Fíjate en el punto 4. Los doc_ids ya leídos son la línea que ataca el síntoma del hook: sin esa lista, Magallanes vuelve a leer lo que ya leyó. Es distraction, directo de la taxonomía de N0.
El nodo: resumir y reemplazar
El nodo une las tres piezas. Si toca compactar, pide el resumen al LLM, marca los mensajes viejos para borrado con RemoveMessage, y añade el resumen como mensaje nuevo.
1def nodo_compaction(state):
2 if not necesita_compactar(state):
3 return # nada que hacer: el estado sigue igual
4
5 mensajes = state["messages"]
6 # mensajes[0] es el system prompt; al quedar fuera de a_destilar,
7 # el reducer add_messages no lo borra — se preserva por exclusión.
8 a_destilar = mensajes[1:] # todo lo demás es candidato a resumen
9
10 # 1) El LLM resume con preservación explícita. Es un paso de sampling extra.
11 # model: la instancia del LLM del esqueleto del lab (definida en el grafo de N0·L3).
12 resumen = model.invoke(
13 [SystemMessage(PROMPT_PRESERVACION)] + a_destilar
14 )
15 mensaje_resumen = HumanMessage(
16 content=f"[RESUMEN DE LA INVESTIGACIÓN HASTA AHORA]\n{resumen.content}"
17 )
18
19 # 2) Borrar lo viejo y dejar solo system + resumen.
20 # RemoveMessage exige el reducer add_messages (MessagesState ya lo trae).
21 borrados = [RemoveMessage(id=m.id) for m in a_destilar]
22 return {"messages": borrados + [mensaje_resumen]}Léelo de arriba abajo. Si no toca compactar, el return sin valor devuelve None. En LangGraph eso significa "sin actualización de estado en este turno": el estado sigue igual, que es justo lo que queremos. No es lo mismo que devolver {}, una actualización vacía. Cuando sí toca, el system prompt sobrevive por exclusión: queda fuera de a_destilar, así que no entra en borrados y el reducer nunca lo toca. Después pide el resumen pasando el prompt de preservación como SystemMessage. Luego marca cada mensaje viejo con RemoveMessage(id=m.id) y devuelve esos borrados más el resumen. El reducer add_messages ejecuta los borrados al ver los objetos RemoveMessage —por eso MessagesState es obligatorio aquí (api-nivel1 §2).
El coste es real y hay que reportarlo. El model.invoke del resumen es un paso de sampling extra: tokens y latencia que el agente baseline no pagaba. La compaction no sale de la nada; se paga en cada compactación. La rúbrica C1 te pedirá esa factura.
La alternativa server-side (mención, no código de lab)
El curso monta el nodo a mano porque enseña más: ves qué se preserva y qué se borra. Pero existe una alternativa server-side: la API de compaction de Anthropic (beta compact-2026-01-12) hace el mismo trabajo dentro del propio request. Detecta el umbral (150K input por defecto), genera un bloque compaction, descarta los mensajes anteriores y continúa.
Su detalle crítico, si la usas: el bloque compaction debe viajar en los mensajes siguientes. Construyes el historial con response.content entero —no solo response.content[0].text— o el resumen se pierde y vuelves al punto de partida (api-nivel1 §5). Y sus instructions custom reemplazan el prompt por defecto, no se añaden: si las pones, te toca incluir tú la preservación completa.
El
SummarizationNodede LangMem es otra alternativa: un nodo que resume automáticamente. El curso lo monta a mano porque así la decisión de preservación queda visible —que es justo lo que se enseña aquí.
Re-ejecuta el sweep: tres curvas
Como toda intervención del nivel, la compaction se valida re-ejecutando el sweep de N0 sin cambios —mismo encargo, longitudes y seed— sobre el baseline (corpus A.7). Ahora tienes tres curvas: baseline, trimming, compaction.
Estos números son ilustrativos —tu run dará los suyos—; lee la forma:
longitud (docs irrelevantes) | calidad
--------------------------------------------------------
| baseline | trimming | compaction
0 docs | 0.92 | 0.91 | 0.92
10 docs | 0.85 | 0.86 | 0.88
25 docs | 0.61 | 0.74 | 0.83
50 docs | 0.34 | 0.41 | 0.78
Lee la forma, no los dígitos. A longitudes medias (10-25), trimming y compaction casi empatan: la ventana deslizante aún no había tirado nada crítico. A 50 documentos se abre el abismo. El trimming se cae —ahí ya tiró las decisiones tempranas— y la compaction aguanta, porque destiló esas decisiones en vez de borrarlas. El codo de la curva se desplazó a la derecha. Eso es lo que la rúbrica C1 llama "la curva se desplaza".
Self-explanation
Antes de leer la respuesta, intenta tú: ¿por qué el prompt de preservación lista los doc_ids ya leídos? ¿Qué síntoma de N0 reaparecería sin esa línea?
Razónalo y comprueba
Sin la lista de doc_ids, el resumen no conserva qué documentos ya se leyeron. En el siguiente turno, Magallanes no puede saber que ya leyó doc-014 —para él, ese documento nunca entró en el contexto. Así que lo vuelve a buscar y a leer.
Eso es distraction, directo de la taxonomía de N0 (los-cuatro-modos-de-morir): el agente repite trabajo ya hecho. Es el síntoma del hook de esta lección.
La línea de doc_ids cuesta una frase en el prompt y evita un modo de fallo entero. Esa es la economía de la preservación explícita: cada ítem del prompt apaga un síntoma concreto.
Si pensaste "para que el informe cite bien", revisa. Las citas son un efecto secundario; el objetivo directo es no re-leer —no repetir búsquedas ya hechas.
3.6Hazlo tú
Andamiaje decreciente. Este es el lab del nivel: un A/B controlado entre dos prompts de compaction. El artefacto que produce ES la dimensión 2 de la rúbrica C1.
Ejercicio 1 — A/B: ingenua vs con preservación
Tienes el nodo de 3.5 montado. Vas a correr el sweep dos veces, cambiando solo el prompt:
- Compaction ingenua: sustituye
PROMPT_PRESERVACIONpor una sola línea —"Resume la conversación hasta ahora."Nada más. - Compaction con preservación: tu prompt de 3.5, con los cuatro ítems.
Re-ejecuta el sweep con cada uno (mismo encargo, mismas longitudes, mismo seed). Documenta el caso concreto donde la ingenua rompe y la tuya no: ¿repite una búsqueda? ¿contradice una sección? Captura la traza. Ese artefacto —"aquí la ingenua re-leyó doc-014; la mía no"— es la dim 2 de C1.
Pista de implementación: es el mismo nodo; solo cambia la constante PROMPT_PRESERVACION. No toques el sweep —si cambias dos cosas, no sabrás cuál movió la curva (corpus A.7).
Elaborative interrogation — antes de correrlo, predice: ¿en qué tramo de la curva (longitudes bajas o altas) se separarán más las dos compactions? ¿Por qué?
Comprueba tu predicción
Esperas que se separen a longitudes altas. A pocas longitudes, el historial casi no cruza el umbral: ninguna de las dos compacta gran cosa, así que dan curvas parecidas. La compaction solo actúa cuando el historial se llena —y eso pasa en los runs largos.
En los runs largos, la ingenua resume sin preservar: pierde los doc_ids y las decisiones. Reaparecen distraction (re-lee) y clash (contradice la sección 1). La tuya destila esas mismas cosas y las mantiene. La brecha entre ambas curvas es la longitud a la que la preservación empieza a importar.
Feedback: si predijiste que se separarían en todo el rango por igual, revisa el umbral. Por debajo del umbral, las dos compactions hacen lo mismo (casi nada). La diferencia solo existe cuando el nodo de verdad dispara —en la cola de la curva.
Ejercicio 2 — sin plantilla: ajusta el umbral
Ahora sin guía. Toma tu compaction con preservación y barre el UMBRAL_COMPACTACION: pruébalo bajo (compacta pronto, muchas veces) y alto (compacta tarde, pocas veces). Re-ejecuta el sweep con cada valor.
Justifica el umbral que elijas con la curva y con el coste. Un umbral bajo compacta mucho: más resúmenes, más sampling extra, más factura —y si compactas demasiado pronto, destilas un historial que aún cabía sin problema. Un umbral alto compacta tarde: te arriesgas a que la degradación pegue antes de la primera compactación. El valor correcto no es un número del corpus; es el que tu curva señala.
3.7Comprueba
Sin pistas. Gate de maestría: predecir el fallo de un prompt de compaction y corregir el peor.
Dos equipos te pasan sus prompts de compaction para Magallanes. Ninguno usa el tuyo. Para cada uno, predice qué fallo concreto aparecerá tras compactar. Puede ser un síntoma de la taxonomía de N0 o un problema de otra naturaleza —justifica en cualquier caso. Luego corrige el peor.
1# Prompt A (equipo Drake)
2"Resume todo lo que ha pasado, de forma exhaustiva y detallada,
3sin omitir nada importante de la conversación."
4
5# Prompt B (equipo Elcano)
6"Conserva las decisiones tomadas, el estado de cada sección del informe
7y los subtemas pendientes. Sé breve."- Predice el fallo de cada prompt; si encaja en la taxonomía de N0, nómbralo —si no, explica de qué naturaleza es.
- Decide cuál es peor y corrígelo.
Criterio de corrección + feedback
Prompt A (Drake): "exhaustivo, sin omitir nada" niega el propósito de la compaction. Un resumen que lo conserva todo no reduce el contexto —o el LLM, forzado a ser exhaustivo, infla el resumen con el texto íntegro de los documentos. Resultado: el contexto no baja (o baja poco), el umbral se vuelve a cruzar enseguida, y la curva no se desplaza. No es un fallo de la taxonomía de N0 en su forma inmediata: es coste sin mejora. La compaction se paga (sampling extra) y no compra nada. Pero el A no evita la taxonomía, solo la retrasa: si el LLM genuinamente no logra condensar sin dejar caer información, el fallo degrada hacia distraction —los doc_ids tempranos desaparecen de todos modos, ahora de forma impredecible. El A lo hace opaco; no lo previene.
Prompt B (Elcano): conserva decisiones, estado y pendientes. Le falta un ítem: los doc_ids ya leídos. Predice el fallo: Magallanes re-leerá documentos que ya había incorporado. Síntoma: distraction —repite búsquedas/lecturas ya hechas.
Cuál es peor: el B. El A desperdicia tokens y su degradación a distraction es tardía e impredecible —el fallo de comportamiento llega, pero más lejos en la curva. El B produce ese mismo fallo de inmediato y de forma garantizada: re-lee desde la primera compactación, gasta turnos, y a más longitud arrastra clash si las relecturas reintroducen información que choca con el informe. Un fallo inmediato y seguro es peor que uno tardío e incierto.
La corrección del B: añadir la línea que falta —
1"...y la lista completa de DOC_IDS ya leídos, para no volver a leerlos."Feedback formativo: si nombraste correctamente el síntoma del B como distraction, dominas lo esencial — es el fallo que el caso de control de C1 tiene que demostrar. Si dijiste que el A era peor "porque pierde información", revisa: el A no pierde, sobra; su problema es que no comprime, no que destruya. Gate: necesitas el síntoma del B (distraction por doc_ids perdidos) identificado y corregido para superar este punto.
3.8Conecta
Acabas de batir el baseline donde el trimming se caía. La curva de compaction aguanta a 50 documentos porque destiló las decisiones que la ventana deslizante tiraba. Eso es la rúbrica C1, dimensiones 2 y 4: la compaction con preservación, y la mejora medida contra el baseline.
Y dejaste lista la dim 2 entera. El A/B de 3.6 —la ingenua re-lee doc-014, la tuya no— es el caso de control que C1 exige. No es un ejercicio decorativo: es media nota del checkpoint.
Pero la lección abre una pregunta incómoda. Mira tu prompt de preservación. En cada compactación, cargas a mano las decisiones, el estado y los pendientes para que sobrevivan al resumen. Funciona. Pero, ¿por qué viven esas cosas dentro del contexto, donde hay que rescatarlas en cada resumen?
Si hay información que NINGÚN resumen debería tener que cargar —decisiones, estado, pendientes que el agente necesita siempre— quizá no pertenece al historial. Quizá pertenece a un sitio fuera del contexto, del que el agente lee cuando lo necesita. Eso es la palanca write, y es memoria (N2).
Cierra el arco del hook: tenías un run muerto que re-leía y se contradecía. Ahora tienes un nodo que destila lo que importa y un caso que prueba que la versión ingenua no lo hace. El siguiente paso del nivel —la-altitud-del-system-prompt— ataca la otra partida del coste: el prompt que pagas en cada turno.
3.9Reflexiona
Tómate un minuto. Responder esto por escrito consolida mejor que releer.
- ¿Qué aprendiste? Resume en una frase por qué "resume la conversación" es la versión con corbata de tirar el historial.
- ¿Qué sigue sin estar claro? ¿Tienes claro qué descarta la compaction (lo redundante/recuperable) frente a qué preserva (lo no reconstruible)? Si no, vuelve a 3.4.
- ¿Qué harías distinto? La próxima vez que un compañero proponga "resumamos el contexto", ¿qué cuatro cosas le preguntarías que su resumen tiene que conservar?
Esto requiere práctica. La intuición de "qué muere si no lo preservo" llega corriendo el A/B y leyendo las trazas donde la ingenua rompe. En L5 integrarás esta compaction con el trimming de L2 y la altitud de L4 en el entregable de C1.