Caso de estudio · synapse

synapse: memoria que se puede demostrar

Un grafo de conocimiento simbólico en Rust para agentes: notas Markdown sincronizadas con RDF, razonamiento OWL-RL y la procedencia de cada hecho. Empezó neurosimbólico y acabó sin vectores.

Rust · Oxigraph + RocksDB · RDF y SPARQL · OWL-RL · MCP · MCP AppsGitHub ↗

El problema

Cuando un agente «recuerda» algo con búsqueda vectorial, recupera lo que se parece, no lo que es. Para charlar basta; para decisiones técnicas, no. Si pregunto qué módulos dependen del de pagos, necesito la lista exacta, no los cinco fragmentos más parecidos. Y necesito saber de dónde salió cada dato y cuándo lo aprendió el agente.

Quería una memoria que respondiera como una base de datos y razonara como una ontología, sin perder el formato que leen las personas: Markdown.

Decisiones

Hechos explícitos. Todo lo que el agente sabe es una tripleta RDF en Oxigraph, persistida en RocksDB. Las consultas son SPARQL: la misma pregunta devuelve siempre la misma respuesta.

Una entidad, un nombre. El id de cada entidad es su ruta: people/bob.md es people/bob, salvo que el frontmatter declare un uri:. Así el enlace [[people/bob]], el propio fichero y lo que un agente afirme sobre people/bob son el mismo nodo, y no tres que hay que reconciliar después.

Markdown y grafo, en los dos sentidos. Una carpeta de notas con frontmatter YAML y enlaces [[...]] se convierte en tripletas. Lo que el motor deduce vuelve al fichero, en una sección de enlaces inversos, así que la persona ve en su editor lo que el agente ha inferido. Synapse solo toca esa sección: el frontmatter es de la persona. Y la ignora al leer, para que lo inferido no vuelva a entrar como si alguien lo hubiera escrito.

Procedencia en cada hecho. Cada origen tiene su propio grafo con nombre: cada fichero, cada ontología y cada llamada de un agente. Su procedencia, en PROV-O, se guarda aparte, en urn:synapse:meta. Ante cualquier respuesta se puede preguntar de dónde viene.

Reindexar es reemplazar, no acumular. Como cada fichero es un grafo, reindexarlo sustituye lo que decía: si una nota deja de afirmar algo, el grafo deja de saberlo, y si se borra, sus hechos se van con ella. Los ficheros sin cambios se saltan comparando el hash del contenido, calculado sin espacios finales para que una línea en blanco no cuente como cambio.

Razonamiento por punto fijo. Un razonador propio aplica RDFS (subclases y subpropiedades) y las reglas OWL-RL de propiedades transitivas, simétricas e inversas hasta que no aparece nada nuevo. Lo deducido es tan auditable como lo escrito.

Escenarios. Las ontologías y los datos de partida se empaquetan por dominio: un núcleo con Schema.org, PROV-O, SKOS y FOAF, un asistente de investigación y uno para el propio kthulu-go. El agente arranca con un vocabulario, no con un grafo vacío.

Herramientas con intención, no SPARQL crudo. En la última versión, el agente pide una ficha de una entidad, el impacto de cambiar una dependencia o la procedencia de un dato, y el servidor traduce a SPARQL. La consulta libre queda para administrar el grafo, ve todos los grafos por defecto, y los ids se validan para que nadie inyecte SPARQL a través de ellos.

El agente también escribe, y puede retractarse. assert_facts guarda hechos con su fuente, el motivo y el tipo de dato. retract_facts retira una aserción entera o hechos sueltos y registra quién, cuándo y por qué. Nunca toca lo que viene de un fichero: lo escrito por una persona solo cambia editando la nota.

Compatible con OKF. El formato de las notas admite el de okf-mcp: type, tags, enlaces tipados [[rel:destino]] y listas en el YAML. Basta apuntar el indexador a un clon de su espejo en GitHub para que los conceptos y sus enlaces casen.

Qué hay construido

  • Un binario en Rust que sirve MCP por stdio o por HTTP con SSE. En el primer arranque carga las ontologías del escenario núcleo, 280 tripletas, y en los siguientes se las salta porque no han cambiado.
  • Herramientas MCP para leer una entidad como ficha, recorrer su vecindario, medir el impacto de una dependencia, consultar procedencia, sincronizar especificaciones, indexar una carpeta de Markdown y afirmar o retirar hechos.
  • Vistas MCP Apps: un panel del motor y un subgrafo dibujado con D3. Por ahora son plantillas; aún no reciben los datos del host en vivo.
  • Un resumen en Markdown que se regenera en cada ingesta, para que el agente empiece la sesión leyendo el estado del grafo.
  • 22 tests, unitarios y de punta a punta, y un despliegue limpio: sin los restos de la época gRPC ni nueve dependencias que nadie usaba, con un Dockerfile que ya no instala onnxruntime ni protobuf.

Lo que aprendí

Synapse nació como capa de memoria neurosimbólica, con vectores, un SDK en Python y gRPC. En dos meses quité todo eso: el almacén vectorial acumulaba arreglos de cuelgues, de persistencia y de arranque lento, y lo que de verdad usaban los agentes era el grafo. Quitar fue la mejor decisión del proyecto.

Lo que no resolvió es encontrar algo cuando no sabes cómo se llama. Por eso okf-mcp recupera la búsqueda semántica, pero como índice para llegar al grafo, no como sustituto de la memoria. Ahora que leen el mismo formato, los dos se complementan.

La segunda lección vino al darle escritura al agente: lo difícil no era el motor, era el diseño de encima. Con identidad por ruta y un grafo por origen, afirmar, corregir y olvidar dejaron de ser casos especiales. Oxigraph sigue siendo el motor; lo que cambió fue cómo se reparten los hechos.

Siguiente casovord: un juez que el agente no puede convencer →