La semana en que le dimos a nuestro agente una memoria a largo plazo de verdad, la recuperación empeoró.
No de inmediato. El primer día fue magia: el agente recordó el miércoles un detalle de un despliegue de la sesión del lunes, sin tener que volver a explicarlo. Para el final de la segunda semana devolvía cinco notas casi idénticas para "cómo manejamos la limitación de tasa", cada una ligeramente equivocada, ninguna era la que yo realmente quería. El agente había estado llamando diligentemente a memorize con cada observación, cada callejón sin salida, cada conjetura a medio formar. Se había construido un cajón de sastre y ahora hurgaba en él en cada consulta.
Esta es la parte de "dale memoria a tu agente" sobre la que nadie te advierte. Una memoria que solo crece no es una memoria, es un registro. Y un registro que buscas por significado se vuelve más ruidoso a medida que crece, porque cada casi-duplicado que almacenaste compite por la misma porción del ranking. Más memorias, peor recuperación. Justo lo contrario de lo que contrataste.
Octobrain es nuestra solución de código abierto para esto: un servidor de memoria MCP escrito en Rust que le da a cualquier agente una memoria a largo plazo persistente y buscable semánticamente. Lo presentamos en abril, y en la versión 0.7.0 la memoria aprendió a olvidar. Este artículo es la guía práctica que me habría gustado tener: cómo configurar una memoria LLM persistente que se mantenga nítida, usando dos ideas —delimítala por proyecto y deja que se consolide— más la cuestión práctica de qué almacenar y qué dejar en paz.
El problema real: la recuperación, no el almacenamiento
Almacenar cosas es fácil. Cualquier almacén clave-valor lo hace. La parte difícil de la memoria a largo plazo de un agente es la misma parte difícil que la de la memoria humana: traer de vuelta lo correcto en el momento correcto, de un montón que nunca deja de crecer.
Octobrain recupera por significado, no por palabras clave. Por debajo incrusta cada memoria en un vector con un modelo local (el predeterminado es fastembed:nomic-ai/nomic-embed-text-v1.5, de 768 dimensiones, corre en CPU, sin clave de API) y los almacena en una base de datos vectorial LanceDB en disco. Una consulta remember se incrusta de la misma forma y vuelven los vectores más cercanos. Para evitar que la búsqueda puramente semántica pase por alto cadenas exactas, la búsqueda es híbrida: texto completo BM25 y similitud vectorial fusionados con Reciprocal Rank Fusion. Encima hay un reranking opcional con cross-encoder.
Esa maquinaria es buena. En el benchmark SciFact de BEIR, la recuperación híbrida de Octobrain obtiene 0,742 de nDCG@10 frente a 0,665 del BM25 clásico y 0,713 del embedding puro: la fusión añade alrededor de +2 de nDCG@10 sobre el embedder solo. La calidad de recuperación nunca fue el cuello de botella.
El cuello de botella era lo que le dábamos. Cinco notas solapadas sobre el mismo error no hacen que ninguna suba en el ranking; reparten la señal. Una buena recuperación sobre un corpus ruidoso sigue siendo una respuesta ruidosa. Así que el arreglo no es un mejor embedder. Es curar el corpus, de forma automática, porque ningún humano va a podar a mano la memoria de un agente.
Idea uno: delimita la memoria por proyecto
La primera fuente de ruido es la contaminación cruzada. Sin aislamiento, las memorias de todos los proyectos comparten un solo grupo, y una consulta sobre el "flujo de autenticación" en tu app web arrastra las notas de autenticación de tres servicios sin relación.
Octobrain aísla las memorias por alcance (scope). Un alcance es una cadena y, por defecto, se deriva de tu remoto de git. Abre el repositorio de este mismo blog y el alcance es github.com/muvon/muvon-web. La derivación es exactamente la que imaginarías:
git remote get-url origin → host/org/repo
Así que [email protected]:muvon/octobrain.git se convierte en github.com/muvon/octobrain. ¿No hay remoto de git? Recurre a un alcance basado en ruta, local/<parent>/<dir>, para que hasta un directorio de pruebas tenga aislamiento. Y hay un alcance reservado —la cadena vacía ""— para las memorias globales que deben ser visibles en todas partes.
Esa división global/proyecto es todo el modelo mental, y se corresponde con una distinción real:
| Alcance | Almacena aquí | Ejemplos |
|---|---|---|
Proyecto (github.com/org/repo) |
Hechos ciertos dentro de este repo | "Las pruebas corren con pnpm -r test:unit", "la API de staging necesita la cabecera X-Acme-Token", "aquí elegimos limitación de tasa por token-bucket" |
Global ("") |
Hechos ciertos sobre ti, en todas partes | "Prefiere tabuladores", "siempre quiere mensajes de commit convencionales", "usa fish, no bash" |
La recompensa: cuando tu agente corre en el repo del blog, una llamada remember solo se compara con las memorias del repo del blog más tus preferencias globales. Las notas de autenticación de aquel otro servicio simplemente no están en el conjunto de candidatos. Candidatos más limpios, recuperación más nítida, antes incluso de que se ejecute cualquier consolidación.
Cómo se fija el alcance
Si manejas Octobrain a través de un cliente MCP que realiza el handshake de sesión, el alcance se bloquea automáticamente desde el directorio de trabajo: el agente nunca tiene que pensar en ello, y el parámetro scope se elimina por completo del esquema de la herramienta para que no pueda equivocarse. El servidor incluso anuncia los alcances disponibles en sus instrucciones MCP:
Available project scopes (pass as the 'scope' parameter):
github.com/muvon/muvon-web
Cuando la sesión no está bloqueada, las herramientas exponen scope directamente, y memorize acepta una bandera global: true para colocar deliberadamente un hecho en el alcance compartido sin importar dónde estés. Esa bandera solo aparece cuando hay un alcance de proyecto bloqueado que sobrescribir; de lo contrario, simplemente pasas scope: "" tú mismo. La orientación incrustada en la descripción de la herramienta es tajante: usa global para hechos que cruzan proyectos como preferencias y convenciones; no lo uses para conocimiento específico del proyecto. La fuga de alcance es el ruido que intentas evitar.
La superficie de herramientas que realmente usas
Octobrain expone deliberadamente una superficie MCP pequeña. Cada herramienta que expones le cuesta contexto al agente en cada turno —lee el esquema antes de hacer cualquier cosa— así que la versión 0.7.0 estrechó la superficie y la 0.8.0 la mantuvo ligera. Cuatro herramientas, cuatro tareas:
memorize almacena una memoria (con relaciones en línea opcionales)
remember recuperación semántica (devuelve vecinos del grafo a 1 salto)
forget elimina (requiere confirm=true)
knowledge indexa/busca/lee/coincide documentos y URLs externos
memorize
El verbo de almacenamiento. Obligatorios: un title (5–200 caracteres) y content (10–10000 caracteres). Todo lo demás es opcional pero vale la pena entenderlo:
memory_type— una categoría comoarchitecture,bug_fix,decision,user_preference,goal. Se usa para filtrar en la recuperación.importance— 0.0–1.0. La descripción de la herramienta da anclas reales: hechos afirmados por el usuario 0.8–1.0, decisiones 0.7–0.9, correcciones de errores 0.6–0.8, inferencias de la IA 0.3–0.6.source— un nivel de confianza, ya seauser_confirmed(el usuario lo dijo explícitamente) oagent_inferred(lo concluyó el agente). Esto no es cosmético: los hechos confirmados suben en la recuperación, y los resultados vuelven etiquetados[CONFIRMED]/[INFERRED]para que el agente sepa cuánto fiarse de cada uno.tags,related_files— para filtrar y para atar una memoria al código que describe.related_to— el interesante. Un arreglo de enlaces tipados a memorias existentes, creados en la misma llamada. Así construyes el grafo sin un segundo viaje de ida y vuelta.
Un almacenamiento concreto, tal como lo emitiría un agente:
{
"title": "Rate limiting uses a token bucket",
"content": "We rate-limit the public API with a token bucket (100 req/min/key), returning 429 + Retry-After on exhaustion. Chosen over fixed-window to avoid burst-at-boundary.",
"memory_type": "decision",
"importance": 0.85,
"source": "user_confirmed",
"tags": ["api", "rate-limiting"],
"related_files": ["src/middleware/ratelimit.rs"]
}
remember
El verbo de recuperación, y el que deberías llamar primero: antes de memorizar (para evitar guardar un duplicado) y al inicio de una tarea (para cargar contexto). Toma un query que es o bien una sola cadena o, preferiblemente, un arreglo de 2–5 términos relacionados para una cobertura semántica más amplia. Devuelve las mejores coincidencias más sus vecinos del grafo inmediatos automáticamente, de modo que un acierto en una memoria saca a la luz las que están enlazadas con ella.
También acepta filtros que se hicieron reales en versiones recientes: created_after / created_before (ISO-8601) para preguntas temporales como "qué decidimos la semana pasada", min_relevance para un umbral de calidad, y memory_types / tags para acotar. La expansión de consulta que rescata las vagas —la retroalimentación de pseudo-relevancia HyDE-lite de Octobrain— corre automáticamente y añade un citado +10–30% de recuperación en consultas de cola larga sin LLM y sin configuración.
forget
Eliminación permanente. Requiere confirm: true: no hay olvido accidental. Elimina por memory_id para una sola, o por query + filtros para una barrida masiva. La descripción de la herramienta lleva una regla que conviene interiorizar: no olvides memorias solo porque son viejas —de eso se encarga el decaimiento (más abajo)—. Solo usa forget cuando algo es genuinamente erróneo o ha sido reemplazado.
knowledge
La otra mitad de la memoria: los documentos externos. memorize es para los insights destilados que produce el agente; knowledge es para el material fuente que el agente lee. Cinco comandos, despachados mediante un campo command:
# Indexa y busca una sola fuente (se indexa al vuelo)
knowledge command=search query="how to handle async tasks" source=https://docs.rs/tokio/
# Lee el texto completo de un archivo o URL (último recurso cuando la búsqueda es escasa)
knowledge command=read source=/path/to/design-doc.md
# Hace grep en el contenido indexado por regex
knowledge command=match pattern="spawn_blocking|block_in_place"
# Guarda y elimina texto crudo bajo una clave (con alcance de sesión)
knowledge command=store key=meeting-notes content="..."
knowledge command=delete key=meeting-notes
Una regla estricta que el esquema impone y que el servidor repite en sus instrucciones: source es siempre un único archivo o URL, nunca un directorio. Si pasas un directorio, se rechaza. Los tipos soportados son .html, .txt, .md, .pdf, .docx, además de URLs http/https.
Idea dos: deja que la memoria se consolide sola
Delimitar el alcance corta el ruido entre proyectos. No hace nada por el problema de las cinco-notas-casi-duplicadas-en-un-proyecto. Para eso está la consolidación, y la decisión clave de diseño en 0.7.0 fue hacerla autónoma, porque en lo único que no puedes confiar es en que un agente (o un humano) se acuerde de ordenar.
Consolidación durante el sueño
Al arrancar, si ha pasado un día desde la última pasada, Octobrain ejecuta una pasada de "sueño": encuentra grupos de memorias similares, recientes y aún en estado Working, y pliega cada grupo en un único insight consolidado. Los originales no se eliminan: pasan al estado Consolidated con importancia atenuada, enlazados bajo el nuevo padre como procedencia. Las cinco notas difusas sobre limitación de tasa se vuelven una memoria limpia y de mayor confianza, y el rastro de vuelta a las notas crudas sobrevive para auditoría.
Está controlada por un archivo marcador, así que es perezosa, no un cron ni un planificador en segundo plano, y una pasada lenta o fallida nunca bloquea nada. Los valores por defecto son conservadores:
[memory]
sleep_consolidation_enabled = true # opt-out, not opt-in
sleep_consolidation_interval_hours = 24 # at most once a day
sleep_consolidation_threshold = 0.85 # cosine similarity to cluster
sleep_consolidation_min_cluster_size = 3 # need 3+ to bother
sleep_consolidation_max_age_days = 7 # only recent Working memories
Puedes forzar una pasada desde la CLI para pruebas —octobrain memory sleep-consolidate --threshold 0.85 --min-size 3— pero la idea es que nunca tengas que hacerlo.
Consolidación anclada en objetivos
La consolidación durante el sueño agrupa por similitud. La versión más afilada agrupa por intención. Octobrain tiene un tipo de memoria goal y un ciclo de vida real: Working → Consolidated → Archived. El flujo, todo expresado mediante los enlaces related_to de memorize, es:
memorizeuna memoria de tipogoalque capture lo que intentas hacer.- Para cada hallazgo en el camino,
memorizeconrelated_to: [{ target_id: <goal_id>, relationship_type: "achieves" }]. - Cuando la tarea se cierra,
memorizela nota de lección aprendida conrelationship_type: "closes".
Ese enlace closes dispara la consolidación automáticamente, sin una llamada de herramienta aparte. La nota de cierre se convierte en el padre consolidado (su importancia elevada a max(sources) * 1.1), y cada fuente achieves pasa al estado Consolidated con importancia atenuada, todavía consultable para el rastro de auditoría. Cinco hallazgos dispersos colapsan en una memoria que significa algo, anclada a la intención que la produjo.
Esto también explica por qué la herramienta consolidate independiente ya no está en la superficie MCP: cerrar un objetivo ahora es solo un memorize con un enlace closes, y la consolidación durante el sueño corre sola. Menos herramientas, el mismo poder. (El comando de CLI octobrain memory consolidate <goal-id> sigue ahí para uso manual y de administración.)
Decaimiento por vida media: olvidar como señal de ranking
El tercer mecanismo corre continuamente. La importancia de cada memoria decae según un calendario de vida media de Ebbinghaus, y cada vez que accedes a una memoria se refuerza. Las memorias recordadas con frecuencia se mantienen fuertes; las que nada toca se desvanecen suavemente en el ranking.
[memory]
decay_enabled = true
decay_half_life_days = 90 # higher = memories stay relevant longer
access_boost_factor = 1.2 # reinforcement multiplier per access
min_importance_threshold = 0.05 # floor — decay never deletes
Dos salvaguardas lo hacen seguro. Hay un piso de importancia: una memoria rancia se hunde hacia el fondo pero nunca desaparece, porque el decaimiento es una señal de re-ranking, no un borrado. Y los conteos de acceso persisten entre reinicios, así que el sentido de la memoria sobre "qué está caliente" no se reinicia cada sesión. El efecto neto es que las memorias que de verdad usas suben y las que almacenaste una vez y nunca necesitaste se hunden, con cero curación de tu parte. Por eso justamente la herramienta forget le dice al agente que no borre cosas por ser viejas: el decaimiento ya se encarga de eso, con gracia, sin perder el historial.
Qué almacenar y qué dejar en paz
El mecanismo solo te lleva hasta cierto punto. La disciplina que arregló nuestra recuperación fue decidir qué merece una memoria. El instinto del agente es almacenar todo; la política correcta se acerca más a lo contrario.
Almacena:
- Decisiones y su justificación. "Elegimos token-bucket sobre ventana fija por el burst-at-boundary." El porqué es la parte cara de reconstruir.
- Hechos difíciles de conseguir sobre este repo. El comando de prueba no obvio, la cabecera de staging, la migración que tiene que correr primero. Cosas que el agente, si no, redescubre (o re-adivina) cada sesión.
- Preferencias del usuario —en el alcance global—. Dichas una vez, ciertas en todas partes.
- Correcciones de errores con causa raíz. No "arreglé el webhook" sino "los reintentos del webhook necesitaban claves de idempotencia; sin ellas, los reintentos cobraban doble".
No almacenes:
- Estado transitorio. La rama actual, lo que hay en el árbol de trabajo, la prueba que falla hoy. Está rancio antes de la siguiente sesión y es puro ruido para siempre después.
- Cualquier cosa trivialmente re-derivable. Si un comando de shell de una línea lo regenera, ese comando es la memoria, no su salida.
- Conjeturas sin verificar, como si fueran hechos. Si el agente debe registrar una corazonada, para eso están
source: agent_inferredy una importancia baja. Etiqueta la incertidumbre para que la recuperación la sitúe por debajo del conocimiento confirmado. - El mismo insight cinco veces. Llama a
rememberantes dememorize. Si vuelve una casi-coincidencia, actualízala o reemplázala en vez de apilar otro casi-duplicado.
Ese último punto tiene soporte de herramienta. Cuando un hecho nuevo reemplaza a uno viejo —cambió un valor, se revirtió una decisión— haz memorize del hecho nuevo con related_to: [{ target_id: <old_id>, relationship_type: "supersedes" }]. La recuperación entonces sitúa el hecho actual por encima del desactualizado, que sigue siendo consultable para el historial. Corriges el registro sin perderlo.
Configurarlo
Instálalo y apunta un cliente MCP hacia él.
# crates.io
cargo install octobrain
# o en macOS vía Homebrew
brew install muvon/tap/octobrain
Prueba el ciclo completo desde la CLI primero:
octobrain memory memorize --title "API Design Pattern" \
--content "Use REST for CRUD, GraphQL for complex queries" \
--memory-type architecture --tags "api,design"
octobrain memory remember "how should I design APIs"
Luego conéctalo a un cliente MCP. Para Claude Desktop, añade a claude_desktop_config.json:
{
"mcpServers": {
"octobrain": {
"command": "/path/to/octobrain",
"args": ["mcp"]
}
}
}
Ejecuta octobrain mcp para stdio, o octobrain mcp --bind 0.0.0.0:12345 para HTTP. El almacenamiento vive en ~/.local/share/octobrain/ (macOS/Linux) o %APPDATA%\octobrain\ (Windows), y las memorias del proyecto se aíslan por alcance como se describió arriba. Todo tiene valores por defecto sensatos: la consolidación durante el sueño, el decaimiento, la búsqueda híbrida y HyDE están todos activados de fábrica. El conjunto completo de perillas está documentado en config-templates/default.toml en el repositorio.
La única decisión de configuración que vale la pena tomar con intención es decay_half_life_days. El valor por defecto de 90 días sirve para una base de código en evolución constante. Súbelo para un proyecto estable donde las decisiones siguen siendo válidas mucho tiempo; bájalo para un prototipo de movimiento rápido donde la verdad del mes pasado es la mentira de este mes.
Preguntas frecuentes
¿Olvidar pierde información?
No. El decaimiento es una señal de ranking con un piso: las memorias de bajo acceso se hunden pero nunca se borran. La consolidación mueve las fuentes al estado Consolidated, atenuadas pero todavía consultables, enlazadas bajo su padre como procedencia. Lo único que de verdad borra es un forget explícito con confirm: true. Siempre puedes rastrear hacia atrás.
¿En qué se diferencia memorize de knowledge?
memorize almacena los insights que el agente produce —decisiones, correcciones, conclusiones—. knowledge indexa los documentos que el agente lee —docs, archivos de diseño, URLs— para poder buscarlos y citarlos. Sabiduría destilada frente a material fuente. Ciclos de vida distintos, herramientas distintas.
¿Tengo que gestionar los alcances a mano?
Normalmente no. Con un cliente MCP que hace el handshake de sesión, el alcance se bloquea desde el remoto de git de tu directorio de trabajo y el parámetro scope se elimina del esquema. Solo tocas el alcance explícitamente para almacenar una preferencia global (global: true, o scope: "").
¿Por qué solo cuatro herramientas MCP? Otros servidores de memoria exponen una docena.
Porque cada herramienta que expones es esquema que el modelo relee en cada turno, y la precisión en la selección de herramientas se degrada a medida que crece el catálogo. La consolidación pasó a ser autónoma y relate/consolidate se plegaron en los enlaces de relación de memorize, así que las tareas que solían necesitar su propia herramienta ya no la necesitan. La CLI todavía tiene el conjunto completo para trabajo de administración.
¿Necesito una clave de API o una GPU?
No. El modelo de embedding por defecto corre localmente en CPU vía fastembed: sin clave, sin red. Los embedders en la nube (Voyage, OpenAI, Google, Jina) están soportados si quieres mayor calidad, pero son opcionales.
Lo que arregló nuestra recuperación no fue un mejor algoritmo de búsqueda —la recuperación de Octobrain ya le ganaba a BM25 en los benchmarks mientras nos ahogábamos—. Fue admitir que una memoria tiene que olvidar para seguir siendo útil. Delimítala para que las cosas correctas sean siquiera candidatas; deja que se consolide para que los duplicados colapsen; deja que decaiga para que el peso muerto se hunda. Entonces el agente deja de hurgar en un cajón de sastre y empieza a responder desde una memoria.
Almacena las decisiones y los hechos difíciles de conseguir. Sáltate el ruido transitorio. Llama a remember antes de memorize. Deja que el resto corra solo.
— Don
Octobrain es de código abierto bajo Apache-2.0. La maquinaria de consolidación y decaimiento descrita aquí llegó en 0.7.0; el sistema de alcances y la superficie de herramientas más ligera continuaron en 0.8.0. Si algún detalle de configuración te traba o quieres que se cambie un valor por defecto de alcance o decaimiento, abre un issue — los leemos.



