En pocas palabras: Cuando el agente crashea, MyCodeAgent recupera la sesión reconstruyéndola desde un transcript JSONL append-only en disco, donde vive todo el historial conversacional. Tres piezas lo logran: ese archivo de eventos, un clasificador de herramientas interrumpidas y una Session Memory derivada incrementalmente del stream.
La recuperación de agentes IA tras un crash se resuelve con persistencia de eventos, no con suerte: si el historial conversacional vive solo en la memoria del proceso, un timeout de red, un OOM o un Ctrl+C lo borra entero. MyCodeAgent lo evita escribiendo cada hecho clave en un archivo JSONL append-only desde el que reconstruye la sesión completa al reiniciar.
La recuperación de agentes IA tras un crash es el conjunto de técnicas que permite a un code agent retomar su trabajo después de que el proceso muere. MyCodeAgent, proyecto open source diseccionado en la entrega 10 de la serie Code Agent Dissection, la implementa con tres piezas: un transcript JSONL append-only, un clasificador de herramientas interrumpidas y una Session Memory derivada incrementalmente del stream de eventos.
En 30 segundos
- Sin persistencia, un crash borra todo: el historial vive en una lista en memoria y muere con el proceso (un timeout de red, un OOM o un Ctrl+C alcanzan).
- MyCodeAgent escribe un transcript JSONL append-only con cada hecho clave del loop, y al reiniciar reconstruye la sesión reproduciendo esos eventos.
- Cada herramienta termina en cuatro estados posibles al recuperar: completada, fallida, pendiente o incierta; las inciertas piden verificación humana.
- Read, Grep y Glob se pueden reejecutar; Edit, Bash y Task tienen efectos secundarios y no se reproducen a ciegas nunca.
- La Session Memory condensa miles de eventos en unos cientos de líneas para dar contexto entre sesiones sin reventar el presupuesto de tokens.
Contexto rápido: MyCodeAgent es un agente de código open source, alojado en GitHub, que su autor publica como material de estudio para explicar con código en mano cómo funciona un code agent por dentro. Y la entrega 10 de la serie agarra justo el tema más incómodo de todos: qué pasa cuando todo se cae a mitad de tarea.
¿Qué sucede con el historial cuando un agente se bloquea?
Se pierde completo. Sin persistencia en disco, la conversación vive en una lista en memoria y muere junto con el proceso, así que tras reiniciar el usuario arranca de cero (sí, en serio) y decenas de pasos de exploración desaparecen sin rastro.
Ponele la escena: le pedís al agente refactorizar el módulo de autenticación y va avanzando paso a paso: lee archivos, aplica edits y corre los tests hasta llegar al paso veintitrés. Ahí el proceso muere por un timeout de red. Y cuando lo reiniciás, arrancás de nuevo, porque toda la “memoria” de la sesión vivía en la RAM y se evaporó junto con él. Según el análisis de la serie, ese es exactamente el punto de partida del problema: la historia conversacional entera descansa en un objeto en memoria mientras el proceso corre.
¿Y el resultado de la herramienta que estaba corriendo en ese instante? Peor: ni siquiera llegó a registrarse.
¿Dónde se guarda el historial de conversación de un agente de código?
En MyCodeAgent, en archivos JSONL dentro de memory/transcripts/: el agente principal escribe en transcript-session-abc123.jsonl y cada subagente lanzado por una llamada Task tiene su propio transcript-subagent-child-xyz.jsonl. Al lado conviven dos archivos más: un trace de depuración (session-abc123.jsonl) y un reporte visual opcional en HTML.
Lo interesante es que transcript y trace salen del mismo stream de eventos, pero cumplen roles distintos. Un CompositeRuntimeEventSink recibe cada evento y lo reenvía a dos suscriptores: el trace registra todo con lujo de detalle (uso de tokens, tiempos por paso, salida cruda del modelo) para debugging, mientras el transcript guarda solo los hechos que importan para recuperarse. Uno es el diario íntimo del sistema; el otro es lo que leés cuando algo explota.
Ojo con un detalle práctico: esos JSONL quedan en el disco de la máquina donde corre el proceso, así que incluí esa carpeta en tus backups.
Event sourcing vs snapshots: ¿qué estrategia conviene para persistir un agente?
Para este caso, event sourcing, y la razón es técnica. Un snapshot serializa el estado completo de forma periódica (como un backup de base de datos), exige garantías de atomicidad para no grabar medio estado y te obliga a elegir cuál restaurar al volver. Un stream de eventos agrega un registro por cada operación completada (como el Write-Ahead Log de una base de datos), es atómico por naturaleza porque cada línea es independiente y se reproduce entero al reiniciar.
| Criterio | Snapshots | Event sourcing (stream) |
|---|---|---|
| Qué guarda | Estado completo serializado, de forma periódica | Un registro por operación completada |
| Analogía | Backup de base de datos | Write-Ahead Log (WAL) |
| Atomicidad | Requiere garantías explícitas (prohibido escribir medio estado) | Natural: cada línea es independiente |
| Restauración | Elegir qué snapshot retomar | Reproducir eventos hasta el último completo |
| Determinismo | Depende del momento elegido | Mismo transcript produce siempre el mismo estado |

Cada evento captura un hecho puntual del loop: contenido de mensajes user/assistant/tool, transiciones de estado con su motivo, fases de cada herramienta (requested, started, completed, failed), checkpoints de compresión de contexto y motivo de terminación. Nada de “estado general”: hechos verificables, línea por línea. En cómo guarda ChatGPT sus conversaciones profundizamos sobre esto.
¿Cómo funciona la recuperación de agentes IA tras un crash?
Con agent.resume_transcript(): el ResumeLoader lee el transcript, reproduce los eventos y arma un estado limpio que luego se inyecta al runtime mediante ResumeState.apply_to_host(). El agente sigue como si nunca hubiera crasheado, y hay un detalle que me parece clave: la recuperación es determinista, el mismo transcript produce siempre el mismo estado, sin depender de nada aleatorio.
- Reconstruye el historial de mensajes a partir de los eventos de conversación guardados línea a línea.
- Conserva el último checkpoint de compresión, para que la proyección del contexto vuelva a plegar la historia vieja igual que antes del accidente.
- Agrega el ciclo de vida de cada tool call juntando, por tool_call_id, todos los estados registrados.
- Inyecta todo al host en marcha: resetea el motor de contexto, escribe el historial en HistoryManager, reactiva los checkpoints y restaura el caché de locks optimistas de lectura (snapshots de mtime que evitan falsos conflictos en Edit).
¿Qué pasa si una herramienta se interrumpe a mitad de ejecución?
Queda marcada como acción incierta, que es el concepto más fino de todo el esquema. Si el proceso murió mientras un Edit modificaba un archivo, ese archivo puede estar cambiado a medias o intacto, y no existe log que lo diga. El loader entonces clasifica cada llamada según su ciclo de vida:
- Completed: terminó bien; no se reproduce.
- Failed: ya falló; tampoco se toca.
- Requested sin started: nunca llegó a ejecutarse; queda pendiente y el agente puede replanificarla.
- Started sin resultado: estado desconocido; se marca como incierta.
Ahora bien, no todas las herramientas pesan igual. Read, Grep y Glob son idempotentes: repetirlas no cambia nada, así que se reejecutan sin drama. Edit, Bash y Task tienen efectos secundarios, y reproducirlas a ciegas puede duplicar cambios o dejar el repo en un estado peor que el original. Por eso el CLI imprime las acciones inciertas al recuperar y te deja la decisión a vos: quizás se ejecutaron, quizás no, verificá vos. ¿Y el agente podría decidir solo? Podría, pero adivinar el estado de tu filesystem me parece una mala idea, y el diseño lo dice sin rodeos.
¿Cómo escribe TranscriptStore sin corruptear el archivo?
Con cuatro medidas concretas sobre el JSONL. Primera: escritura estrictamente append-only, nunca reescribe líneas existentes. Segunda: un lock de archivo que evita corrupción por escrituras concurrentes. Tercera: flush inmediato a disco, sin confiar en el buffer del sistema operativo. Cuarta, y esta es mi favorita: _repair_trailing_record() revisa el final del archivo antes de cada escritura y, si encuentra una línea incompleta (sin newline o con JSON roto), la trunca. Esa línea era el remanente de una escritura interrumpida por el crash anterior. Esto se conecta con lo que analizamos en el razonamiento de los modelos de lenguaje.
Entre el loop y el store hay capas de abstracción: TranscriptRecorder encapsula las operaciones de alto nivel (grabar mensajes, transiciones, ciclos de herramienta), oculta la serialización JSON y, después de cada escritura, llama a SessionMemoryManager.ingest_event() para actualizar la memoria de sesión sobre la marcha.
¿Qué es la Session Memory y por qué no meter el transcript entero en el prompt?
La Session Memory es un resumen acotado derivado del stream de hechos: cientos de líneas contra los miles de eventos que puede acumular un transcript largo. Mandarle el transcript completo al modelo reventaría el presupuesto de tokens, así que el sistema extrae lo relevante y lo inyecta como mensaje de sistema, entre el prompt de sistema y el historial, bajo el rótulo [Session Memory]: “antes completaste X, fallaste en Y, tu objetivo actual es Z”.
- El objetivo vigente: el último mensaje del usuario marca hacia dónde va la sesión.
- Lo completado: mensajes finales del assistant que cierran tareas.
- Decisiones clave: checkpoints de compresión y transiciones de estado importantes.
- Lo que falló: eventos tipo model_recovery_failed quedan registrados.
- Tareas pendientes: items de TodoWrite sin cerrar, más el estado de verificación del completion gate.
El detalle fino: no se reconstruye en cada recuperación. Se actualiza incrementalmente con cada evento escrito, así que el costo se reparte durante la sesión en vez de pagarse todo junto al volver de un crash.
Errores comunes al implementar persistencia en agentes
Si vas a robar estas ideas para tu propio agente (y deberías), estos son los tropiezos típicos: Lo explicamos a fondo en todo lo que ofrece Google.
- Confiarle el flush al sistema operativo. Si no escribís a disco en cada evento, el crash se lleva los últimos registros aunque tu código “ya los haya escrito”. El buffer del SO no es tu amigo en un OOM.
- Reejecutar herramientas con efectos secundarios sin verificar. Reproducir un Bash o un Edit a ciegas puede duplicar cambios. Marcá la incertidumbre y consultá al humano.
- Dar por válida la última línea del log. Una escritura cortada deja JSON truncado que rompe el parser. Truncá o repará el remanente antes de leer, tal como hace
_repair_trailing_record(). - Reconstruir todo desde cero en cada arranque. Releer miles de eventos y regenerar resúmenes completos sale caro. Mantené derivados incrementales, como hace la Session Memory.
Preguntas Frecuentes
¿Dónde guarda el historial un agente de código como MyCodeAgent?
En archivos JSONL append-only dentro de memory/transcripts/, uno por sesión (transcript-session-*.jsonl) más uno independiente por subagente. Aparte mantiene un trace de depuración detallado y un reporte HTML opcional, todos en el disco local de la máquina donde corre el proceso.
¿Se puede retomar una sesión después de un crash?
Sí. resume_transcript() reproduce el transcript, reconstruye historial, checkpoints y estados de herramientas, y reinyecta todo al runtime con apply_to_host(). Las acciones de resultado desconocido quedan marcadas como inciertas para que el usuario las verifique antes de continuar.
¿Cuál es la diferencia entre event sourcing y snapshots?
Un snapshot serializa el estado completo de forma periódica y exige atomicidad para no grabar estados a medias. Event sourcing agrega un registro por operación completada; al reiniciar se reproducen los eventos y el estado vuelve de forma determinista, sin elegir puntos de restauración.
¿Qué herramientas se pueden reejecutar tras un crash?
Solo las idempotentes, como Read, Grep y Glob, porque repetirlas no altera resultados. Las que tienen efectos secundarios (Edit, Bash, Task) requieren que el usuario verifique manualmente qué pasó antes de decidir si se repiten o se replanifican.
¿MyCodeAgent es gratuito y puedo usarlo?
Es open source y está en GitHub: clonás el repo, cargás tu API key de LLM y lo levantás con uv run python main.py. Está pensado para leerlo, modificarlo y extenderlo construyendo tu propio agente.
Conclusión
Lo que cambió con este diseño es simple de enunciar y enorme en la práctica: un crash dejó de significar empezar de cero. Persistiendo hechos en un JSONL append-only, marcando lo incierto en vez de esconderlo y manteniendo memoria incremental, MyCodeAgent demuestra que la recuperación robusta es una decisión de arquitectura temprana, no un parche de última hora. Mi recomendación concreta: cloná el repo, leé runtime/transcript.py con la serie al lado y llevate el patrón completo a tus propios proyectos. Tu yo de las 2 de la mañana, reiniciando procesos en producción, te lo va a agradecer.
