Memora
Un servidor MCP ligero para almacenamiento de memoria semántica, grafos de conocimiento y contexto entre sesiones.
Documentación
Memora
"Nunca conoces de verdad el valor de un momento hasta que se convierte en un recuerdo."
Dale a tus agentes de IA una memoria colectiva persistente
Una capa de memoria MCP para agentes: almacenamiento estructurado, recuperación semántica, relaciones de grafo y contexto entre sesiones respaldado por fuentes.
Absorbe el trabajo del agente en una memoria de grafo duradera y luego usa memory_digest(topic) para recuperar recuerdos relevantes, TODOs/problemas, aristas relacionadas e IDs de fuente.
Características · Vista previa · Instalación · Uso · Configuración · Multi-BD · Contenedores · Grafo en vivo · Grafo en la nube · Chat · Búsqueda semántica · Documentos · Deduplicación LLM · Enlaces · Neovim
Características
Almacenamiento principal
- 💾 Almacenamiento persistente - SQLite con sincronización en la nube opcional (S3, R2, D1)
- 🗄️ Enrutamiento multi-base de datos - Un proceso sirve muchos almacenes; un espacio de trabajo llega al suyo en
/mcp/<name>(ver Enrutamiento multi-base de datos) - 📂 Organización jerárquica - Estructura de sección/subsección con asignación automática de jerarquía
- 📦 Exportar/Importar - Copia de seguridad y restauración con estrategias de fusión
Absorción y linaje
- 🧬 Absorber - Introduce hechos; un LLM los clasifica contra el almacén (duplicado / actualización / contradicción / relacionado / nuevo), omite duplicados, enlaza relaciones y consolida hechos relacionados — con vista previa de
dry_run - 🌱 Linaje de sustitución - Las actualizaciones sustituyen el conocimiento antiguo en lugar de eliminarlo; la recuperación sigue la cadena hasta la versión actual por defecto (modos
follow:active,latest,full_history) - 🗞️ Resumen de temas -
memory_digest(topic)agrupa recuerdos relevantes, TODOs/problemas abiertos, aristas relacionadas e IDs de fuente en una sola recuperación
Búsqueda e inteligencia
- 🔍 Búsqueda semántica - Incrustaciones vectoriales (TF-IDF, sentence-transformers, OpenAI)
- 🎯 Consultas avanzadas - Texto completo, rangos de fechas, filtros de etiquetas (AND/OR/NOT), búsqueda híbrida
- 🔀 Referencias cruzadas - Recuerdos relacionados enlazados automáticamente según similitud
- 🤖 Deduplicación LLM - Encuentra y fusiona duplicados con comparación impulsada por IA
- 🔗 Enlace de recuerdos - Aristas tipadas, refuerzo de importancia y detección de clústeres
Almacenamiento de documentos
- 📄 Documentos estructurados - Almacena documentos markdown como árboles de fragmentos buscables (afirmaciones, elementos de plan, referencias, riesgos)
- 🔒 Integridad de fragmentos - Protege contra eliminación/fusión/absorción accidental de fragmentos de documentos
- 🔍 Búsqueda granular - Afirmaciones y hallazgos individuales son buscables semánticamente mientras el documento completo sigue siendo recuperable como unidad
Herramientas y visualización
- ⚡ Automatización de memoria - Herramientas estructuradas para TODOs, problemas y secciones
- 🕸️ Grafo de conocimiento - Visualización interactiva con renderizado Mermaid y superposiciones de clústeres
- 🌐 Servidor de grafo en vivo - Servidor HTTP integrado con opción alojada en la nube (D1/Pages)
- 💬 Chat con recuerdos - Panel de chat impulsado por RAG con llamada a herramientas LLM para buscar, crear, actualizar y eliminar recuerdos mediante chat en streaming
- 📡 Notificaciones de eventos - Sistema basado en sondeo para comunicación entre agentes
- 📊 Estadísticas y análisis - Uso de etiquetas, tendencias e información de conexiones
- 🧠 Información de memoria - Resumen de actividad, detección de datos obsoletos, sugerencias de consolidación y análisis de patrones impulsado por LLM
- 📜 Historial de acciones - Rastrea todas las operaciones de memoria (crear, actualizar, eliminar, fusionar, reforzar, enlazar) con vista de línea de tiempo agrupada
Vista previa
Instalación
Dos vías. pip es un hijo stdio local que el cliente inicia. Un contenedor es un servicio HTTP separado que inicias con up; con MEMORA_DATABASES sirve múltiples almacenes desde un solo proceso. El LaunchAgent supervisa el proxy, no el contenedor — después de un reinicio del host, el listener puede volver mientras su upstream sigue detenido. Si estás ejecutando memora como servicio, la vía del contenedor es la instalación.
pip (local / stdio)
pip install memora-mcp
El paquete PyPI es memora-mcp (el memora simple en PyPI es un proyecto no relacionado). Incluye almacenamiento en la nube (S3/R2) e incrustaciones OpenAI listas para usar.
# Optional: local embeddings (offline, ~2GB for PyTorch)
pip install "memora-mcp[local]"
# Latest development version straight from git
pip install "git+https://github.com/agentic-box/memora.git"
Luego inícialo desde .mcp.json con "command": "memora-server" (ver Configuración).
Contenedor (servicio HTTP)
El runtime por defecto es el CLI container de Apple. Cada operación de contenedor que scripts/memora-instance.sh realiza (build, up, status, logs, down) usa $MEMORA_CONTAINER_BIN (por defecto container). El proceso proxy generado no; codifica container list.
Antes del primer build:
-
Instala el CLI
containerde Apple (pkg firmado desde sus releases de GitHub). Necesita un Mac con chip de Apple ejecutando macOS 26 — Apple no admite versiones anteriores de macOS paracontainer. -
Inicia el runtime — el primer comando documentado de Apple, que también instala un kernel si no hay ninguno configurado:
container system start -
Clona este repositorio y
cden él:git clone https://github.com/agentic-box/memora.git cd memora -
Copia la plantilla de instancia. Viene con
INSTANCE=myinstancepara que las líneas posteriores debuild/up/proxycoincidan sin renombrar. EditaPORTy un backend (STORAGE_URI,VOLUMEoMEMORA_DATABASES):cp instances/example.env instances/myinstance.env -
Crea el archivo de credenciales e instala el proxy que ejecutará el LaunchAgent.
cred_args()requiere un.mcp.jsoncuyomcpServers.memora.envcontengaCLOUDFLARE_API_TOKEN(acceso a D1) y las claves de incrustación/LLM —upfalla si ese archivo falta. El script busca~/.config/memora/credentials.mcp.jsonsi ese archivo existe, de lo contrario~/repos/agentic-box/.mcp.json. EstableceCRED_SOURCEen el archivo de instancia para elegir una ruta. Por separado,proxyrenderiza un plist cuyo ejecutable es$MEMORA_PROXY_BIN(por defecto~/.local/libexec/memora/memora_proxy.py) y cuyos registros viven en$MEMORA_LOG_DIR(por defecto~/.local/var/log) — nada crea ninguno en un clon nuevo.mkdir -p ~/.config/memora ~/.local/libexec/memora ~/.local/var/log cp scripts/memora_proxy.py ~/.local/libexec/memora/ # real values; any key is fine, an absent file is not # the default umask is permissive -- chmod 600 keeps other local accounts out cat > ~/.config/memora/credentials.mcp.json <<'JSON' {"mcpServers":{"memora":{"env":{"CLOUDFLARE_API_TOKEN":"REPLACE","OPENAI_API_KEY":"REPLACE"}}}} JSON chmod 600 ~/.config/memora/credentials.mcp.jsonEse JSON es la configuración mínima correcta: tanto el LLM como las incrustaciones usan el host predeterminado de OpenAI con una clave real de OpenAI. No agregues
OPENAI_BASE_URLapuntando a OpenRouter sin el par de incrustaciones de Incrustaciones — OpenRouter no tiene endpoint de incrustaciones, cada llamada de incrustación devuelve 404, y memora cae silenciosamente a bolsas de palabras TF-IDF mientras parece saludable.
Luego:
./scripts/memora-instance.sh build myinstance # tags IMAGE from myinstance.env (memora-pilot if IMAGE is unset)
./scripts/memora-instance.sh up myinstance # runs that same IMAGE
./scripts/memora-instance.sh proxy myinstance # render the LaunchAgent; run the printed launchctl
up no publica un puerto de host. El listener al que se conecta el espacio de trabajo es el proxy. proxy solo renderiza un LaunchAgent de macOS e imprime los comandos de launchctl — no carga el servicio. Ejecuta esos comandos impresos.
La URL del espacio de trabajo impresa es siempre http://127.0.0.1:<PORT>/mcp (el predeterminado del registro). Para un almacén no predeterminado, agrega /<name> tú mismo — un /mcp simple en un registro se vincula silenciosamente a MEMORA_DEFAULT_DB:
{"mcpServers": {"memora": {"type": "http", "url": "http://127.0.0.1:<PORT>/mcp/<store>"}}}
Justificación del proxy, credenciales, archivos de instancia y MEMORA_CONTAINER_BIN: Implementación de contenedor.
Uso
El servidor se ejecuta automáticamente cuando está configurado en Claude Code. Invocación manual:
# Default (stdio mode for MCP)
memora-server
# With graph visualization server
memora-server --graph-port 8765
# HTTP transport (alternative to stdio)
memora-server --transport streamable-http --host 127.0.0.1 --port 8080
Configuración
Claude Code
Agrega a .mcp.json en la raíz de tu proyecto:
BD local:
{
"mcpServers": {
"memora": {
"command": "memora-server",
"args": [],
"env": {
"MEMORA_DB_PATH": "~/.local/share/memora/memories.db",
"MEMORA_ALLOW_ANY_TAG": "1",
"MEMORA_GRAPH_PORT": "8765"
}
}
}
}
BD en la nube (Cloudflare D1) - Recomendado:
{
"mcpServers": {
"memora": {
"command": "memora-server",
"args": ["--no-graph"],
"env": {
"MEMORA_STORAGE_URI": "d1://<account-id>/<database-id>",
"CLOUDFLARE_API_TOKEN": "<your-api-token>",
"MEMORA_ALLOW_ANY_TAG": "1"
}
}
}
}
Con D1, usa --no-graph para deshabilitar el servidor de visualización local. En su lugar, usa el grafo alojado en tu URL de Cloudflare Pages (ver Grafo en la nube).
BD en la nube (S3/R2) - Modo sincronización:
{
"mcpServers": {
"memora": {
"command": "memora-server",
"args": [],
"env": {
"AWS_PROFILE": "memora",
"AWS_ENDPOINT_URL": "https://<account-id>.r2.cloudflarestorage.com",
"MEMORA_STORAGE_URI": "s3://memories/memories.db",
"MEMORA_CLOUD_ENCRYPT": "true",
"MEMORA_ALLOW_ANY_TAG": "1",
"MEMORA_GRAPH_PORT": "8765"
}
}
}
}
CLI de Codex
Agrega a ~/.codex/config.toml:
[mcp_servers.memora]
command = "memora-server" # or full path: /path/to/bin/memora-server
args = ["--no-graph"]
env = {
AWS_PROFILE = "memora",
AWS_ENDPOINT_URL = "https://<account-id>.r2.cloudflarestorage.com",
MEMORA_STORAGE_URI = "s3://memories/memories.db",
MEMORA_CLOUD_ENCRYPT = "true",
MEMORA_ALLOW_ANY_TAG = "1",
}
Variables de entorno
| Variable | Descripción | |------------------------|-----------------------------------------------------------------------------| | `MEMORA_DB_PATH` | Ruta local de la base de datos SQLite (predeterminado: `~/.local/share/memora/memories.db`) | | `MEMORA_STORAGE_URI` | URI de almacenamiento: `d1:///` (D1) o `s3://bucket/memories.db` (S3/R2). Se usa cuando `MEMORA_DATABASES` no está definido. | | `MEMORA_DATABASES` | Objeto JSON `{name: uri}` que mapea cada almacén que este proceso sirve. Los nombres son un segmento de ruta URL (`/mcp/`): solo letras, dígitos, `-`, `_`, `.`. Claves duplicadas, valores vacíos, nombres inseguros u objetos no válidos se niegan a iniciar en lugar de elegir silenciosamente un almacén. Sin definir = almacén único (heredado). Ver [Enrutamiento de múltiples bases de datos](#multi-database-routing). | | `MEMORA_DEFAULT_DB` | Nombre del registro que usa un `/mcp` simple. Requerido cuando el registro tiene más de una base de datos; con exactamente un nombre, ese nombre es el predeterminado. Un valor que no esté en el registro se niega a iniciar. | | `CLOUDFLARE_API_TOKEN` | Token de API para D1 (URI `d1://`). Se acepta `CF_API_TOKEN` como alias. | | `MEMORA_CLOUD_ENCRYPT` | Cifra el archivo local antes de subirlo a S3/R2. Sin definir/`false` = desactivado; `1`/`true`/`yes` = activado. | | `MEMORA_CLOUD_COMPRESS`| Comprime el archivo local antes de subirlo a S3/R2. Sin definir/`false` = desactivado; `1`/`true`/`yes` = activado. | | `MEMORA_CACHE_DIR` | Directorio de caché local para una base de datos sincronizada con S3/R2. Sin definir: el backend elige una ruta de caché. | | `MEMORA_ALLOW_ANY_TAG` | Permitir cualquier etiqueta sin validación contra la lista permitida (`1` para habilitar) | | `MEMORA_TAG_FILE` | Ruta a un archivo JSON que contiene una matriz de etiquetas permitidas, p. ej. `["plan", "memora/issues"]` | | `MEMORA_TAGS` | Lista separada por comas de etiquetas permitidas | | `MEMORA_HOST` | Dirección de enlace para transportes HTTP (predeterminado `127.0.0.1`). Se puede anular con `--host`. | | `MEMORA_PORT` | Puerto de enlace para transportes HTTP (predeterminado `8000`). Se puede anular con `--port`. | | `MEMORA_GRAPH_PORT` | Puerto para el servidor de visualización del grafo de conocimiento (predeterminado: `8765`) | | `MEMORA_TRANSPORT` | `stdio` (predeterminado), `sse` o `streamable-http`. Un valor **env** desconocido cae a `stdio`; `--transport` aún rechaza valores desconocidos. El enrutamiento de múltiples bases de datos y la protección de sesión se ejecutan solo en `streamable-http`. | | `MEMORA_TOOL_PROFILE` | Subconjunto de herramientas expuesto a los clientes: `full` (predeterminado, las 43), `leader` (19), `agent` (12). Sin definir/vacío = `full`; un valor desconocido se niega a iniciar. Ver [Perfiles de herramientas](#tool-profiles). | | `MEMORA_MAX_SESSIONS` | Límite máximo de sesiones MCP concurrentes (predeterminado `128`). `0` lo desactiva. Una tasa de creación más un tiempo de espera inactivo no es un límite: un cliente que mantiene identificadores de sesión vivos puede crecer sin límite a la tasa de creación. Los valores no válidos se niegan a iniciar. Solo Streamable-HTTP. | | `MEMORA_MAX_INIT_PER_MIN` | Nuevas sesiones admitidas por minuto (predeterminado `120`). `0` lo desactiva. Los valores no válidos se niegan a iniciar. Solo Streamable-HTTP. | | `MEMORA_MAX_INIT_BODY_BYTES` | Tamaño máximo del cuerpo de la solicitud de inicialización aceptado/almacenado en búfer (predeterminado `65536`, mínimo `1024`). Las solicitudes más grandes reciben `413`. Los valores no válidos se niegan a iniciar. Solo Streamable-HTTP. | | `MEMORA_SESSION_IDLE_TIMEOUT` | Segundos antes de que se elimine una sesión válida abandonada (predeterminado `1800`). `0` lo desactiva. Los valores no válidos se niegan a iniciar. Solo Streamable-HTTP. | | `MEMORA_HEALTH_TOKEN` | Token Bearer para cuerpos de `/health/db` detallados (nombres, conteos, texto de error). Sin definir: solo un par de loopback ve el detalle; todos los demás obtienen estado agregado. FastMCP `custom_route()` no está autenticado incluso cuando la autenticación MCP está configurada. Solo transportes HTTP (`memora.health` se importa para SSE/streamable-http, no para stdio). | | `MEMORA_HEALTH_TTL` | Segundos que una instantánea de preparación puede servirse antes de que se deba una actualización (predeterminado `10`, tope `3600`). Debe ser `> 0`. Los valores no válidos se niegan a iniciar. Solo transportes HTTP: un valor malformado no aborta stdio. | | `MEMORA_HEALTH_TIMEOUT`| Límite en una pasada de actualización y en cada sondeo de almacén (predeterminado `15`, tope `300`). Debe ser `> 0`. Solo transportes HTTP. | | `MEMORA_HEALTH_REFRESH_INTERVAL` | Con qué frecuencia el servidor actualiza la preparación por sí mismo (predeterminado `15`, tope `3600`). `0` = solo sondeo. Sin esto, una implementación de proxy no tiene llamador de loopback y la superficie de alerta permanece `unknown` mientras todas las bases de datos estén bien. Cuando la actualización periódica está habilitada, `interval + timeout` debe ser `< MEMORA_HEALTH_MAX_STALE`. Solo transportes HTTP. | | `MEMORA_HEALTH_MAX_STALE` | Edad después de la cual un resultado por base de datos en caché ya no puede informarse como listo (predeterminado `60`, tope `3600`). Debe ser `>= MEMORA_HEALTH_TTL`. Solo transportes HTTP. | | `MEMORA_STALE_DAYS` | Dos consumidores, dos predeterminados, mismo nombre: `memory_insights` trata un TODO/problema abierto como obsoleto después de **14** días; la interfaz del grafo atenúa los elementos cerrados después de **30** días. Defina la variable para anular ambos. | | `MEMORA_EMBEDDING_MODEL` | Backend de incrustaciones: `openai` (predeterminado), `sentence-transformers` o `tfidf` | | `SENTENCE_TRANSFORMERS_MODEL` | Modelo para sentence-transformers (predeterminado: `all-MiniLM-L6-v2`) | | `MEMORA_EMBEDDING_API_KEY` | Clave de API del proveedor de incrustaciones (atómica con la URL base: ver más abajo) | | `MEMORA_EMBEDDING_BASE_URL` | URL base del proveedor de incrustaciones (atómica con la clave de API: ver más abajo) | | `MEMORA_EMBEDDING_STRICT` | **Recomendado `1`.** Fallar de forma contundente en errores de incrustación en lugar de TF-IDF silencioso. Sin esto, un endpoint roto sigue respondiendo mientras cada vector se convierte en una bolsa de palabras clave (cómo 756 memorias se degradaron sin ser notadas). | | `OPENAI_API_KEY` | **Solo LLM** (deduplicación/chat) cuando `MEMORA_EMBEDDING_*` está definido. Las incrustaciones caen a esta clave solo si **ambas** `MEMORA_EMBEDDING_API_KEY` y `MEMORA_EMBEDDING_BASE_URL` no están definidas | | `OPENAI_BASE_URL` | URL base **LLM** (OpenRouter, Azure, etc.). Misma regla de respaldo atómico que la clave: no es una URL de incrustaciones cuando usa una configuración dividida | | `OPENAI_EMBEDDING_MODEL` | Identificador de modelo para el backend de incrustaciones openai. Debe existir en el host de **incrustaciones** (predeterminado `text-embedding-3-small` es solo OpenAI; Cloudflare necesita p. ej. `@cf/baai/bge-m3`) | | `MEMORA_LLM_ENABLED` | Habilitar comparación de deduplicación impulsada por LLM (`true`/`1`/`yes`; predeterminado: `true`) | | `MEMORA_LLM_MODEL` | Modelo para comparación de deduplicación y, si no está definido, para reescritura de consultas y chat local (predeterminado: `gpt-4o-mini`) | | `MEMORA_LLM_TIMEOUT` | Segundos que espera el cliente OpenAI (predeterminado `60`, con piso en `1`). Un valor no numérico cae a `60`. | | `MEMORA_REWRITE_MODEL` | Modelo para reescritura de consultas RAG en el panel de chat del grafo. Sin definir/vacío usa `MEMORA_LLM_MODEL`. | | `MEMORA_VECTOR_SCAN_PAGE_SIZE` | Filas por página al cargar incrustaciones desde D1 (predeterminado `1000`; no numérico o `<1` cae a `1000`; tope duro `10000`). En el predeterminado, un almacén con menos de 1000 filas devuelve el **corpus completo más cada incrustación en una sola respuesta D1**, lo que compitió con el tope de 30s por solicitud de Cloudflare e hizo que `memory_absorb` fallara por completo. **Use `100` en D1** (el script de instancia ya lo inyecta). La paginación es una mitigación, no la solución: absorb lee el corpus una vez por llamada y reutiliza una caché local de proceso claveada en el `embedding_change_epoch` monótono de la base de datos. | | `CHAT_MODEL` | Modelo para el panel de chat local del grafo. Sin definir/vacío cae a `MEMORA_LLM_MODEL`. (El predeterminado `deepseek/deepseek-chat` es Cloudflare Pages `wrangler.toml`, no este proceso.) | | `MEMORA_CLOUD_GRAPH_ENABLED` | `true`/`1`/`yes` para notificar al grafo alojado de escrituras (predeterminado desactivado). | | `MEMORA_CLOUD_GRAPH_WORKER_URL` | URL base del worker para esas transmisiones (`POST /broadcast`). Sin definir: las transmisiones se omiten. | | `MEMORA_CLOUD_GRAPH_DEBOUNCE` | Segundos para agrupar escrituras rápidas antes de transmitir (predeterminado `1.0`). | | `MEMORA_CLOUD_GRAPH_SYNC_SCRIPT` | Ruta capturada al inicio (predeterminado: `memora-graph/scripts/sync.sh` si ese archivo existe). La ruta de escritura actual **no** ejecuta este script: D1 es la fuente de verdad y solo se ejecuta la transmisión del worker. | | `AWS_PROFILE` | Perfil de credenciales AWS de `~/.aws/credentials` (útil para R2) | | `AWS_ENDPOINT_URL` | Endpoint compatible con S3 para R2/MinIO | | `R2_PUBLIC_DOMAIN` | Dominio público para URLs de imágenes R2 |Perfiles de herramientas (MEMORA_TOOL_PROFILE)
Las 43 herramientas MCP se registran incondicionalmente, por lo que cada sesión de agente se inyecta con el esquema completo de herramientas de ~12,700 tokens incluso cuando la mayoría de las herramientas nunca se llaman. MEMORA_TOOL_PROFILE expone un subconjunto por implementación para que una herramienta restringida esté genuinamente ausente: falta en tools/list Y no es despachable (call_tool devuelve unknown-tool, no una ejecución oculta). El perfil se aplica y se atestigua al inicio; el perfil activo y el recuento de herramientas expuestas se registran en stderr.
| Valor | Herramientas | Uso |
|---|---|---|
full (predeterminado) | las 43 | Uso directo de stdio; cada implementación existente permanece sin cambios byte a byte |
leader | 19 | El conjunto de agente más memory_create_section, memory_store_document, memory_get_document, memory_tags, memory_delete, memory_digest, memory_list |
agent | 12 | La superficie de lectura/creación que necesita un agente worker: memory_absorb, memory_semantic_search, memory_hybrid_search, memory_list_compact, memory_get, memory_related, memory_link, memory_stats, memory_create, memory_create_issue, memory_create_todo, memory_update |
- Sin definir / vacío =
full. Ningún despliegue existente cambia su comportamiento. - Un valor desconocido aborta el inicio con un mensaje que nombra los valores válidos. Nunca retrocede silenciosamente a
full— un error tipográfico no debe reexponer herramientas de mantenimiento destructivas (memory_rebuild_embeddings,memory_delete_batch) a cada worker. Cierre en fallo. memory_listestá enleaderpero no enagent. Fue excluido de ambos mientras costaba 163-174s en un almacén D1 frente a los 0.22s dememory_list_compact; #973 corrigió eso (ahora ~1.1s). Se mantiene fuera deagentporque la superficie de lectura de un worker es deliberadamente estrecha, no por velocidad.- El límite líder/agente es dato en
memora/tool_profile.py(dos frozensets). Editarlo es una línea, no un barrido de 43 decoradores. - La poda elimina del dict privado
_tool_manager._toolsde FastMCP, por lo quememorafijamcp>=1.27,<1.28(el minor auditado) y ejecuta una atestación de inicio a través de los manejadores de solicitudes MCP registrados de bajo nivel (_mcp_server.request_handlers[ListToolsRequest]/[CallToolRequest]— el callable de despacho real que usan las solicitudes de clientes, no los helpers de PythonFastMCP.list_tools/call_tool) que se niega a iniciar si el SDK instalado enruta el listado/despacho a otro lugar (deriva de implementación privada). El pin es la guardia estática; la atestación es el respaldo en tiempo de ejecución. Subir el límite superior requiere volver a ejecutartests/test_tool_profile.py. - Bajo despliegue en contenedor el perfil es por contenedor mientras que los roles son por agente. Un contenedor que sirve al líder de un workspace y a sus workers necesita el superconjunto líder;
agenteliminaríacreate_section/store_document/delete/digest/tagsdel líder. memora-server(es decir,memora.server.main()) es la única ruta de servicio perfilada soportada. Un embedder directo que importamemora.server.mcpy llama amcp.run()él mismo omite el perfilado por completo (elmcpglobal aún contiene las 43 herramientas); los embedders que quieran perfilado deben llamar aapply_tool_profileellos mismos o usarmain().
# Leader deployment — exposes 19 tools
MEMORA_TOOL_PROFILE=leader memora-server
# Agent worker — exposes 12 tools
MEMORA_TOOL_PROFILE=agent memora-server
# Full (default) — all 43 tools, existing behaviour
memora-server
# Typo refuses to start:
# MEMORA_TOOL_PROFILE=agnt memora-server
# Error: unknown MEMORA_TOOL_PROFILE='agnt'; valid values: full, leader, agent
Enrutamiento multi-base de datos
Un proceso memora puede servir a cada workspace. MEMORA_DATABASES es un registro
JSON de {name: storage URI}; un cliente llega a su almacén en /mcp/<name>.
El selector es la URL ya presente en .mcp.json, no un argumento de herramienta — un db
opcional en cada herramienta son 43 oportunidades de olvidar uno, y cada fallo escribiría en
el almacén de otra persona.
MEMORA_DATABASES sin definir es la forma antigua: un backend de MEMORA_STORAGE_URI
/ MEMORA_DB_PATH, un /mcp. Los despliegues stdio existentes no cambian.
Enrutamiento (solo streamable-http):
| URL | Resuelve a |
|---|---|
/mcp/<name> | Esa entrada del registro. Nombres desconocidos devuelven 404 {"error":"unknown database"} — el cuerpo no lista los otros nombres. |
/mcp | MEMORA_DEFAULT_DB. Requerido cuando el registro tiene más de una base de datos; un registro de un solo nombre usa ese nombre. |
El enlace es fijo por sesión MCP, no por solicitud. Una sesión abierta en
/mcp/alpha y reutilizada contra /mcp/beta aún resuelve a alpha. Un cliente
no puede cambiar a medias de base de datos a mitad de conversación.
La configuración malformada se niega a iniciar (no retrocede a la
base de datos heredada): JSON malo, un no-objeto, claves duplicadas, una URI vacía, un nombre
que no sea un segmento de ruta de URL, o MEMORA_DEFAULT_DB faltante/desconocido cuando
se listan más de una base de datos.
Par trabajado — ejecuta esto, conéctate a esto. Un listener streamable-HTTP, no
una entrada MCP command (eso generaría un hijo stdio que nunca habla MCP
en stdio). Las credenciales viven en el proceso del servidor.
MEMORA_DATABASES='{"memora":"d1://<account-id>/<memora-db-id>","ob1":"d1://<account-id>/<ob1-db-id>"}' \
MEMORA_DEFAULT_DB=memora \
CLOUDFLARE_API_TOKEN='<token>' \
MEMORA_VECTOR_SCAN_PAGE_SIZE=100 \
memora-server --transport streamable-http --host 127.0.0.1 --port 8000 --no-graph
{
"mcpServers": {
"memora": {
"type": "http",
"url": "http://127.0.0.1:8000/mcp/ob1"
}
}
}
Variante contenedor / proxy (el lanzador habitual de este host, no el comando
anterior): scripts/memora-instance.sh up myinstance inicia el mismo servidor HTTP
dentro de un contenedor y coloca scripts/memora_proxy.py en 127.0.0.1:<PORT>
(8910 para la instancia memora). La URL del workspace es entonces
http://127.0.0.1:8910/mcp/ob1. Ver Despliegue en contenedor.
Un registro puede mezclar d1://, s3:// y rutas locales; parse_backend_uri
despacha según el esquema.
memory_stats informa la base de datos enlazada. Devuelve database (el nombre
que esta sesión realmente resolvió) y database_source (path,
registry_default o unconfigured). Un nombre válido pero incorrecto en .mcp.json
es indetectable de otro modo: cada herramienta funciona, las lecturas tienen éxito y las escrituras
aterrizan silenciosamente en el almacén de otro proyecto. Llama a memory_stats y verifica database
contra el workspace que querías.
Salud de un proceso multi-base de datos: GET /health es vivacidad (sin I/O de base de datos
— la única señal en la que un supervisor puede reiniciar). GET /health/db es una superficie
de alerta (siempre HTTP 200; status es ok, degraded, unknown — sin
instantánea aún, una actualización agotó el tiempo, o evidencia más antigua que la máxima obsolescencia — o
error si el registro en sí es inutilizable). GET /health/db/{name} es la
sonda específica del workspace (200 o 503). Retirar todo el proceso porque
un almacén está degradado derriba también a los saludables.
Despliegue en contenedor
Con MEMORA_DATABASES sin definir, un proceso aún enlaza una base de datos durante su
vida útil (MEMORA_STORAGE_URI / MEMORA_DB_PATH). Esa es la forma original
de un-almacén-un-contenedor-un-puerto.
Con MEMORA_DATABASES definido, un contenedor sirve a cada workspace y
los clientes seleccionan un almacén por ruta de URL (/mcp/<name>). Ver
Enrutamiento multi-base de datos. scripts/memora-instance.sh
quiere uno de STORAGE_URI, VOLUME o MEMORA_DATABASES por archivo de instancia
(load() requiere al menos uno). Si se definen más de uno, cmd_up usa
MEMORA_DATABASES, luego STORAGE_URI, luego VOLUME.
Dockerfile construye una imagen sin credenciales; scripts/memora-instance.sh despliega una
instancia desde instances/myinstance.env (u otro archivo nombrado). El CLI de tiempo de ejecución del script es
$MEMORA_CONTAINER_BIN (predeterminado container — el CLI de Apple). Cada operación
de contenedor que el script realiza respeta esa anulación (build, up, status,
logs, down). El proceso memora_proxy.py generado fija
container list, que también es por qué existe el proxy: ese runtime reasigna
la IP del contenedor en cada inicio.
./scripts/memora-instance.sh build myinstance # build the image
./scripts/memora-instance.sh up myinstance # run the container
./scripts/memora-instance.sh proxy myinstance # render a LaunchAgent + print install commands
./scripts/memora-instance.sh status # every instance at a glance
Luego apunta el workspace a ello — toda la configuración del cliente, sin secretos en ella.
Una instancia de registro necesita el almacén en la ruta (/mcp/<name>); /mcp desnudo es
el predeterminado del registro:
{"mcpServers": {"memora": {"type": "http", "url": "http://127.0.0.1:8910/mcp/ob1"}}}
Las credenciales nunca entran en la imagen, el archivo de instancia o la configuración HTTP del workspace.
Se leen en tiempo de ejecución desde una configuración de credenciales separada
($CRED_SOURCE — en sí un .mcp.json que contiene solo el bloque mcpServers.memora.env)
y se inyectan con -e. Si el archivo de instancia no define
CRED_SOURCE, el script usa ~/.config/memora/credentials.mcp.json cuando ese
archivo existe, de lo contrario ~/repos/agentic-box/.mcp.json. Pasa todas las
variables que ese archivo define, no unas pocas seleccionadas a mano: un contenedor iniciado con solo
las claves de incrustación pierde silenciosamente la consolidación LLM de memory_absorb en lugar de
fallar ruidosamente.
Por qué existe el proxy — lee esto antes de decidir que no lo necesitas. El
runtime predeterminado (container de Apple) reasigna la IP de un contenedor en cada inicio, no solo al recrear.
Un cliente MCP lee su configuración una vez al inicio, por lo que una dirección movida no produce un
error: produce un cuelgue silencioso permanente. scripts/memora_proxy.py mantiene un
127.0.0.1:<PORT> estable frente a la dirección móvil y re-resuelve por conexión.
Dos modos de fallo que distingue, que costaron una interrupción para aprender:
- La búsqueda se ejecutó y el contenedor no está listado → realmente se fue. Rechaza.
- La búsqueda no pudo ejecutarse (tiempo de espera bajo presión de memoria del host) → nada nuevo se
sabe. Sigue sirviendo la última dirección buena conocida, limitada por
MEMORA_PROXY_STALE_GRACE(300s). Confundir los dos llevó a cada workspace fuera de línea mientras los contenedores estaban respondiendo normalmente en direcciones sin cambios.
Define MEMORA_TOOL_PROFILE por instancia (ver Perfiles de herramientas). Nota que el perfil es
por contenedor mientras que los roles son por agente: si un contenedor sirve al líder de un workspace
y a sus workers, necesita el superconjunto líder.
Variable de script en tiempo de despliegue (no una variable de entorno de memora-server — nunca llega al proceso dentro del contenedor):
| Variable | Significado |
|---|---|
MEMORA_CONTAINER_BIN | CLI que cada operación de contenedor memora-instance.sh usa (build, up, status, logs, down; predeterminado container). El proceso memora_proxy.py generado no respeta esto; fija container list. |
instances/README.md cubre los campos de configuración y launchd/README.md el proxy
supervisado. REVERT.md documenta restaurar un workspace al servidor stdio directo.
Búsqueda semántica y embeddings
Memora soporta tres backends de embeddings:
| Backend | Instalación | Calidad | Velocidad |
|---|---|---|---|
openai (predeterminado) | Incluido | Alta calidad | Latencia de API |
sentence-transformers | pip install memora[local] | Buena, funciona sin conexión | Media |
tfidf | Incluido | Coincidencia básica de palabras clave | Rápida |
Los embeddings y el LLM se configuran por separado.
| Rol | Variables |
|---|---|
| LLM (deduplicación, chat) | OPENAI_API_KEY + OPENAI_BASE_URL |
| Embeddings | MEMORA_EMBEDDING_API_KEY + MEMORA_EMBEDDING_BASE_URL (ambos o ninguno — par atómico) |
| Respaldo | Si ambos MEMORA_EMBEDDING_* están sin definir, los embeddings usan el par completo OPENAI_* |
Una división parcial (solo un MEMORA_EMBEDDING_* definido) se rechaza para que el secreto de un proveedor nunca se envíe a otro host.
Trampa — OpenRouter no tiene endpoint de embeddings. El catálogo de OpenRouter es solo chat/multimodal (sin modelos de embedding). No apuntes la ruta de embeddings a OpenRouter vía OPENAI_BASE_URL (o una URL base MEMORA). Esa combinación da 404 en cada llamada de embed; sin MEMORA_EMBEDDING_STRICT=1 Memora retrocede a TF-IDF y sigue respondiendo, por lo que el almacén se llena con bolsas de palabras clave mientras parece saludable. OpenRouter sigue siendo adecuado solo para el LLM.
Ejemplo trabajado (LLM vía OpenRouter, embeddings vía Cloudflare Workers AI):
@cf/baai/bge-m3 es de 1024 dimensiones. El token necesita permiso de Workers AI. Forma del endpoint:
https://api.cloudflare.com/client/v4/accounts/<account_id>/ai/v1
{
"env": {
"MEMORA_EMBEDDING_MODEL": "openai",
"OPENAI_API_KEY": "<openrouter-key>",
"OPENAI_BASE_URL": "https://openrouter.ai/api/v1",
"MEMORA_LLM_MODEL": "deepseek/deepseek-chat",
"MEMORA_EMBEDDING_API_KEY": "<cloudflare-api-token-with-workers-ai>",
"MEMORA_EMBEDDING_BASE_URL": "https://api.cloudflare.com/client/v4/accounts/<account_id>/ai/v1",
"OPENAI_EMBEDDING_MODEL": "@cf/baai/bge-m3",
"MEMORA_EMBEDDING_STRICT": "1"
}
}
Lo que esta corrección hace (sin exagerar): los embeddings y el LLM pueden usar proveedores diferentes; una división parcial se rechaza; el modo estricto convierte la degradación silenciosa en un fallo duro y nombrado.
Automático: Los embeddings y las referencias cruzadas se calculan automáticamente cuando memory_create, memory_update o memory_create_batch.
Reconstrucción manual requerida cuando la huella del almacén cambia — no solo MEMORA_EMBEDDING_MODEL, sino también:
- Endpoint de embeddings (
MEMORA_EMBEDDING_BASE_URL/ host) - ID real del modelo (
OPENAI_EMBEDDING_MODEL, p. ej. cambiar a@cf/baai/bge-m3) - Tipo o dimensiones del vector (bolsas TF-IDF de palabras clave vs densas de 1024-d; o 384 vs 1024)
- Almacén mixto (algunas filas densas, algunas dispersas) — la similitud coseno solo comparte claves, por lo que los tipos mixtos dan 0.0 de recuperación para filas antiguas
Forma de la huella: backend|model|repr (p. ej. openai|@cf/baai/bge-m3|dense:1024). El valor de metadatos heredado openai solo se trata como una discrepancia.
# After changing embedding model/endpoint, rebuild all embeddings
memory_rebuild_embeddings
# Then rebuild cross-references to update the knowledge graph
memory_rebuild_crossrefs
Servidor de Gráfico en Vivo
Un servidor HTTP integrado se inicia automáticamente con el servidor MCP, ofreciendo una visualización interactiva del grafo de conocimiento.
![]() Panel de Detalles | ![]() Panel de Línea de Tiempo |
Acceso local:
http://localhost:8765/graph
Acceso remoto vía SSH:
ssh -L 8765:localhost:8765 user@remote
# Then open http://localhost:8765/graph in your browser
Configuración:
{
"env": {
"MEMORA_GRAPH_PORT": "8765"
}
}
Para deshabilitar: añade "--no-graph" a los argumentos en tu configuración de MCP.
Funciones de la Interfaz del Grafo
- Panel de Detalles - Ver contenido de memoria, metadatos, etiquetas y memorias relacionadas
- Panel de Línea de Tiempo - Explorar memorias cronológicamente, haz clic para resaltar en el grafo
- Panel de Historial - Registro de acciones de todas las operaciones con entradas consecutivas agrupadas y referencias de memoria clicables (las memorias eliminadas se muestran tachadas)
- Panel de Chat - Haz preguntas sobre tus memorias usando chat LLM impulsado por RAG con respuestas en streaming y referencias
[Memory #ID]clicables - Control Deslizante de Tiempo - Filtrar memorias por rango de fechas, arrastra para explorar el historial
- Actualizaciones en Tiempo Real - El grafo, la línea de tiempo y el historial se actualizan vía SSE cuando las memorias cambian
- Filtros - Menús desplegables de etiquetas/secciones, controles de zoom
- Renderizado Mermaid - Los bloques de código se renderizan como diagramas
Colores de Nodos
- 🟣 Etiquetas - Tonos púrpura por etiqueta
- 🔴 Problemas - Rojo (abierto), Naranja (en progreso), Verde (resuelto), Gris (no se corregirá)
- 🔵 TODOs - Azul (abierto), Naranja (en progreso), Verde (completado), Rojo (bloqueado)
El tamaño del nodo refleja el número de conexiones.
Grafo en la Nube (Recomendado para D1)
Cuando usas Cloudflare D1 como tu base de datos, la visualización del grafo se aloja en Cloudflare Pages - no se necesita servidor local.
Beneficios:
- Acceso desde cualquier lugar (sin túnel SSH)
- Actualizaciones en tiempo real vía WebSocket
- Soporte multi-base de datos vía el parámetro
?db= - Acceso seguro con Cloudflare Zero Trust
Configuración:
-
Crear base de datos D1:
npx wrangler d1 create memora-graph npx wrangler d1 execute memora-graph --file=memora-graph/schema.sql -
Desplegar Pages:
cd memora-graph npx wrangler pages deploy ./public --project-name=memora-graph -
Configurar enlaces en el Panel de Cloudflare:
- Pages → memora-graph → Configuración → Enlaces
- Añadir D1:
DB_MEMORA→ tu base de datos - Añadir R2:
R2_MEMORA→ tu bucket (para imágenes)
-
Configurar MCP con el URI de D1:
{ "env": { "MEMORA_STORAGE_URI": "d1://<account-id>/<database-id>", "CLOUDFLARE_API_TOKEN": "<your-token>" } }
Acceso: https://memora-graph.pages.dev
Asegurar con Zero Trust:
- Panel de Cloudflare → Zero Trust → Acceso → Aplicaciones
- Añadir aplicación para
memora-graph.pages.dev - Crear política con correos electrónicos permitidos
- Pages → Configuración → Habilitar Política de Acceso
Consulta memora-graph/ para configuración detallada y configuración multi-base de datos.
Chat con Memorias
Haz preguntas sobre tu base de conocimiento directamente desde la interfaz del grafo. El panel de chat usa RAG (Generación Aumentada por Recuperación) para buscar memorias relevantes y transmitir respuestas del LLM con soporte de llamada a herramientas.
- Alternar vía el ícono de chat flotante en la parte inferior derecha
- Búsqueda semántica encuentra las memorias más relevantes como contexto
- Respuestas en streaming con referencias
[Memory #ID]clicables que enfocan el nodo del grafo - Llamada a herramientas — el LLM puede crear, actualizar y eliminar memorias directamente desde el chat (por ejemplo, "guarda esto como una memoria", "elimina la memoria #42", "actualiza la memoria #10 con...")
- Funciona tanto en el servidor local como en el despliegue de Cloudflare Pages
Configurar el modelo de chat:
| Backend | Variable | Predeterminado |
|---|---|---|
| Servidor local | variable de entorno CHAT_MODEL | Recurre a MEMORA_LLM_MODEL |
| Cloudflare Pages | CHAT_MODEL en wrangler.toml | deepseek/deepseek-chat |
Requiere una API compatible con OpenAI (OPENAI_API_KEY + OPENAI_BASE_URL para local, secreto OPENROUTER_API_KEY para Cloudflare). El modelo de chat debe soportar uso de herramientas (llamada de funciones).
Deduplicación con LLM
Encuentra y fusiona memorias duplicadas usando comparación semántica impulsada por IA:
# Find potential duplicates (uses cross-refs + optional LLM analysis)
memory_find_duplicates(min_similarity=0.7, max_similarity=0.95, limit=10, use_llm=True)
# Merge duplicates (append, prepend, or replace strategies)
memory_merge(source_id=123, target_id=456, merge_strategy="append")
Comparación con LLM analiza pares de memorias y devuelve:
verdict: "duplicate", "similar" o "different"confidence: puntuación de 0.0-1.0reasoning: Explicación brevesuggested_action: "merge", "keep_both" o "review"
Funciona con cualquier API de chat compatible con OpenAI (OpenAI, OpenRouter, Azure, etc.) vía OPENAI_BASE_URL. OpenRouter es adecuado para esta ruta de LLM; no proporciona embeddings — configura los embeddings por separado (consulta Búsqueda Semántica y Embeddings).
Almacenamiento de Documentos
Almacena documentos estructurados (informes de investigación, decisiones de arquitectura, autopsias) como árboles de fragmentos buscables:
# Store a markdown document — auto-parsed into typed fragments
memory_store_document(
content="# Research Report\n\n## Evidence Table\n| Claim | Confidence |\n...",
document_key="research/memora-enhancements-2026-04-08",
tags=["memora/research"]
)
# Returns: {root_id: 230, fragment_count: 100, node_map: {claim: [...], plan_item: [...], ...}}
# Retrieve the full document or specific fragment types
memory_get_document(document_key="research/memora-enhancements-2026-04-08")
memory_get_document(document_key="...", node_kinds=["claim"], content_mode="full")
# Delete a document and all its fragments
memory_delete_document(document_key="research/memora-enhancements-2026-04-08")
Cómo funciona: El analizador divide el markdown por estructura — las tablas se convierten en afirmaciones individuales, las listas numeradas en elementos de plan, las listas de URL en referencias, y las secciones de riesgo en fragmentos de riesgo. Cada fragmento es buscable de forma independiente vía memory_semantic_search mientras que el documento completo se puede recuperar como una unidad.
Tipos de fragmentos: claim, plan_item, reference, section_chunk, risk
Protecciones de integridad: Los fragmentos de documentos están protegidos contra modificaciones accidentales:
memory_deleterequiereforce=Truepara fragmentosmemory_mergese niega a fusionar fragmentosmemory_absorbexcluye fragmentos de la coincidencia de similitudmemory_find_duplicatesymemory_detect_supersessionsomiten fragmentos- La interfaz del grafo oculta fragmentos, mostrando solo el nodo raíz del documento
Herramientas de Automatización de Memorias
Herramientas estructuradas para tipos comunes de memoria:
# Create a TODO with status and priority
memory_create_todo(content="Implement feature X", status="open", priority="high", category="backend")
# Create an issue with severity
memory_create_issue(content="Bug in login flow", status="open", severity="major", component="auth")
# Create a section placeholder (hidden from graph)
memory_create_section(content="Architecture", section="docs", subsection="api")
Perspectivas de Memoria
Analiza memorias almacenadas y muestra perspectivas accionables:
# Full analysis with LLM-powered pattern detection
memory_insights(period="7d", include_llm_analysis=True)
# Quick summary without LLM (faster, no API key needed)
memory_insights(period="1m", include_llm_analysis=False)
Devuelve:
- Resumen de actividad — memorias creadas en el período, agrupadas por tipo y etiqueta
- Elementos abiertos — TODOs y problemas abiertos con detección de obsoletos (configurable vía
MEMORA_STALE_DAYS;memory_insightspredeterminado 14, interfaz del grafo predeterminado 30 — misma variable, dos consumidores) - Candidatos de consolidación — pares de memorias similares que podrían fusionarse
- Análisis con LLM — temas, áreas de enfoque, brechas de conocimiento y un resumen (requiere
OPENAI_API_KEY)
Enlace de Memorias
Gestiona relaciones entre memorias:
# Create typed edges between memories
memory_link(from_id=1, to_id=2, edge_type="implements", bidirectional=True)
# Edge types: references, implements, supersedes, extends, contradicts, related_to
# Remove links
memory_unlink(from_id=1, to_id=2)
# Boost memory importance for ranking
memory_boost(memory_id=42, boost_amount=0.5)
# Detect clusters of related memories
memory_clusters(min_cluster_size=2, min_score=0.3)
Exportación del Grafo de Conocimiento (Opcional)
Para visualización sin conexión, exporta memorias como un archivo HTML estático:
memory_export_graph(output_path="~/memories_graph.html", min_score=0.25)
Esto es opcional - el Servidor de Gráfico en Vivo proporciona la misma visualización con actualizaciones en tiempo real.
Integración con Neovim
Explora memorias directamente en Neovim con Telescope. Copia el plugin a tu configuración:
# For kickstart.nvim / lazy.nvim
cp nvim/memora.lua ~/.config/nvim/lua/kickstart/plugins/
Uso: Presiona <leader>sm para abrir el explorador de memorias con búsqueda difusa y vista previa.
Requiere: telescope.nvim, plenary.nvim y memora instalados en tu entorno de Python.

