LangGraph — Módulo 6: persistencia y memoria de corto plazo
Sexto módulo del curso de LangGraph: checkpointers y threads para que el agente recuerde entre turnos. InMemorySaver, PostgresSaver, thread_id, get_state y get_state_history.
☰ Índice del contenido
- La tesis del módulo
- El problema, en concreto
- La solución: el checkpointer
- La segunda pieza: el thread
- Inspeccionar el estado guardado
- De desarrollo a producción: Postgres
- Memoria de corto plazo vs largo plazo
- Un aviso sobre el historial largo
- Errores comunes
- La frase para llevarse
- Qué viene
- Material relacionado
Sexto módulo del Curso completo de LangGraph. El agente del Módulo 5 funciona, pero tiene un problema serio: es amnésico. Cada invoke empieza con el estado en blanco. Si le dices tu nombre y luego preguntas cuál es, no tiene idea. Hoy lo arreglamos.
La tesis del módulo
La memoria de un agente no es una feature del modelo: es persistencia de estado. Un checkpointer guarda el estado del grafo después de cada paso. Un thread identifica a qué conversación pertenece ese estado. Con esas dos piezas, el mismo grafo que ya escribiste recuerda — sin cambiar un solo nodo.
El problema, en concreto
agente.invoke({"messages": [{"role": "user", "content": "Me llamo Felipe."}]})
agente.invoke({"messages": [{"role": "user", "content": "¿Cómo me llamo?"}]})
# → "No tengo esa información."
El segundo invoke creó un estado nuevo desde cero. El historial del primero se perdió al terminar la ejecución. El reducer add_messages acumula dentro de una ejecución, no entre ejecuciones.
La solución: el checkpointer
Un checkpointer es un componente que guarda una foto del estado (un checkpoint) después de cada paso del grafo. Se lo pasas al compilar:
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
agente = builder.compile(checkpointer=checkpointer)
InMemorySaver guarda en memoria RAM: perfecto para desarrollo y pruebas, inútil al reiniciar el proceso. Más abajo lo cambiamos por Postgres.
La segunda pieza: el thread
El checkpointer guarda estados, pero necesita saber de quién. Ese identificador es el thread_id, y va en la configuración de cada invocación:
config = {"configurable": {"thread_id": "felipe-001"}}
agente.invoke(
{"messages": [{"role": "user", "content": "Me llamo Felipe."}]},
config,
)
agente.invoke(
{"messages": [{"role": "user", "content": "¿Cómo me llamo?"}]},
config, # ← el mismo thread
)
# → "Te llamas Felipe."
Eso es todo. Mismos nodos, mismas aristas, misma tool. Lo único que cambió fue compilar con checkpointer y pasar un thread_id.
thread "felipe-001"
┌────────────────────────────────────────────────────┐
│ invoke 1 ──► checkpoint ──► checkpoint ──► guarda │
│ │ │
│ ▼ │
│ invoke 2 ──► carga estado previo ──► continúa │
└────────────────────────────────────────────────────┘
thread "ana-002" ──► estado completamente separado
Un thread_id distinto es una conversación distinta, aislada. En una app real suele ser el id de la sesión de chat, o usuario_id + conversación_id.
Detalle clave: en el segundo
invokepasaste solo el mensaje nuevo, no todo el historial. LangGraph carga el estado guardado, le aplica tu actualización con el reducer y sigue. No armes el historial a mano: ese es justamente el trabajo que te está ahorrando.
Inspeccionar el estado guardado
Como el estado está persistido, puedes leerlo cuando quieras — sin ejecutar el grafo:
snapshot = agente.get_state(config)
snapshot.values # el estado completo (incluye messages)
snapshot.next # nodos pendientes; () significa que terminó
snapshot.metadata # step, source, writes
Y el historial completo de checkpoints, del más reciente al más antiguo:
for snap in agente.get_state_history(config):
print(snap.metadata["step"], snap.next, len(snap.values["messages"]))
Esto no es solo curiosidad: es tu herramienta de debugging. Cuando un agente se comporta raro, get_state_history te muestra paso a paso qué escribió cada nodo. Y es la base del time travel que veremos en el bloque avanzado.
También puedes modificar el estado guardado, como si un nodo lo hubiera escrito:
agente.update_state(config, {"messages": [{"role": "user", "content": "Corrijo: me llamo Felipe B."}]})
update_state crea un checkpoint nuevo (no pisa el anterior) y aplica los reducers normalmente.
De desarrollo a producción: Postgres
InMemorySaver desaparece al reiniciar. En producción quieres una base de datos real:
from langgraph.checkpoint.postgres import PostgresSaver
DB_URI = "postgresql://usuario:clave@host:5432/basedatos"
with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
checkpointer.setup() # crea las tablas e índices (solo la 1ª vez)
agente = builder.compile(checkpointer=checkpointer)
agente.invoke(entrada, config)
Requiere el paquete langgraph-checkpoint-postgres. Hay también SqliteSaver para persistencia local en un archivo, y variantes Async* si tu app es asíncrona.
Con esto, un usuario puede cerrar el navegador, volver mañana y retomar la conversación exactamente donde quedó. El servidor puede reiniciarse en medio y no se pierde nada.
| Checkpointer | Cuándo |
|---|---|
InMemorySaver | Desarrollo, notebooks, tests |
SqliteSaver | Apps locales, un solo proceso |
PostgresSaver | Producción, múltiples instancias |
Memoria de corto plazo vs largo plazo
Lo de este módulo es memoria de corto plazo: vive dentro de un thread. Si el mismo usuario abre una conversación nueva (thread_id distinto), el agente no recuerda nada.
Para que recuerde a través de conversaciones —“este usuario prefiere respuestas breves”, “es alérgico a los mariscos”— se necesita el Store, la memoria de largo plazo. Es el Módulo 10.
Corto plazo (checkpointer) → dentro de un thread → el historial del chat
Largo plazo (store) → entre threads → lo que sabes del usuario
Un aviso sobre el historial largo
El checkpointer acumula todo. Una conversación de 200 turnos son 200 turnos que se le mandan al modelo en cada llamada: caro y, pasado el límite de contexto, imposible. Las estrategias habituales son recortar los mensajes antiguos antes de invocar el modelo, o resumirlos periódicamente en un mensaje de sistema. No confundas “el estado lo guarda todo” con “hay que enviárselo todo al modelo”.
Errores comunes
- Compilar con checkpointer pero olvidar el
thread_id. Sin config, LangGraph no sabe dónde guardar y falla. - Reenviar todo el historial en cada
invoke. Duplica mensajes. Envía solo el turno nuevo. - Usar
InMemorySaveren producción. Funciona en tus pruebas y pierde todo en el primer deploy. - Reutilizar el mismo
thread_idpara usuarios distintos. Les mezclas las conversaciones. Es un problema de privacidad, no solo un bug. - Olvidar
checkpointer.setup()la primera vez con Postgres: las tablas no existen.
La frase para llevarse
“El agente no recuerda porque el modelo sea listo. Recuerda porque tú guardaste el estado y le pusiste nombre a la conversación.”
Qué viene
En el Módulo 7 aprovechamos que el estado ahora se puede pausar y retomar para meter a un humano en el ciclo: human-in-the-loop. El agente se detiene antes de una acción sensible, espera tu aprobación y continúa. Sin checkpointer esto sería imposible — por eso venía después.
Material relacionado
- Glosario de LangGraph
- Memoria de agentes — el concepto general, corto y largo plazo
- Módulo 5: tools y el patrón ReAct
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.