al-buddy-memory

Memoria gobernada y portátil para agentes de IA: cada recuperación devuelve quién afirmó un hecho, desde cuándo ha sido verdadero y qué lo reemplazó. Los hechos nunca se sobrescriben. SQLite local, sin clave de API.

Documentación

al-buddy-memory

Al Buddy — es Al, un nombre, que suena como "pal". No es I.A.

Una memoria que es tuya.

Memoria portátil, gobernada e independiente del modelo para agentes de IA.

Un hecho se invalida, nunca se sobrescribe; en un identificador gobernado, el borrado pasa por políticas y se audita. El texto sin procesar es la fuente de verdad y no se puede editar. Los embeddings son una caché desechable etiquetada por modelo. Cada hecho y cada enlace se exportan a un formato documentado. La memoria sobrevive a cualquier modelo, runtime o empresa que la haya producido.


Por qué existe esto

Cada producto de memoria para agentes en el mercado responde bien a una pregunta: ¿qué recuerda el agente? Ninguno responde a las cuatro preguntas que deciden si puedes confiar y conservar esa memoria:

PreguntaEsta libreríaLettaMem0Zep
¿De dónde viene este hecho y quién lo afirmó?Procedencia en cada nodo y arista (UserInput / AIInferred / GuardianAdded / SystemGenerated)Historial git de archivos de memoriaCampo de metadatosEpisodios de grafo
¿Cuándo fue verdad y qué lo reemplazó?Dos ejes consultables: validAt responde qué era verdad en Y; getNodeAsOf / snapshotAsOf responden qué creía el almacén en X a partir de versiones completas antes/después. Se combinan en una sola llamada (snapshotAsOf(X, { validAt: Y })), una lectura que el historial no puede garantizar se marca, y el borrado también elimina el historialHistorial git de archivos, no un modelo de hechosHistorial de cambios por memoria (history(): valor antiguo, valor nuevo, evento, marcas de tiempo) — historial de transacciones, no tiempo válidoGrafo temporal (su verdadera fortaleza): las aristas de Graphiti llevan valid_at / invalid_at junto con created_at / expired_at
¿Puedo llevármela conmigo, sin pérdidas, a otro runtime?Una exportación JSON versionada con un esquema publicado y pruebas de conformidad.af (estado del agente, con forma de framework, memoria de archivo aún no incluida)Exportación en la nubeSolo nube desde 2025
¿Funciona sin proveedor, sin clave, sin servidor?SQLite en disco, embeddings en el dispositivoAutoalojamiento posible; la nube es el productoAutoalojamiento posible (Apache-2.0, almacenes vectoriales locales); necesita un LLM para extracción; la nube es el productoZep es nube; Graphiti se autoaloja (base de datos de grafo + clave de LLM requerida)

Fuentes para las celdas anteriores, cada una verificada contra el código o anuncio propio del proyecto el 2026-09-18: el historial por memoria de Mem0 es una tabla SQLite con old_memory, new_memory, event, created_at, updated_at (mem0/memory/storage.py); Mem0 es Apache-2.0 y funciona contra almacenes vectoriales locales, incluidos Qdrant, Chroma, pgvector y FAISS (documentación de almacenes vectoriales), con un LLM llamado para extraer hechos en la ruta add() predeterminada. Zep dejó de mantener Community Edition el 2025-04-02, en sus propias palabras: "hemos decidido dejar de mantener y publicar Zep Community Edition." Graphiti, el motor bajo Zep, es Apache-2.0 y se autoaloja, y sus requisitos declarados son una base de datos de grafo (Neo4j, FalkorDB, Amazon Neptune o el obsoleto Kuzu) más una clave de LLM — "usa OpenAI por defecto para inferencia de LLM y embeddings."

Los tres locales y sin clave que la gente mencionará, leídos contra su propio código el 2026-09-21. Responden a la cuarta pregunta como esta librería: tu máquina, tus archivos, sin cuenta — y son la comparación honesta, no los productos en la nube.

  • basic-memory (AGPL-3.0; las notas Markdown en disco son la verdad, SQLite es un índice reconstruible). Tiene tiempo válido, y vale la pena decirlo claramente: una observación puede llevar @effective[2026-06-10,2026-07-27) en la propia nota, analizada en un índice de tiempo (models/knowledge.py) y reconstruida desde el archivo, por lo que el archivo sigue siendo la fuente de verdad. Lo que no lleva en una instalación local es quién afirmó un hecho: created_by / last_updated_by existen y contienen un id de usuario en la nube, nulos para uso local y CLI. La portabilidad es del tipo más fuerte y menos especificado: las notas son el formato, así que nada queda atrapado, y no hay un esquema versionado para importarlas a otra cosa. La captura y la búsqueda de texto completo no necesitan clave; la búsqueda semántica es opcional y sí la necesita.
  • El servidor de memoria MCP (la implementación de referencia que la mayoría de agentes conocen primero; un archivo JSONL). Entidades, relaciones y observaciones, y nada más: sin campo para quién lo dijo, sin marcas de tiempo en absoluto, y deleteObservations filtra el valor antiguo fuera de la matriz, por lo que lo que era verdad antes desaparece en lugar de cerrarse. El archivo es su propia exportación. Nada llama a un modelo; nada sale de la máquina.
  • Memori (Apache-2.0; tu propia base de datos SQL — SQLite, Postgres, MySQL, Oracle). Un hecho en memori_entity_fact tiene contenido, un embedding, un recuento y una fecha de última vista; quién lo afirmó solo se puede recuperar recorriendo las claves foráneas hasta un mensaje de conversación, no es una propiedad del hecho, y no hay tiempo válido — date_updated lo reemplaza. "Tus datos permanecen en tu base de datos" es la historia de portabilidad, sin un formato de intercambio publicado. Es local en el sentido que importa para tus datos, pero la captura no es sin clave: la extracción y los embeddings llaman a un LLM, y la ruta del SDK predeterminada también espera una cuenta de Memori.

También vale la pena mencionar: OpenMemory MCP (Mem0, lanzado en mayo de 2025) envió la misma idea de distribución — un almacén de memoria local compartido entre clientes MCP — y Mem0 lo archivó; su README ahora comienza "Este proyecto ha sido archivado." Su esquema es el contraste del que trata esta tabla: models.py da a una memoria content, created_at, updated_at, archived_at, deleted_at y un estado, sin campo para quién afirmó el hecho y sin tiempo válido, y content se reescribe en su lugar al actualizar. Sí mantiene un historial de transiciones de estado y un registro de acceso, y sí tiene una exportación ZIP — así que no es la ausencia de portabilidad lo que separa a los dos, es la procedencia, el tiempo válido y un texto sin procesar inmutable.

Los benchmarks de recuperación (LOCOMO, LongMemEval, DMR) miden lo que un agente recuerda. Ninguno puntúa un sistema de memoria por procedencia, invalidación o portabilidad. Esta librería está construida para ese eje, y el puntuador de conformidad a continuación es un intento de medirlo. La tabla es nuestra lectura del código y la documentación pública de cada proyecto, fechada arriba; si tenemos una celda incorrecta, un PR con un enlace la corrige.

Empieza aquí

Una memoria vacía no le da a un asistente nada sobre lo que apoyarse. docs/STARTER.md siembra la tuya en diez minutos: fija quién es la persona y cómo quiere que la traten (incluido "nunca un adulador"), elige las reglas que el almacén aplica y deja que derive el resto cada noche.

Actualizando desde una versión anterior: CHANGELOG.md marca cualquier cosa que cambie lo que un llamador existente recibe. 0.4.0 tiene cambios disruptivos (contenido inmutable, borrado gobernado, un nuevo listNodes en la interfaz del almacén, las exportaciones MCP movidas a al-buddy-memory/mcp), así que lee esa entrada antes de actualizar.

Qué hay en la caja

  • MemoryStore: una interfaz agnóstica al almacenamiento; SqliteMemoryStore y InMemoryStore se incluyen, con una suite de conformidad contra la que cualquier backend puede ejecutarse.
  • ProjectMemory: un cerebro por proyecto o persona, cada uno en su propio archivo SQLite.
  • HybridRetriever: recuperación léxica + semántica con confianza consciente de la decadencia; embeddings en el dispositivo vía transformers.js (sin clave de API). La recuperación se puede delimitar (tipo de memoria, etiquetas, confianza mínima, niveles de privacidad y retención), y el ámbito se aplica tanto al lado de palabras clave como al vectorial.
  • exportPortable / importPortable: el formato de intercambio sin pérdidas, versionado, con un JSON Schema.
  • PinnedBlocks: un nivel de hechos con límite de tamaño que pertenecen a cada prompt, editable por el propio agente, sobre el almacén gobernado.
  • consolidate: un pase en horas de sueño que lee la memoria sin procesar reciente y escribe hechos derivados nuevos con aristas de procedencia de vuelta a sus fuentes; lo sin procesar nunca se reescribe y nada se resume.
  • verifyDerived: cada conclusión almacena las palabras exactas en las que se apoya (evidence), verificadas al escribirse; esto las re-verifica en cualquier momento y retracta, nunca elimina, una cuya evidencia ya no se sostiene.
  • Modelos mentales: preguntas permanentes con respuestas mantenidas al día en segundo plano, de modo que leer una no cuesta ninguna llamada de modelo (defineMentalModel, refreshMentalModels, getMentalModel). Cada respuesta cita los hechos en los que se apoya, mantiene su historial ("¿qué pensábamos en junio?") y se lee como obsoleta en el momento en que uno de esos hechos deja de ser verdad o se borra.
  • explainFact: por qué se cree un hecho, en una llamada — procedencia y origen, validez y qué lo reemplazó, la evidencia de una conclusión con cada cita verificada y su historial. A través de un identificador gobernado, una fuente que el lector no puede ver se nombra como retenida, nunca mostrada.
  • Las conclusiones van con sus hechos: un hecho que deja de ser verdad retracta lo que se concluyó de él (conservado, marcado); un hecho que se borra se lleva todo lo construido a partir de él (SPEC §8a).
  • listConsolidations / undoConsolidation: revisa lo que concluyó cada pase, con la evidencia de cada hecho, y retira las conclusiones de un pase. Deshacer retracta (validTo) y registra quién retiró cada hecho y por qué; nunca elimina, por lo que el historial aún muestra lo que se creía, cuándo se retiró y la razón.
  • buildSourceProvenance / readSourceProvenance, renderMemoryBlock, exportMemoryMarkdown, ayudantes de decadencia.
  • Integraciones: al-buddy-memory/ai-sdk (herramientas y middleware del SDK de IA de Vercel), al-buddy-memory/langchain (un almacén de memoria a largo plazo de LangGraph + herramientas de LangChain), al-buddy-memory/mastra (un procesador de entrada + herramientas). Los frameworks son dependencias opcionales entre pares; cada escritura está gobernada y registra qué agente la hizo. Guías en docs/integrations.

Node ≥ 20. Una dependencia de runtime (better-sqlite3); transformers.js es opcional.

Trabajo previo: el proyecto Letta publicó la idea de un nivel de memoria fijado y un pase en segundo plano sobre la memoria (bloques de memoria; agentes en horas de sueño). Lo que es diferente aquí: cada hecho derivado debe citar lo sin procesar en lo que se apoya o se rechaza, lo sin procesar nunca se reescribe, y un pase completo se puede revisar y deshacer, con el deshacer y su razón conservados en el registro.

import { SqliteMemoryStore, ProjectMemory, PinnedBlocks, exportPortable } from "al-buddy-memory";

const store = new SqliteMemoryStore("./brain.db");
await store.addNode({ provenance: "UserInput", memoryType: "Lesson", content: { text: "Chris prefers decisions over options." }, /* …governance fields… */ });
const pins = new PinnedBlocks(store);
await pins.pin({ text: "Never present options without a recommendation.", label: "rule" });
const snapshot = await exportPortable(/* … */);   // → docs/portable-format.schema.json

Contrato completo: docs/SPEC.md. Registro de diseño: docs/DECISION-2026-07-07.md.


Python y otros lenguajes

El servidor MCP y el formato portátil son la superficie neutral al lenguaje: un agente Python puede usar el servidor de gobernanza hoy, y cualquier lenguaje puede leer la exportación (es JSON plano con un esquema). Un paquete Python nativo está planificado; abre un issue si lo necesitas antes y di qué usarías primero.

Licencia

Apache-2.0. Ver LICENSE.

La gobernanza se aplica, no se implica

El vocabulario — Público / Privado / Sensible / Sellado, niveles de retención, procedencia en cada hecho y relación — se incluye con el almacén. govern() es lo que lo aplica: políticas frente a cada escritura, actualización, lectura y exportación, y un rastro de auditoría de solo añadido de quién leyó qué y por qué.

import { SqliteMemoryStore, govern, personalDefaults, storeAudit } from "al-buddy-memory";

const inner = new SqliteMemoryStore("brain.db");
const store = govern(inner, {
  policies: [personalDefaults({ owner: "chris" })],
  context: () => ({ actor: currentActor() }),
  // The trail goes in the database, hash-chained, in the same transaction as
  // the fact it describes. `new ChainedAudit("audit.jsonl")` puts it in a file
  // instead — for a store that is not SQLite, or when you want it outside the
  // file it describes. See docs/GOVERNANCE.md for what each one proves.
  audit: storeAudit(inner),
});

Para asegurarte de que nada se borre por accidente, añade memoryLock() a las políticas: mientras esté ahí, el borrado se rechaza para todos, incluido el propietario, hasta que lo quites (o cambies el interruptor que lee). Invalidar un hecho sigue funcionando; eso no es borrado. El almacén sin procesar y el archivo de base de datos están fuera de cualquier política, así que mantén copias de seguridad.

Y para hacer que una eliminación sea algo que puedas revertir, pasa recentlyDeleted: { days: 14 } a govern(): una eliminación entonces mueve el hecho fuera de la recuperación durante 14 días, restoreDeleted lo trae de vuelta, y purgeDeleted lo borra definitivamente una vez que los días se agotan (preguntando a las políticas de nuevo, por lo que el bloqueo sigue ganando). Nada se ejecuta en un temporizador, y hasta que se purga el hecho sigue en exportaciones y copias de seguridad. Revisa el rastro con al-buddy-memory verify-audit brain.db. Nombra el primer evento que fue editado, eliminado, insertado o reordenado. Lo que establece, y las dos cosas que no hace, están escritas en docs/GOVERNANCE.md — versión corta: la cadena es a prueba de manipulaciones evidente; una cola cortada de un archivo de registro es invisible para ella, una cola cortada de la tabla se detecta siempre que nadie reinicie el contador propio de la tabla, y solo una cabeza que ancles en otro lugar detecta una reescritura o una copia de seguridad restaurada.

Se incluyen tres políticas para copiar: valores predeterminados personales (los secretos se clasifican automáticamente como Sensibles; los hechos sensibles nunca llegan, salen o son borrados por nadie más que el propietario en persona; solo el propietario cambia un hecho), modo guardián (solo un guardián puede escribir o cambiar un hecho de un guardián), auditoría empresarial (inferencias de baja confianza ocultas de los no revisores; exportaciones restringidas a exportadores). Una política es un objeto simple con cinco ganchos opcionales; consulta docs/GOVERNANCE.md.

Sin ninguna política, el almacén aún garantiza: los hechos sellados nunca aparecen en una búsqueda a menos que se soliciten por clasificación; provenance, nodeId, encryptionKeyRef, content sin procesar y el rastro de anclaje son inmutables después de la escritura, a través de todas las rutas, incluida la importación; cada instante se almacena en una ortografía UTC canónica. Dos cosas que no hace, dicho claramente: la procedencia es lo que el escritor afirma (inmutable una vez escrito, no verificado — vincula a los actores a la procedencia en una política); y encryptionKeyRef nombra una clave que gestionas, no cifra el archivo.

El identificador gobernado es el límite. govern(store, …) pone políticas delante de cada operación que puede cambiar un hecho o revelar uno — incluido el borrado, que se rechaza a menos que una política lo permita explícitamente. Quien tenga el almacén interno no está gobernado por nada, así que entrega el gobernado.

Las reglas a las que se somete un asistente en esta memoria se publican en docs/policies: comportamiento ético, soberanía y privacidad del usuario, ciclo de vida y guardianes, administración de datos — y un registro honesto de lo que el código aplica, lo que una instrucción conlleva, y lo que sigue siendo una decisión de una persona. Cambian abiertamente.

Límites, medidos

Un archivo SQLite, un proceso, un escritor. Medido en un portátil M1 Pro con 100,000 hechos (bench/bench.mjs, better-sqlite3, WAL):

Operación (100,000 hechos)Medido
Insertar, un hecho por llamada5,400–5,900 hechos/s (17–18 s para los 100k)
Recuperación por palabras clave, top 10 (FTS5 + reordenamiento por decaimiento)30–50 ms mediana, ~150 ms peor de cinco términos; primera consulta después de abrir ~320–380 ms (caché fría)
Recuperación por palabras clave a través de un identificador gobernado, top 10~75 ms para una palabra en el 10% de los hechos, ~135 ms para dos palabras así, ~850 ms para una palabra en cada hecho — ver abajo
Recuperación solo por filtros, top 100.5–1 ms
Obtener por id0.1 ms
Invalidar un hecho0.5 ms
Reconstruir snapshotAsOf2.19 s con 100,001 versiones
Tamaño del archivo69 MB para los 100,000 hechos; cada cambio registrado añade ~738 bytes (140 MB después de una actualización a cada hecho)
Evento de auditoría en audit_events, en la transacción propia del hecho+0.04 ms por escritura gobernada, +0.5 ms por lectura gobernada (una lectura también se audita, así que toma el bloqueo de escritura brevemente)
Verificar la cadena — verify-audit <db>lineal, ~3 µs/evento: 83 ms a 20,000 eventos, 325 ms a 100,000, 1.5 s a 500,000. Cada proceso lo paga una vez, antes de su primera escritura gobernada y fuera de la transacción de escritura, así que retrasa ese proceso y no bloquea a ningún otro. Memoria constante (el recorrido fluye)
Qué tan rápido crece el rastroun evento por escritura gobernada, uno o dos por lectura gobernada — un remember es +2, un recall +1. Nada lo poda. A 500,000 eventos el rastro es ~128 MB, que puede exceder los hechos que describe; si conduces un almacén tan duro, vigílalo

Los rangos son tres ejecuciones del mismo script en 0.4.0. La paginación es exacta: una página de diez es la primera diez de la lectura ordenada completa. Cuando los hechos han decaído genuinamente, el almacén puede tener que leer más allá de su grupo de candidatos de 200 filas para mantener esa promesa — el peor caso es una lectura completa de los hechos coincidentes (~300 ms a 100k), y solo ocurre cuando un hecho decaído y uno más reciente intercambiarían lugares.

La medición de tiempo de transacción es una ejecución en el mismo M1 Pro: 100,000 hechos, un refuerzo registrado por hecho, y una invalidación anterior. Reconstruir todos los 100,000 hechos de 100,001 versiones tomó 2.19 s. Es una reconstrucción lineal en memoria, no una búsqueda de punto indexada.

Una búsqueda de palabras clave gobernada es más lenta a propósito. Lee cada coincidencia, mantiene las que el actor puede ver, y las clasifica con rareza de palabras contada solo sobre esas coincidencias visibles: el BM25 del almacén cuenta la rareza en todos los hechos, incluidos los ocultos, así que permitiría que un hecho oculto reordenara los resultados visibles. Al tamaño de una memoria personal (unos pocos miles de hechos) la diferencia de velocidad no se nota, y una prueba mantiene su calidad: doce hechos solicitados en preguntas simples ("cuál es el inicio de sesión wifi") entre doscientos distractores todos aterrizan en la primera página.

Lo que eso significa: un asistente personal o un servicio de un solo inquilino no notará el almacén; un SaaS multiinquilino necesita el backend Postgres en la hoja de ruta. Node/TypeScript solo por ahora; el incrustador opcional en el dispositivo es una descarga de modelo de 25 MB.

La ruta semántica cuesta más, y es el punto débil honesto

Todo lo anterior es la ruta de palabras clave. La recuperación con un incrustador conectado pasa por un escaneo lineal de fuerza bruta: cada vector almacenado se lee, analiza y puntúa contra la consulta. Medido de la misma manera (bench/bench-vectors.mjs, vectores de 384 dimensiones, el ancho del modelo predeterminado en el dispositivo), en el mismo portátil:

Con un incrustador conectado20,000 hechos100,000 hechos
Tamaño del archivo (hechos + vectores)172 MB864 MB
De los cuales vectores160 MB800 MB
Por vector, en disco~8.0 KB~8.0 KB
Recuperación semántica, top 10 — primera de una sesión765 ms3,200 ms
Recuperación semántica, top 10 — posterior36 ms mediana187 ms mediana

Léelo como un techo, no como una victoria de referencia.

Y el escaneo de fuerza bruta no es lo que cuesta. Eso vale la pena decirlo claramente, porque es el sospechoso obvio y está mal. Cronometrando una llamada en frío etapa por etapa a 100,000 hechos, dos ejecuciones en el mismo portátil (la segunda 2026-09-19):

Etapa de una recuperación semántica en frío, 100,000 hechosMedido
Lectura SQL de la tabla de vectores978–1,182 ms
JSON.parse de esas filas1,418–1,744 ms
Escaneo de coseno de todos los 100,000 vectores85–127 ms
Toda la llamada, en frío, de extremo a extremo3,650–4,170 ms
Por vector, almacenado como texto JSON8,003 bytes

Leer las filas y analizarlas es 95–96% de esas tres etapas; el escaneo que todos asumen que es el cuello de botella es alrededor del 4%. Tres cosas siguen:

  • Los vectores se almacenan como texto JSON, así que un vector de 384 flotantes cuesta 8,003 bytes en lugar de los ~1.5 KB que los mismos flotantes ocupan como binario. Ahí es donde va el tamaño del archivo — los mismos 100,000 hechos son 69 MB sin vectores y 864 MB con ellos — y, según la tabla, también es donde va el tiempo, porque esas filas de 8 KB tienen que leerse y analizarse.
  • Así que el almacenamiento BLOB es el movimiento, y sqlite-vec no lo es — a este tamaño. Almacenar el vector como un BLOB Float32Array elimina el análisis por completo y reduce la lectura en aproximadamente 5×, que es donde reside el 95% del costo. Entregar la búsqueda a sqlite-vec atacaría el escaneo de 85–127 ms, que no es el problema todavía. Ambas cifras son una proyección de la tabla anterior, no un resultado logrado: ninguna está construida, y ningún número en este README proviene de una implementación BLOB.
  • La caché es desechable y etiquetada por modelo. Los vectores viven en su propia tabla claveada por (nodeId, model); eliminarlos no pierde nada más que tiempo, y un vector de un modelo diferente se omite en lugar de compararse. Actualizar el incrustador es un re-índice, nunca una migración. Los tamaños solo de hechos anteriores son lo que la memoria realmente pesa.

El escaneo sigue siendo lineal en el número de hechos, y la primera llamada de una sesión paga por toda la tabla de vectores; las llamadas posteriores reutilizan una caché en proceso de 60 segundos y aún puntúan cada vector. Si estás conectando un incrustador sobre decenas de miles de hechos, dimensiona la máquina para la tabla anterior, o mantente en la ruta de palabras clave hasta que el trabajo BLOB llegue.

Copias de seguridad, restauraciones y carpetas sincronizadas

Una base de datos SQLite en modo WAL es tres archivos — brain.db, brain.db-wal, brain.db-shm — y dos hábitos ordinarios perderán silenciosamente la memoria de una persona:

  • Restaurar una copia de seguridad: detén el servidor primero, luego elimina brain.db-wal y brain.db-shm antes de copiar la copia de seguridad en su lugar. Un -wal dejado junto a un archivo restaurado se reproduce sobre él en la próxima apertura, así que la restauración parece tener éxito, no informa ningún error, y te deja con los datos que intentabas reemplazar. Verificado, 2026-09-19.
  • Qué comando de copia de seguridad: sqlite3 brain.db ".backup out.db" y VACUUM INTO 'out.db' son los seguros — ambos toman una instantánea consistente de una base de datos en vivo, verificada mientras otro proceso escribía. sqlite3 .dump también funciona, pero no lleva user_version; antes de 0.4.2 un volcado restaurado no podía abrirse en absoluto (duplicate column name: valid_from), y ahora migra limpiamente. Nunca hagas una copia de seguridad cp-ando una base de datos en vivo: una copia tomada a mitad de escritura puede ser ilegible, y una legible aún puede fallar PRAGMA integrity_check.
  • Carpetas sincronizadas: nunca pongas la base de datos en iCloud, Dropbox, OneDrive o Google Drive. El modo WAL asume una máquina coordinando sus propios bloqueos; un cliente de sincronización copiando los tres archivos independientemente, o dos máquinas escribiendo a través de una carpeta, corrompe el archivo en lugar de conflictuar visiblemente. Haz una copia de seguridad de la carpeta por todos los medios — cópiala en un horario, o usa .backup/VACUUM INTO — pero no dejes que un cliente de sincronización posea el archivo en vivo.

La puntuación de conformidad

Pruébalo: albuddy.com — pega cualquier exportación de memoria, nada sale de tu navegador.

Las referencias de recuperación están saturadas. Nadie puntúa si un sistema de memoria puede decir quién afirmó un hecho, desde cuándo, si es aún verdadero, y si el hecho sobrevive al dejar al proveedor. Esto lo hace, en cualquier exportación que pegues:

npx al-buddy-memory conformance my-export.json          # format auto-detected
npx al-buddy-memory conformance agent.af --format blocks     # block-style agent files
npx al-buddy-memory conformance memories.json --format records  # flat memory records
npx al-buddy-memory conformance --demo                  # a small governed store, for comparison

Siete dimensiones, cada una 0–100% con la razón explicada; una dimensión que la muestra no puede probar (sin hechos retirados presentes, por ejemplo) se informa como no probada y se deja fuera del total en lugar de contarse como un fallo. La puntuación de referencia:

ExportaciónProcedenciaDesde cuándoRetirar sin borrarConfianzaRelacionesPortabilidadGrado
al-buddy-memory (almacén de demostración)100%100%100%100%100%100%A

Puntúa tu propia exportación de la misma manera: --format blocks para archivos de agente de estilo bloque, --format records para registros de memoria planos, o pégala en la demostración en albuddy.com.

De dónde vienen los números, dicho claramente, porque este es un puntuador con el que también nos puntuamos a nosotros mismos. Cinco de los siete — procedencia, desde cuándo, retirar sin borrar, confianza, relaciones — se cuentan de los registros en el archivo que pegas; cambia la muestra y se mueven. Dos no lo son. Si un sistema mantiene un hecho retirado, si su esquema está publicado, y si detalla los hechos son propiedades del sistema, que ninguna exportación individual puede probar, así que el autor del adaptador las declara en src/conformance/adapters.ts y aparecen textualmente en la línea de razón del informe. La única parte que se ejecuta en lugar de afirmarse es el viaje de ida y vuelta: para nuestro propio formato, el puntuador importa tu artefacto en un almacén nuevo, lo exporta de nuevo y compara — en tu archivo, y dice lossy si eso falla. El libro de reglas, incluyendo qué dimensión es cuál y qué no mide la puntuación (calidad de recuperación, veracidad, latencia), está en docs/SCORING.md. Los adaptadores están escritos contra formas de exportación, no contra proveedores. Si un sistema comienza a registrar procedencia, su puntuación sube — ese es el punto. Añade un adaptador para tu forma y abre un PR; si crees que declaramos un rasgo incorrectamente para el tuyo, eso también es un PR de una línea.

El servidor MCP de gobernanza

La mayoría de los servidores MCP de memoria le entregan al agente un dato. Este le entrega un dato que puede sopesar: cada resultado de recall lleva provenance, validFrom, validTo, current, confidence, qué asistente lo escribió y cuál lo retiró, y — para un dato superado — el id de lo que lo reemplazó. invalidate cierra la validez de un dato y conserva el registro; el servidor no tiene herramienta de borrado. Sirve un almacén gobernado: el personalDefaults del propietario con el cliente de IA como audiencia, de modo que un secreto que un agente escribe se clasifica como Sensible y se mantiene fuera de la recuperación de cualquier IA, y cada llamada es auditada — dentro de la propia base de datos, en la misma transacción que el dato.

{ "mcpServers": { "memory": { "command": "npx",
    "args": ["-y", "--package=al-buddy-memory@0.6.0", "al-buddy-memory-mcp"] } } }

al-buddy-memory-mcp es un ejecutable dentro del paquete al-buddy-memory, no un paquete propio, por lo que --package= es lo que le dice a npx dónde encontrarlo — npx al-buddy-memory-mcp looks for a package by that name and gets a 404. Drop the @0.6.0 para seguir la última versión en lugar de la que probaste.

¿Publicando? Este pin es una versión documentada y queda obsoleto en el momento en que se publica una nueva — el ejemplo instalaría entonces un servidor más antiguo que el que describe la página. Adelántalo en el mismo commit que el aumento de versión — así se ha hecho, en 0.4.2 y 0.5.0, y fue @0.4.1 mientras 0.4.1 era la actual. Ejecutar el comando fijado contra una versión más reciente devuelve el handshake del servidor más antiguo, que es como un lector termina leyendo documentación que no coincide con lo que acaba de instalar (revisión de lanzamiento de Astra, 2026-09-19).

La memoria se guarda en ~/.al-buddy-memory/brain.db; establece AL_BUDDY_MEMORY_DB para ponerla en otro lugar. Dale una ruta absoluta — una configuración JSON no es un shell, y un ~ en ella es expandido por este servidor pero no por todo lo demás que pueda leer el valor. El rastro de auditoría va dentro de esa base de datos, en audit_events, añadido en la misma transacción que el dato que describe — una sola cadena hash sin importar cuántos asistentes estén ejecutándose. Compruébalo con al-buddy-memory verify-audit ~/.al-buddy-memory/brain.db. Un servidor de antes de la tabla dejaba un registro por proceso en brain.db.audit/, y uno anterior a ese un único brain.db.audit.jsonl; esos no se adoptan ni se extienden (atestiguan un período que la tabla no puede, y viceversa) y el mismo comando los reporta junto a la tabla. AL_BUDDY_MEMORY_AUDIT sigue nombrando un archivo JSONL para usar en su lugar — un proceso por archivo si lo haces.

Un resultado de recall se ve así — cada campo que un agente necesita para decidir cuánto confiar en el dato:

{ "id": "…", "text": "Lives in Tokyo", "provenance": "UserInput", "validFrom": "2026-06-01T00:00:00Z",
  "validTo": null, "current": true, "confidence": 1, "supersededBy": null, "derivedFrom": [],
  "recordedAt": "2026-06-01T00:00:00Z",
  "origin": { "app": "claude-desktop", "appVersion": "1.2.3", "via": "mcp" }, "retiredBy": null }

origin es qué asistente escribió el dato, tomado del handshake de MCP en lugar de del modelo, y retiredBy es cuál lo cerró — así que en memoria genuinamente compartida entre asistentes, un dato retirado es un evento con un actor. Ambos son null cuando el host no sabía nada.

remember responde "¿qué podría reemplazar esto?" Devuelve el dato que almacenó más mayConflictWith: hasta tres datos actuales que se leen como el nuevo, cada uno con su id, texto y validFrom.

{ "id": "…", "text": "Lives in Berlin", "…": "…",
  "mayConflictWith": [ { "id": "…", "text": "Lives in Tokyo", "validFrom": "2026-06-01T00:00:00Z" } ] }

Nada se retira automáticamente — el cliente los lee y llama a invalidate sobre los que dejaron de ser verdad. Ese es todo el bucle invalidar-nunca-sobrescribir, y hasta 0.4.2 nada en la superficie lo solicitaba. Trata la lista como datos a leer: son los mejores emparejamientos léxicos, no conflictos que se hayan probado.

El primer recall de una conexión también devuelve el nivel fijado — las reglas permanentes de la persona — como un segundo bloque de contenido, de modo que el nivel que afirma estar en cada prompt llega allí sin gastar ninguno de los 512 caracteres del handshake.

Herramientas: remember, recall, history, explain, invalidate, pin, unpin, pinned, mental_model, define_mental_model. mental_model lee la respuesta preescrita de una pregunta permanente con su frescura y evidencia (el host actualiza las respuestas según su propio horario). explain responde por qué se cree un dato: quién lo afirmó, cuándo fue verdad, qué lo terminó, y para una conclusión las palabras exactas en las que se apoya, cada una verificada ahora. remember acepta como máximo 4,000 caracteres y pin 500; una consulta de recall 1,000, una razón de invalidate 500, y un id 128. pin rechaza texto que parezca un secreto, porque el servidor lo almacenaría como Sensible y ningún asistente podría verlo. El replacedBy de invalidate debe nombrar un dato que el llamante pueda ver. SQLite en disco, sin servicio, sin clave. Los cuerpos de las herramientas son una función simple sobre un MemoryStore (governanceTools(...), exportado desde al-buddy-memory/mcp), por lo que se ejecutan contra cualquier backend y se prueban sin transporte.

Hoja de ruta

  • La especificación y el formato portátil, publicados y versionados (este repositorio)
  • al-buddy-memory conformance <export>: puntuar cualquier exportación de memoria por procedencia, invalidación y portabilidad, con adaptadores para archivos de agente estilo bloque y registros de memoria planos (v0.2.0)
  • El servidor MCP de gobernanza: un servidor de memoria que devuelve procedencia y validez con cada dato (v0.2.0)
  • Hooks de gobernanza con rastro de auditoría y tres políticas de ejemplo; procedencia inmutable en tiempo de ejecución; límites medidos en 100k datos (v0.3.0)
  • El evento de auditoría confirmado en la misma transacción que el dato que describe, como una sola cadena que muchos procesos comparten (v0.4.2)
  • Una tabla comparativa y una demo en vivo de pega-tu-exportación (albuddy.com)
  • Tiempo de transacción, la segunda mitad de bi-temporal: "qué creíamos en X", incluyendo un dato sostenido erróneamente y luego corregido (v0.5.0)
  • Un backend Postgres detrás de la misma interfaz MemoryStore, para despliegues multi-tenant y alojados (SQLite sigue siendo el predeterminado local-first; la interfaz es pequeña y la suite de conformidad es lo que un backend debe pasar)
  • Integraciones de frameworks (LangChain, CrewAI, Vercel AI SDK)

Desarrollo

npm ci
npm run check   # typecheck, tests, and the browser bundle — exactly what CI runs

Las pruebas incluyen una suite de conformidad conductual contra la que cada backend se ejecuta a sí mismo.