La memoria infinita de Claude Code: compactación, sueños y consolidación
Nota: Los fragmentos de código que aparecen en esta serie son recreaciones en Python basadas en la arquitectura y patrones descritos públicamente tras la filtración. No se reproduce código original de Anthropic. El objetivo es puramente educativo: entender los conceptos de ingeniería detrás de una herramienta de IA de producción.
1. El problema del límite de tokens
Todos los modelos de lenguaje tienen una ventana de contexto: la cantidad máxima de texto que pueden procesar a la vez. Es como una mesa de trabajo con espacio limitado — todo lo que el modelo "sabe" durante una conversación tiene que caber en esa mesa.
Claude tiene una ventana de 200.000 tokens (aproximadamente 150.000 palabras). Parece enorme, pero en una sesión de trabajo real — leyendo archivos, ejecutando comandos, recibiendo errores y corrigiendo — ese espacio se llena rápido.
El problema no es solo el tamaño. Cada token tiene coste económico: cuanto más larga la conversación, más cara cada respuesta. Y más allá del coste, un contexto muy lleno degrada la calidad: el modelo empieza a "olvidar" cosas que están al principio de la conversación.
Claude Code resuelve esto con un sistema de compactación en múltiples niveles, cada uno más agresivo que el anterior. Además, tiene un sistema de memoria persistente que permite al modelo "recordar" cosas entre sesiones completamente distintas.
2. La jerarquía de compactación
El sistema tiene 5 niveles de compactación, ordenados de menos a más agresivos:
microCompact (limpiar resultados de herramientas)
→ apiMicrocompact (edición de cache del servidor)
→ sessionMemoryCompact (resumen desde memoria de sesión)
→ fullCompact (resumen generado por IA)
→ partialCompact (resumir subconjunto de mensajes)Cada nivel se intenta antes de escalar al siguiente. La idea es que la mayoría de las veces, una limpieza ligera es suficiente y no hace falta invocar al modelo para resumir toda la conversación.
3. Nivel 1 — microCompact (la limpieza rápida)
El nivel más ligero no involucra ninguna llamada a un modelo de IA. Simplemente limpia los resultados antiguos de herramientas que ya no son relevantes.
# Recreación del sistema de microcompact
COMPACTABLE_TOOLS = {
"FileRead", "Bash", "Grep", "Glob",
"WebSearch", "WebFetch", "FileEdit", "FileWrite",
}
CLEARED_MESSAGE = "[Old tool result content cleared]"
class MicroCompact:
def __init__(self, config):
self.gap_threshold_minutes = config.get("gapThresholdMinutes", 60)
self.keep_recent = max(config.get("keepRecent", 5), 1)
def maybe_compact(self, messages, last_assistant_time):
"""Compacta si ha pasado suficiente tiempo desde la última respuesta."""
minutes_since_last = self._minutes_since(last_assistant_time)
if minutes_since_last < self.gap_threshold_minutes:
return messages # No ha pasado suficiente tiempo
# Encontrar todos los tool_result de herramientas compactables
tool_results = self._find_compactable_results(messages)
# Mantener los N más recientes intactos, limpiar el resto
to_clear = tool_results[:-self.keep_recent]
for result in to_clear:
result["content"] = CLEARED_MESSAGE
return messages
def estimate_tokens(self, message):
"""Estima tokens con un 33% de margen conservador."""
raw_estimate = len(str(message)) // 4
return int(raw_estimate * 4 / 3)Este nivel tiene tres caminos:
Basado en tiempo: si han pasado más de 60 minutos desde la última respuesta del asistente (el usuario se fue y volvió), limpia todos los resultados de herramientas excepto los 5 más recientes
Edición de cache: usa una API del servidor para eliminar resultados directamente del cache sin invalidar el prefijo cacheado — esto evita retokenizar toda la conversación
Fallback: si ninguna condición se cumple, no hace nada
El dato del 33% de margen en la estimación de tokens es un detalle de ingeniería importante: es más seguro sobreestimar que quedarse corto.
4. Nivel 2 — apiMicrocompact (compactación del lado del servidor)
Este nivel trabaja directamente con la API de mensajes de Claude usando un parámetro llamado context_management:
# Recreación de la configuración de apiMicrocompact
API_MICROCOMPACT_CONFIG = {
"max_input_tokens": 180_000,
"target_input_tokens": 40_000,
"strategies": [
"clear_tool_uses", # Elimina pares tool_use/tool_result
"clear_thinking", # Elimina bloques de pensamiento
],
}La idea es pedirle al servidor que limpie selectivamente pares de uso de herramientas y bloques de pensamiento extendido. Al hacerlo del lado del servidor, se aprovecha el cache existente sin necesidad de reenviar toda la conversación.
5. Nivel 3 — sessionMemoryCompact (resumen sin IA)
Este es un nivel ingenioso: en lugar de llamar a un modelo para que resuma la conversación, usa un archivo de memoria de sesión que ya existe como resumen.
# Recreación del sistema de session memory compact
SESSION_MEMORY_CONFIG = {
"min_tokens": 10_000, # Mínimo de tokens a mantener post-compact
"min_text_messages": 5, # Mínimo de mensajes con texto a preservar
"max_tokens": 40_000, # Máximo de tokens en mensajes preservados
}
EXTRACTION_WAIT_TIMEOUT_MS = 15_000 # Esperar hasta 15s si hay extracción en curso
EXTRACTION_STALE_THRESHOLD_MS = 60_000 # Considerar extracción obsoleta tras 60s
def try_session_memory_compaction(messages, session_memory_path, config):
"""Intenta compactar usando la memoria de sesión existente."""
# Leer el archivo de memoria de sesión
summary = read_file(session_memory_path)
if not summary:
return None # No hay memoria → caer al siguiente nivel
# Calcular cuántos mensajes recientes preservar
keep_index = calculate_messages_to_keep(
messages,
min_tokens=config["min_tokens"],
min_text_messages=config["min_text_messages"],
max_tokens=config["max_tokens"],
)
# Ajustar el índice para no romper pares tool_use/tool_result
keep_index = adjust_index_to_preserve_api_invariants(messages, keep_index)
# Construir conversación compactada: resumen + mensajes recientes
compacted = [create_summary_message(summary)] + messages[keep_index:]
# Verificar que la compactación realmente reduce suficiente
if estimate_tokens(compacted) > auto_compact_threshold:
return None # No basta → caer al siguiente nivel
return compactedLa función adjust_index_to_preserve_api_invariants es crítica: la API de Claude requiere que los bloques tool_use y tool_result siempre vayan en pareja, y que los bloques de pensamiento extendido se mantengan con su mensaje padre. Si la compactación cortase en medio de un par, la API devolvería un error.
6. Nivel 4 — fullCompact (el resumen completo)
Cuando los niveles anteriores no liberan suficiente espacio, se activa la compactación completa: se llama a un modelo de IA para que genere un resumen estructurado de toda la conversación.
# Recreación del sistema de compactación completa
POST_COMPACT_MAX_FILES_TO_RESTORE = 5
POST_COMPACT_TOKEN_BUDGET = 50_000
POST_COMPACT_MAX_TOKENS_PER_FILE = 5_000
MAX_COMPACT_STREAMING_RETRIES = 2
MAX_PTL_RETRIES = 3 # Reintentos por prompt-too-long
def compact_conversation(messages, system_prompt, model):
"""Compactación completa: genera un resumen con IA."""
# Paso 1: reemplazar imágenes por marcadores de texto
cleaned = strip_images_from_messages(messages)
# Paso 2: generar resumen usando un agente forked
# (comparte el cache del prompt del agente padre)
try:
summary = stream_compact_summary_via_fork(cleaned, system_prompt, model)
except PromptTooLongError:
# Si el propio prompt de compactación es demasiado largo,
# eliminar los grupos de mensajes más antiguos y reintentar
for attempt in range(MAX_PTL_RETRIES):
cleaned = truncate_oldest_groups(cleaned, drop_ratio=0.2)
try:
summary = stream_compact_summary_via_fork(
cleaned, system_prompt, model
)
break
except PromptTooLongError:
continue
# Paso 3: reconstruir la conversación compactada
return build_post_compact_messages(
summary=summary,
recent_messages=get_recent_messages(messages),
file_attachments=restore_recent_files(max_files=5, budget=50_000),
skill_attachments=preserve_skill_context(),
)El resumen que genera el modelo no es un resumen libre. Sigue una estructura de 9 secciones predefinida que merece su propia sección.
Después de generar el resumen, el sistema hace algo inteligente: relee hasta 5 archivos recientes (con un presupuesto de 50.000 tokens, máximo 5.000 por archivo) para que el modelo no pierda awareness de los archivos clave. También preserva el contexto de cualquier "skill" que se haya invocado durante la sesión.
7. Las 9 secciones del resumen (y por qué esas y no otras)
Este es quizá el componente más revelador de todo el sistema de compactación. Cuando Claude Code necesita comprimir una conversación, no le dice al modelo "resume esto". Le da un prompt extremadamente específico que exige un resumen en 9 secciones concretas. Cada sección existe por una razón.
# Recreación del prompt de compactación
COMPACT_SUMMARY_SECTIONS = {
1: {
"name": "Primary Request and Intent",
"instruction": "Capture all of the user's explicit requests and intents in detail",
"why": "Sin esto, el modelo post-compactación no sabe QUÉ le pidieron",
},
2: {
"name": "Key Technical Concepts",
"instruction": "List all important technical concepts, technologies, "
"and frameworks discussed",
"why": "Preserva el vocabulario técnico compartido entre usuario y modelo",
},
3: {
"name": "Files and Code Sections",
"instruction": "Enumerate specific files and code sections examined, "
"modified, or created. Include full code snippets where "
"applicable and a summary of why each file is important",
"why": "El modelo necesita saber qué archivos tocó y qué cambió en ellos",
},
4: {
"name": "Errors and Fixes",
"instruction": "List all errors encountered and how they were fixed. "
"Pay special attention to specific user feedback, "
"especially if the user told you to do something differently",
"why": "Evita que el modelo repita los mismos errores tras compactar",
},
5: {
"name": "Problem Solving",
"instruction": "Document problems solved and any ongoing "
"troubleshooting efforts",
"why": "Captura el razonamiento, no solo el resultado",
},
6: {
"name": "All User Messages",
"instruction": "List ALL user messages that are not tool results. "
"These are critical for understanding the users' "
"feedback and changing intent",
"why": "Los mensajes del usuario son la fuente de verdad de la intención",
},
7: {
"name": "Pending Tasks",
"instruction": "Outline any pending tasks that you have explicitly "
"been asked to work on",
"why": "Sin esto, las tareas incompletas se pierden tras compactar",
},
8: {
"name": "Current Work",
"instruction": "Describe in detail precisely what was being worked on "
"immediately before this summary request, paying special "
"attention to the most recent messages",
"why": "El modelo debe retomar EXACTAMENTE donde lo dejó",
},
9: {
"name": "Optional Next Step",
"instruction": "List the next step related to the most recent work. "
"Include direct quotes from the most recent conversation "
"showing exactly what task you were working on. "
"IMPORTANT: ensure this step is DIRECTLY in line with "
"the user's most recent explicit requests. Do not start "
"on tangential or old requests without confirming first",
"why": "Previene la 'deriva de tareas' — que el modelo se vaya por la tangente",
},
}Hay decisiones de diseño muy reveladoras en esta estructura:
La sección 6 ("All User Messages") es la más inusual. En un resumen normal, nadie listaría todos los mensajes del usuario textualmente. Pero aquí tiene un propósito crítico: los mensajes del usuario son la única fuente fiable de intención cambiante. Si el usuario dijo "haz X" y luego dijo "no, mejor Y", un resumen narrativo podría perder ese matiz. Listando los mensajes explícitamente, el modelo post-compactación puede reconstruir la evolución de lo que el usuario realmente quería.
La sección 9 incluye una instrucción anti-deriva. El prompt dice textualmente: "ensure that this step is DIRECTLY in line with the user's most recent explicit requests. Do not start on tangential requests or really old requests that were already completed without confirming with the user first." Esto existe porque sin esta restricción, el modelo post-compactación tiende a retomar tareas antiguas que ya se completaron, o a inventarse pasos siguientes que el usuario nunca pidió. Además, exige citas textuales de la conversación original para anclar el siguiente paso a lo que realmente se dijo.
La sección 4 enfatiza el feedback del usuario. No solo pide listar errores, sino que insiste: "Pay special attention to specific user feedback, especially if the user told you to do something differently." Esta frase aparece tanto en la instrucción de análisis como en la de la sección 4. La repetición es intencionada: perder una corrección del usuario sería peor que perder un detalle técnico.
El proceso de dos fases: analysis + summary. Antes de escribir el resumen, el modelo debe generar un bloque <analysis> con su razonamiento. Este bloque se descarta automáticamente después — nunca llega al contexto post-compactación. Es un "scratchpad" que mejora la calidad del resumen forzando al modelo a pensar antes de comprimir. El código que lo elimina es simple:
def format_compact_summary(raw_summary):
"""Elimina el scratchpad de análisis y extrae el resumen limpio."""
# El bloque <analysis> es un borrador que mejora la calidad
# pero no tiene valor informativo una vez escrito el resumen
cleaned = re.sub(r"<analysis>[\s\S]*?</analysis>", "", raw_summary)
# Extraer contenido del bloque <summary>
match = re.search(r"<summary>([\s\S]*?)</summary>", cleaned)
if match:
content = match.group(1).strip()
cleaned = f"Summary:\n{content}"
return cleaned.strip()La "compactación parcial" usa variantes del prompt. Cuando solo se compacta una parte de la conversación (no toda), existen dos variantes:
Desde un punto (
from): resume los mensajes recientes, asumiendo que los anteriores se preservan intactosHasta un punto (
up_to): resume el principio de la conversación, sabiendo que los mensajes recientes se mantendrán después
La variante up_to cambia la sección 8 de "Current Work" a "Work Completed" y la 9 de "Next Step" a "Context for Continuing Work", porque el contexto narrativo es distinto: no se está retomando trabajo interrumpido, sino proporcionando antecedentes para los mensajes que vienen después.
El mensaje de continuación post-compactación. Después de compactar, el resumen se envuelve en un mensaje que dice: "This session is being continued from a previous conversation that ran out of context." Si el sistema está en modo autónomo (sin usuario interactivo), se añade una instrucción extra: "Continue your work loop: pick up where you left off. Do not greet the user or ask what to work on." El modelo debe retomar como si nada hubiera pasado.
8. El patrón del agente forked
Tanto la compactación completa como la extracción de memorias usan un patrón arquitectónico clave: el agente forked. En lugar de hacer una llamada API nueva con toda la conversación, se "bifurca" el agente actual, reutilizando el cache de prompts del agente padre.
Agente principal (200K tokens cacheados)
↓ fork
Agente hijo (reutiliza el cache del padre)
→ genera resumen
→ devuelve resultado al padreEsto ahorra tanto latencia como coste: no hay que retokenizar toda la conversación para generar el resumen. Es como fotocopiar un documento en lugar de reescribirlo desde cero.
9. Cuándo se compacta y el circuit breaker
El sistema decide si necesita compactar basándose en umbrales de tokens:
# Recreación de los umbrales de auto-compactación
MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000 # Reservados para generar el resumen
AUTOCOMPACT_BUFFER_TOKENS = 13_000 # Margen de seguridad
WARNING_THRESHOLD_BUFFER = 20_000 # Aviso antes de compactar
MAX_CONSECUTIVE_FAILURES = 3 # Circuit breaker
def calculate_thresholds(context_window_size):
"""Calcula los umbrales de compactación."""
effective_window = context_window_size - MAX_OUTPUT_TOKENS_FOR_SUMMARY
auto_compact_at = effective_window - AUTOCOMPACT_BUFFER_TOKENS
warning_at = auto_compact_at - WARNING_THRESHOLD_BUFFER
return {
"effective_window": effective_window,
"auto_compact": auto_compact_at,
"warning": warning_at,
}
# Para una ventana de 200K tokens:
# effective_window = 200.000 - 20.000 = 180.000
# auto_compact_at = 180.000 - 13.000 = 167.000
# warning_at = 167.000 - 20.000 = 147.000
class AutoCompactTracker:
def __init__(self):
self.compacted = False
self.turn_counter = 0
self.consecutive_failures = 0
def should_compact(self, current_tokens, threshold, query_source):
"""Decide si hay que compactar."""
# Nunca compactar recursivamente
if query_source in ("session_memory", "compact"):
return False
# Circuit breaker: si falló 3 veces seguidas, desistir
if self.consecutive_failures >= MAX_CONSECUTIVE_FAILURES:
return False
return current_tokens >= threshold
def record_result(self, success):
if success:
self.consecutive_failures = 0
else:
self.consecutive_failures += 1El circuit breaker merece atención especial. Los datos de producción de Anthropic mostraron 1.279 sesiones con más de 50 fallos consecutivos de compactación, desperdiciando aproximadamente 250.000 llamadas a la API por día. La solución fue simple: si la compactación falla 3 veces seguidas, se desactiva para esa sesión.
El orden de ejecución cuando se activa la auto-compactación es:
Intentar session memory compact (ligero, sin llamada a IA)
Si devuelve null, ejecutar full compact (pesado, con llamada a IA)
Registra resultado para el circuit breaker
10. La memoria persistente — CLAUDE.md y más allá
Claude Code no solo recuerda cosas dentro de una conversación. Tiene un sistema de memoria persistente en múltiples niveles:
# Recreación de la jerarquía de memorias
MEMORY_HIERARCHY = [
{"priority": 1, "type": "managed", "path": "/etc/claude-code/CLAUDE.md",
"description": "Instrucciones de empresa (no excluibles)"},
{"priority": 2, "type": "user", "path": "~/.claude/CLAUDE.md",
"description": "Instrucciones globales del usuario"},
{"priority": 3, "type": "project", "paths": [
"CLAUDE.md",
".claude/CLAUDE.md",
".claude/rules/*.md",
], "description": "Instrucciones del proyecto"},
{"priority": 4, "type": "local", "path": "CLAUDE.local.md",
"description": "Instrucciones locales (gitignored)"},
{"priority": 5, "type": "auto_mem", "path": "MEMORY.md",
"description": "Memorias extraídas automáticamente"},
{"priority": 6, "type": "team_mem", "path": "team memory files",
"description": "Memorias compartidas del equipo"},
]
MAX_MEMORY_CHARACTER_COUNT = 40_000 # Límite total de contenido de memorias
MAX_INCLUDE_DEPTH = 5 # Profundidad máxima de @include
def get_memory_files(git_root, cwd):
"""Recorre desde la raíz del repo hasta el directorio actual,
recogiendo archivos de memoria en cada nivel."""
files = []
current = git_root
for segment in path_segments(git_root, cwd):
current = join(current, segment)
for pattern in ["CLAUDE.md", ".claude/CLAUDE.md", ".claude/rules/*.md"]:
files.extend(glob(join(current, pattern)))
return deduplicate_worktree_files(files)Fíjate en un detalle de seguridad: las memorias de tipo "managed" (las que pone la empresa) no se pueden excluir. Es el mecanismo que permite a una organización establecer reglas que ningún usuario puede saltarse.
La carga de memorias soporta una directiva @include que permite incluir otros archivos, con una profundidad máxima de 5 niveles para evitar recursión infinita.
11. Extracción automática de memorias
Después de cada interacción, Claude Code ejecuta un proceso en segundo plano que analiza la conversación y extrae "lecciones aprendidas" — preferencias del usuario, patrones del proyecto, correcciones recibidas.
# Recreación del sistema de extracción de memorias
MEMORY_TYPES = {
"user": "Preferencias, estilo de código, convenciones",
"feedback": "Correcciones y feedback del usuario",
"project": "Detalles técnicos específicos del proyecto",
"reference": "Documentación de APIs, patrones reutilizables",
}
# Lo que NO se debe guardar:
EXCLUDED_FROM_MEMORY = [
"Patrones de código (el código mismo es la fuente de verdad)",
"Historial de git",
"Soluciones de depuración (demasiado efímeras)",
"Contenido de CLAUDE.md (ya se carga por separado)",
"Detalles de tareas temporales",
]
class MemoryExtractor:
def __init__(self, memory_dir):
self.memory_dir = memory_dir
self.last_extracted_message_id = None
self.in_progress = False
self.pending_context = None
self.turns_since_last = 0
def maybe_extract(self, messages, turn_throttle=1):
"""Extrae memorias si toca (ejecuta como agente forked)."""
self.turns_since_last += 1
if self.turns_since_last < turn_throttle:
return
# Si ya hay una extracción en curso, guardar contexto pendiente
if self.in_progress:
self.pending_context = messages
return
self.in_progress = True
self.turns_since_last = 0
# Lanzar agente forked con permisos limitados:
# - Read, Grep, Glob: sin restricciones
# - Bash: solo comandos de lectura
# - Edit, Write: solo dentro del directorio de memorias
run_forked_agent(
messages=messages,
tools=self._restricted_tools(),
max_turns=5,
on_complete=self._on_extraction_done,
)
def _on_extraction_done(self):
self.in_progress = False
# Patrón "trailing run": si hay contexto pendiente,
# hacer una extracción más para no perder información
if self.pending_context:
context = self.pending_context
self.pending_context = None
self.maybe_extract(context)El patrón trailing run es un detalle elegante: si llega una nueva petición de extracción mientras hay una en curso, el contexto se guarda para una ejecución "trailing" posterior. Esto garantiza que ninguna interacción del usuario se pierda, pero sin crear una cola infinita — solo se permite una ejecución pendiente.
La extracción sigue un proceso de dos pasos: primero escribe el archivo de memoria con el contenido, y luego actualiza el índice MEMORY.md con un enlace al nuevo archivo.
12. La selección inteligente de memorias (prefetch)
No todas las memorias son relevantes para cada pregunta. Claude Code usa un modelo rápido (Sonnet) para seleccionar las memorias más pertinentes antes de que el modelo principal procese la consulta:
# Recreación del sistema de selección de memorias
MAX_RELEVANT_MEMORIES = 5
def find_relevant_memories(user_query, available_memories, already_surfaced):
"""Usa un modelo rápido para seleccionar memorias relevantes."""
# Filtrar memorias que ya se han mostrado
candidates = [m for m in available_memories if m.path not in already_surfaced]
# Llamar a un modelo rápido (Sonnet) para seleccionar
selected = side_query(
model="sonnet",
prompt=f"""Selecciona hasta {MAX_RELEVANT_MEMORIES} memorias relevantes
para esta consulta del usuario: {user_query}
Memorias disponibles:
{format_memory_list(candidates)}
Criterios:
- Sé selectivo: solo memorias claramente relevantes
- Omite documentación de APIs para herramientas usadas recientemente
- Pero mantén advertencias y gotchas aunque la herramienta sea conocida
""",
)
return selectedEste mecanismo de prefetch se ejecuta en paralelo con la llamada principal a la API, de modo que las memorias ya están disponibles cuando el modelo las necesita, sin añadir latencia.
13. DreamTask — La consolidación de memorias "mientras duerme"
Esta es la joya del sistema de memoria. Cuando Claude Code está inactivo (sin interacción del usuario), puede ejecutar un proceso de consolidación que revisa sesiones pasadas y reorganiza las memorias — exactamente como el cerebro humano consolida memorias durante el sueño REM.
# Recreación del sistema DreamTask
DREAM_DEFAULTS = {
"min_hours": 24, # Horas mínimas desde la última consolidación
"min_sessions": 5, # Sesiones mínimas para disparar
}
SESSION_SCAN_INTERVAL_MS = 10 * 60 * 1000 # Escanear cada 10 minutos
class DreamTask:
STARTING = "starting"
UPDATING = "updating"
def __init__(self, memory_dir):
self.phase = self.STARTING
self.sessions_reviewing = 0
self.files_touched = 0
self.turns = 0
def should_dream(self, last_consolidation_time, session_count):
"""Decide si es momento de soñar."""
hours_since_last = hours_since(last_consolidation_time)
if hours_since_last < DREAM_DEFAULTS["min_hours"]:
return False
if session_count < DREAM_DEFAULTS["min_sessions"]:
return False
if consolidation_lock_exists():
return False
return True
def dream(self, session_transcripts, memory_dir):
"""Ejecuta la consolidación en 4 fases."""
# Fase 1 — Orientación
# Leer el directorio de memorias y el índice MEMORY.md
index = read_file(join(memory_dir, "MEMORY.md"))
existing = list_memory_files(memory_dir)
# Fase 2 — Recopilación
# Leer transcripciones, buscar memorias desactualizadas,
# identificar patrones recurrentes
insights = analyze_transcripts(session_transcripts)
drifted = find_drifted_memories(existing, insights)
# Fase 3 — Consolidación
# Crear/actualizar archivos de memoria
for insight in insights:
write_or_update_memory(insight, memory_dir)
self.files_touched += 1
# Fase 4 — Poda e indexación
# Mantener MEMORY.md por debajo de ~25KB
prune_and_reindex(memory_dir, max_size_kb=25)
self.phase = self.UPDATINGLas condiciones de activación son conservadoras: deben haber pasado al menos 24 horas desde la última consolidación y tener al menos 5 sesiones nuevas. Esto evita consolidaciones innecesarias pero garantiza que las memorias se mantengan actualizadas.
El proceso de consolidación sigue 4 fases:
Orientarse: leer el directorio de memorias y el índice actual
Recopilar: analizar transcripciones de sesiones recientes, detectar memorias obsoletas
Consolidar: crear o actualizar archivos de memoria
Podar e indexar: mantener el índice
MEMORY.mdpor debajo de ~25KB
El límite de 25KB del índice es un detalle curioso: lo impone el prompt de consolidación, no el código. Es un ejemplo de "prompt como política" — el modelo se auto-limita siguiendo instrucciones textuales.
14. El escáner de secretos
Cuando las memorias se comparten entre miembros de un equipo, existe un riesgo real: que alguien haya mencionado una API key en una conversación y que esa key se guarde como memoria. Claude Code incluye un escáner de secretos que analiza el contenido antes de subirlo:
# Recreación del escáner de secretos para memorias de equipo
SECRET_PATTERNS = {
"aws_access_key": r"AKIA[0-9A-Z]{16}",
"aws_secret_key": r"[0-9a-zA-Z/+]{40}",
"github_pat": r"ghp_[0-9a-zA-Z]{36}",
"github_fine_grained": r"github_pat_[0-9a-zA-Z_]{82}",
"gitlab_token": r"glpat-[0-9a-zA-Z_-]{20}",
"slack_token": r"xox[baprs]-[0-9a-zA-Z-]+",
"stripe_key": r"[sr]k_(live|test)_[0-9a-zA-Z]{24,}",
"openai_key": r"sk-[0-9a-zA-Z]{20,}",
"anthropic_key": r"sk-ant-[0-9a-zA-Z-]+",
"generic_private_key": r"-----BEGIN (RSA|DSA|EC|PGP) PRIVATE KEY-----",
# ... 25+ reglas en total, derivadas de gitleaks
}
def scan_for_secrets(content):
"""Escanea contenido en busca de secretos."""
findings = []
for rule_id, pattern in SECRET_PATTERNS.items():
matches = re.findall(pattern, content)
if matches:
findings.append({
"rule": rule_id,
"label": rule_id_to_label(rule_id),
"count": len(matches),
})
return findings
def redact_secrets(content):
"""Reemplaza secretos detectados por [REDACTED]."""
for pattern in SECRET_PATTERNS.values():
content = re.sub(pattern, "[REDACTED]", content)
return contentEl escáner cubre más de 25 tipos de secretos: AWS, GCP, Azure, GitHub, GitLab, Slack, Stripe, OpenAI, Anthropic, claves privadas genéricas y cadenas de alta entropía. Las reglas están derivadas de gitleaks, un proyecto open source de escaneo de secretos en repositorios git.
15. Flujo completo: qué pasa cuando una conversación se alarga
Para unir todas las piezas, veamos qué ocurre en una sesión larga:
Tokens: 0 ────────────────────────────── 200K
[conversación crece...]
→ 10K tokens: primera extracción de memoria de sesión
→ cada 5K tokens + 3 tool calls: actualizar memoria de sesión
→ 147K tokens: AVISO — "te acercas al límite"
→ 167K tokens: AUTO-COMPACT se activa
1. ¿Hay memoria de sesión? → Intentar sessionMemoryCompact
2. ¿No basta? → fullCompact (resumen con IA)
3. Reconstruir: resumen + mensajes recientes + 5 archivos releídos
→ Tras compactar: ~40-50K tokens
[la conversación continúa con el resumen como base]
→ Si se acerca al límite otra vez: repetir
→ Si falla 3 veces: circuit breaker, desactivar auto-compact
→ Al terminar, si han pasado 24h+ y hay 5+ sesiones: DreamTask consolidaLa post-compactación ejecuta una limpieza de estado que reinicia: el estado de microcompact, el colapsado de contexto, el cache de archivos de memoria, las secciones del prompt de sistema, las aprobaciones del clasificador y el cache de mensajes de sesión. La amplitud de este reseteo revela cuánto estado se acumula durante una sesión.
16. Curiosidades
"Compactando al compactador"
Cuando la propia petición de compactación es demasiado larga para la ventana de contexto (error prompt-too-long), el sistema elimina el 20% de los grupos de mensajes más antiguos y reintenta, hasta 3 veces. Es la recursión definitiva: comprimir el comprimir.
Las imágenes se convierten en texto antes de compactar
Antes de enviar la conversación al modelo de compactación, todas las imágenes se reemplazan por el marcador [image]. Las imágenes consumen muchos tokens pero su contenido visual no se puede resumir como texto — es más eficiente descartarlas y confiar en que la información importante está en el texto circundante.
La transcripción como red de seguridad
El mensaje de resumen de compactación incluye la ruta al archivo JSONL de la transcripción completa. Si el modelo necesita recuperar un detalle que se perdió en la compactación, puede leer la transcripción original. Es una red de seguridad que reconoce que la compactación es imperfecta.
Protección anti-redirección de memorias
El sistema de rutas de memoria excluye explícitamente projectSettings de la resolución del directorio de auto-memorias. El motivo: un repositorio malicioso podría incluir un .claude/settings.json que redirija la escritura de memorias a una ruta controlada por el atacante.
El umbral de 8K que casi nadie alcanza
El sistema empieza con un límite de salida de 8.000 tokens (porque el p99 de producción es de solo 4.911 tokens), y lo escala a 64.000 solo si se necesita. Esta "reserva de slot" reduce el desperdicio de recursos — menos del 1% de las peticiones alcanzan el tope de 8K.
El modelo de confianza de las memorias
Las memorias se tratan como "afirmaciones históricas", no como verdades actuales. Una memoria que nombra una función o archivo específico es una afirmación de que existía cuando la memoria fue escrita — pero el código puede haber cambiado desde entonces. El sistema reconoce explícitamente que las memorias pueden volverse obsoletas.
Fuentes
Análisis basado en la filtración del código fuente de Claude Code v2.1.88, documentada en el Post 1 de esta serie
The Verge — "Claude Code leak exposes a Tamagotchi-style 'pet' and an always-on agent"
Esta serie es un proyecto de Levante — un cliente de escritorio open source para trabajar con MCPs, modelos y herramientas en una sola interfaz. Si te interesa la arquitectura de herramientas de IA, te interesa Levante.
Saúl Gómez es fundador de Levante, un cliente de escritorio open source para trabajar con modelos de IA.
