Opinión

Memoria inmutable de cinco capas para un agente de IA: por qué una sola base vectorial nunca es suficiente

Intentar guardar toda la memoria a largo plazo de un agente de IA en un único índice vectorial fracasa, porque la búsqueda exacta por palabras, la similitud semántica, el recorrido de relaciones en varios pasos y el historial de cambios de decisiones requieren estructuras de datos físicamente distintas. Un índice incremental de SQLite FTS5 con ranking BM25 sobre 66.995 sesiones y 538.956 mensajes (1,35 GB) eliminó los cuelgues del entorno de desarrollo y redujo el arranque de sesión de 15 segundos a décimas de segundo. Para proteger la memoria de la degradación se implementó la sanitización determinista de claves mediante UUID5, la escritura bitemporal sin borrar hechos antiguos y un ciclo nocturno de consolidación con desgaste según la distribución de Weibull.

Memoria inmutable de cinco capas para un agente de IA: por qué una sola base vectorial nunca es suficiente
Hamidun News.
Escuchar artículo
Ficha técnica y cifras clave
ParámetroDatos
Editor jefe
Volumen del índice episódico66.995 sesiones y 538.956 mensajes en una base SQLite FTS5 de 1.353 MB
Base del segundo cerebro y embeddings10.019 tarjetas de conocimiento estructuradas y un `brain.db` de 2,37 GB
Dato clave #2Velocidad de arranque tras eliminar los hooks pesados: la latencia de inicialización cae de 5–15 s a < 0,2 s
Umbrales de la consolidación nocturnafusión de duplicados con similitud coseno ≥ 0,70 y detector de contradicciones con umbral de 0,40, sin necesidad de un LLM
  • Volumen del índice episódico: 66.995 sesiones y 538.956 mensajes en una base SQLite FTS5 de 1.353 MB
  • Base del segundo cerebro y embeddings: 10.019 tarjetas de conocimiento estructuradas y un `brain.db` de 2,37 GB
  • Velocidad de arranque tras eliminar los hooks pesados: la latencia de inicialización cae de 5–15 s a < 0,2 s
  • Umbrales de la consolidación nocturna: fusión de duplicados con similitud coseno ≥ 0,70 y detector de contradicciones con umbral de 0,40, sin necesidad de un LLM

El punto de partida de mi arquitectura de memoria fue un cuelgue brutal del editor de código. Al intentar abrir el historial de conversaciones con el agente de IA, el proceso Extension Host superó su límite de memoria y se vino abajo arrastrando consigo la terminal. En medio año de trabajo había acumulado 2,45 GB de logs de sesión en bruto en formato JSONL, y la extensión intentaba leer sus cabeceras desde disco en cada arranque. Encima, la configuración de fábrica `cleanupPeriodDays=30` borraba en silencio las sesiones de más de un mes de antigüedad, y para cuando la cambié a `99999`, parte de mi archivo de invierno ya había desaparecido.

Una revisión mostró que dos de mis cuatro bases de memoria estaban prácticamente muertas, porque requerían levantar manualmente unos daemons en segundo plano. El verdadero descubrimiento, sin embargo, fue que la "memoria del agente" en realidad son cinco clases distintas de preguntas, y cada una necesita su propio motor.

¿Por qué SQLite FTS5 le ganó a

Qdrant y a grep con medio millón de mensajes?

Para la memoria episódica ("¿dónde hablamos de la configuración de los webhooks hace tres meses?"), `grep` sobre gigabytes de texto resultaba demasiado lento, y levantar una instancia local de `Qdrant` con `fastembed` al inicio de la sesión añadía entre 5 y 15 segundos al arranque y consumía cientos de megabytes de RAM.

El ganador fue el motor SQLite FTS5 integrado en Python, con ranking BM25 y modo `WAL`. El indexador `search_chats.py` compara `file_mtime` con la tabla `sessions`: si la diferencia es menor a un segundo, el archivo se salta.

Los logs en bruto de más de 14 días y los volcados de más de 20 MB se mueven a un archivo frío: el directorio de trabajo bajó de 2,45 GB a 527 MB, los cuelgues desaparecieron y el índice creció hasta 66.995 sesiones y 538.956 mensajes (290.

544 del humano y 248.412 del asistente) con un peso de 1.353 MB.

Para los documentos de referencia críticos (como un manual de 20 capítulos armado a partir de 2.240 mensajes y 177 notas de voz transcritas con Deepgram), se aplica un respaldo en 4 niveles: un resumen en memoria de trabajo, un puntero en `MEMORY.md` (limitado estrictamente a 200 líneas y ~22 KB), una regla de enrutamiento y una instantánea vectorial.

¿Para qué sirve un UUID5 determinista en una base vectorial y cómo

funciona la bitemporalidad?

Cuando la consulta no contiene las palabras exactas (alguien pregunta "cómo lavar frutas sin químicos" mientras la base tiene una nota sobre el modo alcalino `pH 11,5` de un ionizador), entra en juego la capa semántica basada en embeddings (384 dimensiones en local y 1536 para libros). En la práctica, los identificadores de documentos se ven como cadenas tipo `knowledge:42` o `session/2026-04-10/msg-17` con caracteres cirílicos, pero el motor vectorial Qdrant exige que el `point_id` sea un entero o un UUID estricto. Un `uuid4()` aleatorio genera duplicados al reindexar, así que introdujimos un `uuid5` determinista sobre el espacio de nombres DNS:

```python import uuid

def to_deterministic_point_id(raw_doc_id: str) -> str: # Convierte cualquier cadena con cirílico y caracteres especiales en un UUID5 ASCII estable return str(uuid.uuid5(uuid.NAMESPACE_DNS, raw_doc_id))

point_id = to_deterministic_point_id(doc_id) payload = {**metadata, "doc_id": doc_id, "content": text_chunk} ```

La misma cadena siempre produce un UUID idéntico, con lo que cada reescritura se convierte en un `upsert` idempotente, mientras que la clave original se conserva en el `payload`.

Para que el agente recuerde el historial de cambios de decisiones ("¿por qué en primavera cambiamos de motor?"), tomamos prestada una idea de Datomic y prohibimos los `UPDATE` que sobrescriben el pasado. Cada relación tiene campos `valid_from`, `valid_to` e `ingested_at`, y la base del segundo cerebro suma además contadores de uso `use_count` y `last_used`. Cuando una decisión queda obsoleta, a la arista antigua se le asigna un `valid_to`, y junto a ella se inserta un nuevo registro con un enlace `supersedes`.

¿Cómo limpia el sueño algorítmico con una curva de

Weibull las contradicciones de la memoria?

Para que la base de datos no se convierta en un basurero, un pipeline de consolidación en segundo plano (los módulos `dream` y `sleeptime`) ejecuta periódicamente una limpieza en tres etapas:

1. Agrupamiento de duplicados (SHMR): una matriz de similitud coseno sobre embeddings de 1024 dimensiones (`Qwen3-Embedding-0.6B` en LanceDB) fusiona los registros con similitud `>= 0,70`. 2. Desgaste selectivo según distribución de Weibull: las observaciones pasajeras (`observation`) se desvanecen a las 336 horas (~2 semanas), los hechos de trabajo a las 2.160 horas, mientras que las preferencias personales (`preference`), los procedimientos y los patrones de error (`error_pattern`) quedan protegidos por una lista `EXEMPT_TYPES`. 3. Búsqueda de contradicciones sin LLM: un escáner determinista busca conflictos en una ventana de 7 días mediante señales ponderadas (`negation: 0,35`, `antonym: 0,25`, `numeric: 0,25`, `temporal: 0,15`) con un umbral de `0,40`.

Este esquema de memoria en cinco capas (SQLite FTS5, LanceDB y un grafo temporal) está integrado en el núcleo de [Nomad AI](https://nomadnet.ai) y de la plataforma en la nube [agent.nomadnet.ai](https://agent.nomadnet.ai). Hay un curso sobre ingeniería de memoria disponible en [Hamidun Academy](https://academy.hamidun.com) (prueba de 72 horas), y los scripts de indexación están en la biblioteca de [Vibe Club](https://hamidun.com/club).

Preguntas frecuentes sobre la noticia

¿Por qué SQLite FTS5 le ganó a Qdrant y a grep con medio millón de mensajes?

Para la memoria episódica ("¿dónde hablamos de la configuración de los webhooks hace tres meses?"), `grep` sobre gigabytes de texto resultaba demasiado lento, y levantar una instancia local de `Qdrant` con `fastembed` al inicio de la sesión añadía entre 5 y 15 segundos al arranque y consumía cientos de megabytes de RAM. El ganador fue el motor SQLite FTS5 integrado en Python, con ranking BM25 y modo `WAL`. El indexador `search_chats.py` compara `file_mtime` con la tabla `sessions`: si la diferencia es menor a un segundo, el archivo se salta. Los logs en bruto de más de 14 d

¿Para qué sirve un UUID5 determinista en una base vectorial y cómo funciona la bitemporalidad?

Cuando la consulta no contiene las palabras exactas (alguien pregunta "cómo lavar frutas sin químicos" mientras la base tiene una nota sobre el modo alcalino `pH 11,5` de un ionizador), entra en juego la capa semántica basada en embeddings (384 dimensiones en local y 1536 para libros). En la práctica, los identificadores de documentos se ven como cadenas tipo `knowledge:42` o `session/2026-04-10/msg-17` con caracteres cirílicos, pero el motor vectorial Qdrant exige que el `point_id` sea un entero o un UUID estricto. Un `uuid4()` aleatorio genera duplicados al reindexar, así

¿Cómo limpia el sueño algorítmico con una curva de Weibull las contradicciones de la memoria?

Para que la base de datos no se convierta en un basurero, un pipeline de consolidación en segundo plano (los módulos `dream` y `sleeptime`) ejecuta periódicamente una limpieza en tres etapas: 1. Agrupamiento de duplicados (SHMR): una matriz de similitud coseno sobre embeddings de 1024 dimensiones (`Qwen3-Embedding-0.6B` en LanceDB) fusiona los registros con similitud `>= 0,70`. 2. Desgaste selectivo según distribución de Weibull: las observaciones pasajeras (`observation`) se desvanecen a las 336 horas (~2 semanas), los hechos de trabajo a las 2.160 horas, mientras que las

ZK
Zhemal Khamidun — CPO de Alpina Digital, product manager de AlpinaGPT. Crea productos de IA desde 2019. Hamidun News es un proyecto personal que creció hasta ser un agregador en producción con más de 6.000 lectores.
✦ Autonomous AI Execution · Nomad Agent

Delegate Deep Research, Coding & Multi-Step Workflows to Nomad Agent

Deploy autonomous task execution with Nomad Agent (agent.nomadnet.ai) — automated web research, data synthesis, code generation, and MCP tool orchestration.

Launch Nomad Agent →Nomad AI Workspace
¿Qué te parece?
Cargando comentarios…