# Memoirs: Enseñando a agentes a recordar (sin perder la cabeza en la nube)


## La amnesia de los mil tokens

Trabajar con agentes conversacionales (Claude, Cursor, iteraciones CLI) tiene un peaje invisible: siempre parecen levantarse con la memoria borrada. Su contexto, por muy grande que sea hoy, parte de una hoja en blanco en cada nuevo proyecto. Si logras convencerlos de que mantengan una hebra, terminas con monstruosidades arquitectónicas que envían todo el historial del chat ruidosamente a la ventana de contexto. Pagar el costo de inferencia de 40K tokens, para que al final el agente ignore lo más importante oculto en el centro del documento (el fatídico efecto *Lost in the Middle*), no sólo es costoso, sino ineficiente.

Existen soluciones corporativas pesadas y bases de datos vectoriales subidas a la nube con llamadas de red carísimas. Pero yo no pretendo exponer mis notas de arquitectura ni mis credenciales ofuscadas a una API externa que me responde en 2 segundos cuando la red va bien. Quería una solución *local-first*, que mantuviera la privacidad de las decisiones operativas, consolidando eficientemente mis variables de entorno, mis elecciones de frameworks y mi estilo. 

Así nació [memoirs](https://github.com/misaelzapata/memoirs). No un simple motor RAG (Retrieval-Augmented Generation). Eso hubiese sido fácil y aburrido. Quería un sistema real que olvidara orgánicamente y consolidara lo que importa.

## La anatomía subyacente: 6 capas sobre SQLite

Decidí anclar la persistencia en algo brutalmente sólido y subestimadamente poderoso hoy en día: SQLite. Pero no un simple archivo `.db`. Un SQLite hipervitaminado con `sqlite-vec` para búsqueda densa (vectores) y `FTS5` nativo (funciones léxicas BM25).

La arquitectura se resolvió en 6 capas, porque un sistema de memoria no es sólo un almacén, es una planta de tratamiento de agua:
1. **Raw logs**: El tubo de desagüe que recibe todo el historial y los *diffs* ruidosos.
2. **Extraction**: Un modelo LLM curador corriendo localmente (mi opción habitual actualmente es Qwen 2.5 3B, agilísimo) escoge candidatos (heurísticas, credenciales, preferencias, estilo).
3. **Graph**: El momento de relacionarlo usando principios de *Zettelkasten*, uniéndolo con el resto de nodos, encontrando conexiones semánticas compartidas.
4. **Indexado Dual**: Actualizaciones atómicas en paralelo (sqlite-vec y FTS5).
5. **Memory Engine**: La curación donde las memorias ganan o pierden puntuación y rango válido (con soporte bi-temporal).
6. **Surface**: La capa superficial por la cual se expone (vía HTTP, REST y sobre todo un MCP con 22 endpoints RPC para que un agente lo consulte limpiamente).

## RRF y BM25: Vectores no resuelven todo

Una de las grandes crisis de desarrollo fue observar que los vectores de `sentence-transformers` fallaban espectacularmente cuando le pedía a `memoirs` que recuperara identificadores específicos o nombres de herramientas oscuras. Los espacios vectoriales mapean semántica maravillosamente: saben que "coche" es cercano a "vehículo". Pero cuando un modelo guarda la frase *"En el sistema viejo usaba psql-driver-v9"*, el embedding semántico no le da prioridad al nombre exacto. Para un humano (o un agente), eso es determinante.

La solución (mi momento de revelación en la búsqueda) fue combinar algo muy nuevo con algo muy clásico: Reciprocal Rank Fusion (RRF). Ejecuto al mismo tiempo una consulta semántica densa y una búsqueda pura de texto por índice invertido (BM25 vía FTS5). Si BM25 lo encuentra exactamente, y la búsqueda semántica lo entiende por contexto general, RRF estabiliza los puntajes y lo sube firmemente al puesto número uno de los resultados recuperados, bajando mis percentiles (p50) a latencias asombrosas (~3.9 ms), un desempeño letal para una base de datos local que supera por mucho a los servicios prefabricados en la nube.

## La asombrosa curva de Ebbinghaus y el sueño asíncrono

Un sistema no tiene "memoria" a largo plazo si no es capaz de olvidar. Los agentes tienden a ahogarse en conocimiento rancio ("Ayer me peleé con las APIs de Docker" seguido de "Hoy desechamos Docker y estamos con llamadas OCI puras"). Si un motor de memoria preserva y emite al agente ambos hechos con la misma fuerza, el agente inevitablemente tomará la decisión de desarrollo equivocada.

Tuve que meterme a los apuntes viejos e implementar funciones basadas en la Curva del Olvido de Hermann Ebbinghaus: $R(t) = e^{-\Delta t_h / (S \cdot 24)}$. Las nuevas introducciones van perdiendo su peso por el paso del tiempo asintóticamente. Pero su 'Strength' (S) –su fortaleza original– se multiplica cada vez que el ecosistema consulta positivamente dicha memoria. Las variables o convicciones fundamentales terminan decayendo a un ritmo mucho más lento que las anécdotas de depuración circunstanciales de una sesión de programación solitaria.

Claro, este peso procesal y la propia consolidación y purga de conflictos semánticos es demasiado computo para ejecutar en tiempo real. Y eso exigió implementar un mecanismo que los humanos usan constantemente pero que el software casi nunca aprovecha: el *sueño*.
Escribí un demonio (`sleep_consolidation.py`) que detecta los momentos valle de CPU; solo cuando el desarrollador y los agentes dejan el teclado para ir a servirse café (literalmente "tiempos donde el lock está libre y el Idle es mayor a N minutos"). En esa ventana asíncrona ciega, el modelo local de Qwen o Phi despierta, repasa los perfiles almacenados recientemente, busca las contradicciones entre versiones y consolida/archiva la memoria obsoleta, compactando el grafo silenciosamente en segundo plano antes de la siguiente interacción.

Al final, lograr un ratio perfecto de MRR (Mean Reciprocal Rank) en bancos de prueba de memoria como LoCoMo, mientras el overhead en RAM del modelo apenas pasaba de 231 MB frente a gigantes como LlamaIndex o Mem0, me confirmó el objetivo. Resulta que las mejores características para agentes no se escriben enviando millones de tokens a proveedores remotos; se escriben entendiendo muy bien índices básicos y imponiendo una disciplina de recursos implacable en tu propia máquina.

