Site Logo
Published on

# Acervo v0.1: construí mi propia memoria para IA porque la solución estándar no funciona

Authors

Este es el primer post de una serie donde documento el desarrollo de Acervo — una librería open source que ataca el problema de memoria en agentes de IA. Lo escribo mientras desarrollo, versión a versión, con errores incluidos.


Tu IA olvida todo. Siempre.

Si usás herramientas de IA en tu trabajo diario — Claude, ChatGPT, Cursor, o cualquier agente — ya lo viviste.

Lunes: "Trabajo en un estudio de software, tenemos tres proyectos web, uno con React, otro con Next.js, el tercero con Angular..."

Martes: "Trabajo en un estudio de software, tenemos tres proyectos web..."

Cada sesión empieza desde cero. Y dentro de la misma sesión, el problema es peor de lo que parece: el LLM recibe toda la conversación anterior en cada turno. Turno 1: 200 tokens. Turno 50: 9,000 tokens. Turno 100: se choca contra el límite de contexto y empieza a perder información — irónicamente, lo primero que le dijiste.

Es como si tu compañero de trabajo leyera todas las reuniones del último mes cada vez que le hacés una pregunta. Y cuando hay demasiadas reuniones, empieza a olvidar las primeras.

Gráfico de tokens creciendo por turno

El problema es peor de lo que parece

La industria intentó mejorar esto, pero con más capas de lo mismo. Ahora podés agregar archivos como CLAUDE.md o Custom Rules en Copilot para darle contexto persistente al modelo. Podés escribir Skills.md para agregarle conocimiento resumido sobre tu stack. AGENTS.md para darle personalidad o especificar cómo querés que actúe. Todo esto ayuda — pero son más tokens. Más contexto estático que se envía en cada request.

Y eso es solo el principio. Los tool definitions agregan esquemas JSON. Los agentes coordinadores inyectan sus propias reglas. Los MCP servers agregan más instrucciones. Cada capa nueva quema tokens del contexto que podrían usarse para la conversación real. Los modelos ahora soportan 128K, 200K, hasta 1M tokens — pero tener más espacio no resuelve el problema de fondo. Estás metiendo todo en una bolsa cada vez más grande en vez de organizar lo que necesitás. Con sesiones largas, el modelo pierde información igual.

Y hay un segundo problema que nadie habla: las sesiones. Cada conversación es independiente. Si hoy hablás 2 horas con tu agente sobre un bug complejo, mañana eso no existe. Necesitaríamos que hablar con un agente sea como hablar con un compañero de trabajo — sin importar la sesión, sin importar cuándo fue la última conversación, el contexto relevante siempre está disponible.


La solución estándar: RAG

La respuesta de la industria se llama RAG (Retrieval-Augmented Generation). La idea es intuitiva: guardás las conversaciones como documentos, las convertís en vectores, y cuando llega una pregunta nueva, buscás los fragmentos más parecidos y los metés en el contexto.

Funciona. Hasta cierto punto. Pero tiene tres problemas de fondo:

RAG recupera texto, no conocimiento. Si en la sesión 3 dijiste "soy hincha de River" y en la sesión 47 preguntás sobre el técnico de River, el sistema tiene que encontrar ese fragmento específico entre miles de chunks. A veces lo encuentra. A veces no. No hay un nodo "River" con todo acumulado — hay pedazos de texto desperdigados.

RAG manda ruido. Un retrieval típico trae 5 chunks de ~500 tokens cada uno = 2,500 tokens. La mayoría es contexto irrelevante alrededor de la frase que realmente importaba. Para responder "¿qué framework usamos?", el LLM recibe 2,500 tokens de texto cuando la respuesta cabe en 5.

RAG crece pero no mejora. Cada conversación agrega más chunks. Más chunks = más ruido en los resultados = más tokens gastados sin agregar valor.

Párrafo de texto vs nodos del grafo

El origen de la idea

Uso agentes de programación como Claude Code desde hace más de un año. Siempre sentí que le falta algo: un "agente" coordinador que gestione todo lo que hay que hacer, que pueda trabajar cross múltiples proyectos, que mantenga el hilo entre sesiones.

Claude Code mejoró con agentes en paralelo y equipos de sub-agentes, pero el thread principal sigue padeciendo el mismo problema de contexto. Yo quería un agente que pudiera usar para tareas diversas sin perder el contexto cada vez que cambio de tema o cierro una sesión.

Por un momento pensé que con OpenClaw iba a poder hacerlo — y si bien está bueno, el problema era más de fondo aún. El core eran los problemas de contexto de los modelos y las sesiones. No importaba qué orquestador usara, todos chocaban contra la misma pared: el historial crece, el contexto se llena, la información se pierde.

Y fue cuando se me ocurrió: ¿por qué en lugar de usar siempre el chat completo como contexto, no le "traemos" de a poco la información necesaria, a demanda?

Pensando en esto, me vino una referencia de cómo pensamos los humanos. Similar a cómo archivábamos información antes de las computadoras: archiveros con índices, carpetas organizadas por tema. Cuando necesitabas algo, no leías todo el archivo — buscabas en el índice, abrías la carpeta correcta, y traías solo lo necesario. Si necesitabas más, investigabas más profundo. Pero siempre ibas "pidiendo" información de a poco, a demanda.

De ahí salió el nombre: acervo. En bibliotecología, un acervo es la colección completa de una biblioteca — cada libro, documento y registro, organizado para que cualquier cosa se pueda encontrar cuando se necesite.


La idea central: dejá de enviar el chat completo

La idea no era hacer otro RAG. Era algo conceptualmente más simple.

¿Y si en vez de enviar toda la conversación anterior al modelo, le traemos solo el conocimiento que necesita para este turno?

No el historial completo. No chunks de texto. Entidades con sus atributos y relaciones — lo que el modelo realmente necesita saber, comprimido en una estructura organizada.

El cambio fundamental no es "usar menos tokens" — es dejar de usar el chat como fuente de contexto. En el approach tradicional, el chat ES el contexto: todo lo que el modelo sabe viene de releer los mensajes anteriores. Si se pierden (por límite de contexto o por nueva sesión), se pierde el conocimiento.

Con Acervo, el conocimiento vive en el grafo. El chat es transitorio — solo un medio por el cual la información llega. Una vez extraída y persistida en el grafo, el chat original puede desaparecer sin que se pierda nada.

Lo que esto cambia en la práctica

Pensalo así. En una sesión de trabajo típica con un agente de código, tu contexto hoy se compone de:

System prompt + CLAUDE.md / rules          ~2,000 tokens
Skills / AGENTS.md / personalidad          ~1,500 tokens
Tool definitions (MCP, function calling)   ~3,000 tokens
Historial de conversación (turno 50)       ~9,000 tokens
                                          ─────────────
Total:                                    ~15,500 tokens

El historial crece con cada turno. En el turno 100, son 20,000+ tokens solo de chat. Y cuando cambiás de sesión, ese historial desaparece y arrancás de nuevo.

Con Acervo, el historial de chat se reemplaza por nodos del grafo:

System prompt + rules                      ~2,000 tokens
Skills / personalidad                      ~1,500 tokens
Tool definitions                           ~3,000 tokens
Contexto del grafo (nodos relevantes)        ~400 tokens  ← constante
Últimos 2 mensajes                           ~200 tokens
                                          ─────────────
Total:                                     ~7,100 tokens

La diferencia clave: esos ~400 tokens de grafo no crecen. Turno 1, turno 50, turno 500 — siempre ~400 tokens porque el grafo solo trae los nodos relevantes al tema actual, no todo lo que se dijo.

Y cuando cerrás la sesión y abrís una nueva al día siguiente, no arrancás de cero. El grafo sigue ahí. El contexto se reconstruye en el primer turno, con los mismos ~400 tokens. La sesión deja de ser un límite.

Incluso en un escenario extremo — muchos skills, muchos MCPs, chunks de documentos — el total se mantiene lejos del límite de contexto. Y si mañana abrís una nueva sesión, vuelve a bajar al baseline. Podés seguir hablando como si nunca hubieras cerrado la conversación.

El grafo como capa de compresión semántica entre las conversaciones y el LLM. Ese fue el concepto fundacional de Acervo.


La arquitectura inicial

Desde el principio pensé en Acervo como una librería independiente — algo que se pudiera invocar como API, como MCP server, o integrarse de cualquier forma en cualquier sistema de agentes. La idea era que fuera agnóstica del framework: si usás LangChain, CrewAI, o tu propio sistema, Acervo se conecta como una pieza más.

Flujo de retrieval

El grafo de conocimiento

El corazón del sistema es un grafo persistido en disco. Cada nodo representa una entidad del mundo real — una persona, un lugar, una organización, una tecnología, un proyecto. Cada arista representa una relación entre dos entidades.

Si en una conversación digo "Soy hincha de River y vivo en Cipolletti", el extractor crea:

Nodos:
  River Plate  → organization
  Cipolletti   → place

Hechos:
  River Plate: "El usuario es hincha de River" (fuente: usuario)
  Cipolletti: "El usuario vive en Cipolletti" (fuente: usuario)

Un principio desde el día uno: un nodo nunca se duplica, se enriquece. Si "River Plate" aparece en 30 conversaciones durante 3 meses, hay un solo nodo. Cada conversación agrega hechos al nodo existente. El grafo es un repositorio de conocimiento acumulado, no un historial de conversaciones.

Dos analogías para entender las capas

La analogía del sistema operativo: un OS no le da a cada programa acceso a toda la memoria. Le da la que necesita, cuando la necesita, y la libera cuando ya no se usa. Acervo hace lo mismo: el LLM (programa) no recibe todo el historial (memoria). Recibe un subset pequeño y relevante (RAM). El grafo (disco) guarda todo lo demás. Cuando el usuario menciona algo viejo (page fault), el grafo lo trae de vuelta.

RAM/disco = context/grafo

La analogía humana — que para mí es la más intuitiva: las capas hot/warm/cold son una metáfora de cómo pensamos cuando hablamos. Cuando estás en una conversación, mantenés el foco en una o pocas cosas a la vez — eso es la capa hot. Las cosas que mencionaste hace un rato están "ahí" pero no en primer plano — warm. Y todo lo demás que sabés pero no estás pensando — cold. No estás leyendo toda tu vida cada vez que contestás una pregunta. Traés a primer plano solo lo relevante.

El pipeline por turno

Diagrama del pipeline completo
Mensaje del usuario
Topic detector (¿cambió el tema?)
Activación de nodos relevantes del grafo
Query planner (¿qué información necesita el LLM?)
Context build (arma hot + warm layers)
LLM responde al usuario (streaming)
Extractor (saca conocimiento de la conversación)
Persiste al grafo → disponible para el próximo turno

El detector de temas en cascada

Antes de cada turno, el sistema detecta si cambiaste de tema. Esto determina qué nodos se activan.

Nivel 1 — Keywords (gratis, instantáneo): regex. "Cambiando de tema", "on another note". Si matchea → resuelto.

Nivel 2 — Embeddings (rápido, sin LLM): similitud coseno entre el mensaje y el tema actual. Alta → mismo tema. Baja → tema nuevo. Media → ambiguo, escala al nivel 3.

Nivel 3 — LLM (solo si L2 no puede): le pregunta al modelo "¿mismo tema, subtema, o tema nuevo?".

La mayoría de los turnos se resuelven en L1 o L2, sin llamadas LLM.

El extractor conservador

El extractor tiene una filosofía estricta: solo guarda lo que se dijo explícitamente. Nunca inferencias, nunca conocimiento general. ¿Por qué? Porque las alucinaciones en el grafo se propagan para siempre. Si un hecho incorrecto entra al grafo, el LLM lo va a repetir con confianza en cada conversación futura. Un nodo vacío es mejor que un nodo con datos incorrectos.

Setup local

Todo corre localmente. Dos modelos en LM Studio:

  • Qwen 3.5 9B — conversación. Inteligente, fluido.
  • Qwen 2.5 3B — extracción y clasificación. Rápido, JSON limpio, sin modo "thinking".

¿Por qué dos modelos? El 9B tenía un modo de razonamiento que generaba bloques <think> de 4,000+ tokens de pensamiento interno. Útil para conversar, un desastre para extracción: consumía contexto y a veces truncaba el JSON de salida.

Sin APIs de nube. Sin costo por token. Sin datos saliendo de tu máquina.


Qué funcionó

Al final de v0.1, el sistema capturaba conocimiento real y lo usaba en conversaciones futuras. Si el lunes hablabas de tu proyecto y el miércoles preguntabas "¿qué framework usamos?", el LLM ya lo sabía.

El contexto se mantenía constante independientemente de cuántas conversaciones hubieran pasado. La idea central de compresión semántica funcionaba.


Qué rompió

Demasiadas llamadas LLM. 3-4 por turno, varias sincrónicas. El planner corría antes de que el modelo empezara a responder. El usuario esperaba.

El detector de temas era hiperactivo. Una conversación sobre Harry Potter saltaba entre "Literatura" → "Cultura popular" → "Fantasía" en turnos consecutivos. Las preguntas de seguimiento fallaban porque no mencionaban a Harry Potter explícitamente.

El grafo era plano. Nodos sueltos sin estructura jerárquica. Batman y Gotham existían pero nada los conectaba al universo DC.

El 9B pensaba demasiado. Bloques <think> de 4,000+ tokens antes de producir respuesta. Por eso necesitaba el 3B separado — pero mantener dos modelos era frágil.

Ciclo de obsolescencia

Lo que aprendimos para v0.2

v0.1 validó la hipótesis: un grafo de conocimiento puede reemplazar el historial crudo y mantener el contexto constante. La compresión semántica funciona.

Pero aún debíamos comprobar que funciona con conversaciones largas — los chats cortos anduvieron, pero no lo probamos con sesiones de 100+ turnos con múltiples cambios de tema.

También nos dimos cuenta de que el query planner estaba de más, que necesitábamos otra forma de detectar topics, y que la extracción tenía que mejorar drásticamente. La conclusión fue que necesitábamos un modelo fine-tuneado para esto.

Y lo más importante: Acervo tenía que ser instalable como una herramienta — como Git, como Claude Code — en cualquier proyecto, para que pueda incluirse dentro de cualquier sistema de agentes. No una app. Una pieza de infraestructura.

La pregunta que me llevó a v0.2 fue: ¿y si un solo modelo pudiera hacer todo? Eso implicaba fine-tuning. Nunca lo había hecho. Tenía que aprender.


En el próximo post: fine-tunear un Qwen 3.5 9B para que haga chat Y extracción con un solo prompt, simplificar el pipeline a la mitad, y el concepto de Acervo stateless.


Acervo es open source bajo Apache 2.0. Repo: github.com/sandyeveliz/acervo · Modelo: huggingface.co/SandyVeliz/acervo-extractor-qwen3.5-9b