DDR-004 — Almacenamiento portable (SQLite como backend por defecto)¶
Versión: 0.1.0 | Fecha: 2026-05-09 | Estado: Aceptado (merge 2026-05-10, PR #5)
Contexto¶
A través de DDR-001 → DDR-003, Engrama consolidó una arquitectura limpia
de cuatro capas: herramientas → skills → engine → protocolos → backends.
Los protocolos existían (GraphStore, VectorStore, EmbeddingProvider)
pero solo se distribuía una implementación de cada uno: la pila Neo4j.
Eso significaba que todo camino de "probar Engrama" pasaba por:
- Instalar Docker Desktop.
- Descargar una imagen de Neo4j de ~500 MB.
- Crear credenciales, rellenar
.env. - Ejecutar
docker compose up, esperar a que la JVM arranque. - Ejecutar
engrama initpara aplicar las restricciones de Cypher.
Para alguien evaluando un framework de memoria para agentes en un portátil, eso es un prerrequisito con múltiples pasos. Para alguien que distribuye una CLI o una biblioteca que embebe Engrama como capa de memoria, obligar a los usuarios a tener Docker es inviable.
En paralelo, apareció un problema más amplio: la capa EmbeddingProvider
solo tenía una implementación en producción (Ollama). Quien quisiera usar
OpenAI, Jina, LM Studio, vLLM o llama.cpp tenía que escribir un nuevo
proveedor, aunque todos esos servicios ya hablan el mismo protocolo HTTP
(la API de embeddings de OpenAI).
Decisión¶
Hacer que Engrama sea instalable y útil con cero servicios externos, distribuyendo un backend basado en SQLite como valor por defecto y consolidando los proveedores de embedding detrás de un único cliente compatible con OpenAI.
Concretamente¶
- Backend por defecto = SQLite + sqlite-vec.
GRAPH_BACKENDpor defecto essqlite(antes eraneo4j).- El almacenamiento reside en
~/.engrama/engrama.db(modificable víaENGRAMA_DB_PATH). -
Los datos del grafo usan las tablas relacionales de SQLite; la búsqueda de texto completo usa FTS5; la búsqueda vectorial usa la tabla virtual
vec0de la extensiónsqlite-vec. Los tres viven en el mismo archivo. -
Neo4j pasa a ser un extra opcional.
- Se instala con
uv sync --extra neo4j(opip install engrama[neo4j]cuando Engrama esté en PyPI). - El driver de
neo4jdeja de ser una dependencia base. -
El comportamiento no cambia cuando
GRAPH_BACKEND=neo4jy las credenciales están en.env. -
Proveedor único de embeddings compatible con OpenAI.
engrama/embeddings/openai_compat.pycubre OpenAI, Ollama (/v1/embeddings), LM Studio, vLLM, llama.cpp, Jina y cualquier futuro servicio que hable el mismo formato.-
El antiguo
OllamaProviderse mantiene como wrapper de conveniencia para usuarios que prefieran las variables de entorno anteriores. -
La factoría unifica el cableado en toda la pila.
engrama/backends/__init__.pyexponecreate_stores()ycreate_async_stores().- La CLI, el SDK y el servidor MCP despachan todos a través de la factoría.
-
Cambiar de backend es un cambio de una sola variable.
-
El modelo de datos es compartido.
- Mismas etiquetas, mismas relaciones, misma clasificación facetada.
- SQLite codifica las etiquetas en una columna
labely las relaciones en una tablaedges; Neo4j usa etiquetas de nodo y aristas nativas. - Las suites de tests de contrato (
tests/contracts/) parametrizan sobre ambos backends y sobre almacenes síncronos y asíncronos, de modo que el contrato a nivel de interfaz se cumple.
Consecuencias¶
Positivas¶
- El tiempo de onboarding baja a segundos.
git clone+uv sync→uv run engrama verify→uv run engrama search foo. Sin Docker, sin JVM. (Engrama aún no está en PyPI; una vez publicado, el camino se reduce apip install engrama && engrama verify.) - La CI / los tests funcionan en cualquier sitio. No hay servicio
que arrancar. La suite completa de tests de SQLite (~76 tests) pasa
contra un checkout vacío sin
.env. - El uso embebido se vuelve viable. Engrama puede distribuirse dentro de otra CLI, una aplicación de escritorio o un runtime en el edge sin arrastrar Docker a la cadena de dependencias.
- Más opciones de embedder gratis. El cliente compatible con OpenAI desbloquea LM Studio, vLLM, llama.cpp, Jina, OpenAI propiamente dicho y Ollama a través del mismo código.
- La historia de portabilidad del vault se refuerza. Combinado con
DDR-002 (cada relación se persiste en el frontmatter del vault), una
instalación limpia de SQLite apuntando a un vault de Obsidian existente
reconstruye el grafo completo ejecutando
engrama_sync_vault.
Negativas¶
- Techo de escritor único en SQLite. El modo WAL gestiona bien los lectores concurrentes, pero solo un proceso puede escribir a la vez. Las configuraciones multi-agente deberían elegir Neo4j.
- Sin Cypher ad-hoc en SQLite. La detección de patrones en
reflectrequirió traducir cada consulta Cypher a SQL; los puntos de entradarun_pattern/run_cypherlanzanNotImplementedErroren SQLite. - La búsqueda vectorial escala de forma diferente.
sqlite-veces fuerza bruta en esta fase; funciona cómodamente hasta ~100k vectores. Por encima de eso, el índice HNSW de Neo4j es la herramienta adecuada. - Dos backends que mantener. La suite de contrato es la mitigación: cualquier divergencia entre backends se detecta en CI. Tres errores que se colaron antes del merge de pruebas están ahora permanentemente protegidos por los tests parametrizados.
Riesgos (mitigados durante la implementación)¶
El PR que incorporó este cambio encontró y corrigió tres errores que merece la pena registrar, tanto como historial como para servir de barreras de protección en el trabajo futuro:
- Deriva del contrato del almacén asíncrono.
SqliteAsyncStorereenviaba los resultados síncronos sin modificar vía__getattr__, lo que filtraba la forma legacy[{"n": ...}]al servidor MCP (que esperaba la forma enriquecida{"node": ..., "created": ...}que devuelveNeo4jAsyncStore). Corrección: delegación explícita método a método con traducción de forma;tests/contracts/test_async_graphstore_contract.pyparametriza sobre ambos backends asíncronos. - Reflect sobreescribía Insights aprobados.
engrama_reflectllamaba amerge_nodeconstatus="pending"en cada patrón detectado, deshaciendo silenciosamente las aprobaciones del usuario. Corrección: un métodoget_approved_titlesen cada capa del almacén más un filtrodismissed | approveden la herramienta reflect. - La búsqueda descartaba el enriquecimiento en resultados puramente
semánticos. El puntuador híbrido solo copiaba
summary/tagsde los resultados de texto completo; los nodos clasificados únicamente por similitud vectorial aparecían vacíos. Corrección:search_similarahora proyectasummary,tags,confidenceyupdated_aten ambos backends, y el puntuador los copia también en el camino vectorial.
Sustituye¶
Este DDR sustituye la decisión implícita en DDR-003 de que Neo4j era el único backend de producción. La capa de protocolos descrita en la Fase A de DDR-003 no cambia — DDR-004 es lo que convierte una implementación no-Neo4j en un miembro de primera clase de esa capa.
Seguimientos abiertos (no bloqueantes)¶
- Saneamiento de consultas FTS5 en SQLite. El tokenizador por
defecto trata
-como un operador, por lo que consultas comoengrama-mcp-serverno pasan por la ruta de texto completo. Envolver la consulta en"…"o escapar los guiones cerraría la brecha. La forma de los resultados es correcta; solo varía el ranking. Se gestiona por separado de este DDR. - Herramienta de exportación/importación de primera clase. La
migración entre backends hoy es un script manual del SDK. Un comando
engrama export/engrama importharía simétrica la historia de migración. - Matriz de embedders en el README. La lista de proveedores (OpenAI / Ollama / LM Studio / vLLM / llama.cpp / Jina) merece un ejemplo práctico para cada uno.
Referencias¶
- PR #5 —
feat: portable storage — SQLite + sqlite-vec default backend docs/portable-storage-spec.md— especificación previa a la implementación (local, en gitignore).- backends.md — guía de decisión pública para recién llegados.
- architecture.md — diagrama de capas actualizado.