
Cómo crear tu propio grafo de conocimiento con quartz-okf
Tengo tres corpus de conocimiento en marcha que no se parecen en nada: una tradición doctrinal, una biblioteca de terapia familiar destilada para uso clínico y el desmontaje de un estándar sectorial de arquitectura empresarial. Los tres se publican como sitio navegable con su grafo, sobre el mismo motor y sin una línea de código distinta.
El motor se llama quartz-okf, y el repositorio es abierto, con licencia MIT. Salió del sitio donde nació —una base de infraestructura que fue su primer consumidor— y hoy vive aparte. Esta es la explicación larga de qué resuelve y cómo se usa; los posts de proyectos concretos enlazan aquí para no repetirla.
Un montón de notas no es un grafo
El punto de partida lo conoce cualquiera que haya acumulado notas en markdown. A las cincuenta, el [[wikilink]] sirve. A las doscientas, no: tienes enlaces que dicen «esta nota menciona a esta otra» y nada más. Que una nota enlace a un libro no distingue si lo cita como apoyo, si lo contradice o si lo nombra de pasada. La bola de pelo que sale de ahí no contesta ninguna pregunta.
El remedio evidente es tipar las relaciones. Y ahí aparece el segundo fallo, peor porque tarda más en verse: si el vocabulario está abierto, cada nota se inventa su etiqueta. A las doscientas tienes sesenta, la mitad sinónimas, y el grafo ha pasado de no decir nada a decir sesenta cosas incompatibles.
Lo que hace falta es justo lo contrario de la libertad: un vocabulario cerrado —esto es un concepto, aquello una tesis; esta relación se escribe Cites y no hay otra forma— y un validador que rompa el build cuando alguien se sale. La restricción es el producto.
Una spec ajena en vez de un formato propio
OKF —Open Knowledge Format— es una especificación de Google Cloud, hoy en la v0.1, que representa el conocimiento como un directorio de ficheros markdown con frontmatter YAML. Eso es todo, y esa pobreza es la gracia: ni base de datos, ni servidor, ni formato binario. El corpus es lo que ves con ls.
Podía haberme inventado el formato en una tarde. No lo hice porque así el cliente acaba con un activo que no depende de mi herramienta —ficheros de texto que abrirá cualquiera dentro de diez años— y porque una spec deja sitio para un perfil encima, que es donde vive lo mío.
Lo que quartz-okf añade encima son dos cosas: un vocabulario cerrado de tipos, y relaciones tipadas escritas en la prosa de la nota en lugar de en un fichero de datos aparte. Cada nota abre con una sección # Topology.
---
type: divergence
title: Tres apofatismos incompatibles
---
# Topology
* **About**: [[autores/pseudo-dionisio-areopagita]], [[autores/maestro-eckhart]]
* **Depends on**: [[conceptos/lo-uno-apofatico]]
* **Cites**: [[libros/teologia-mistica]], [[libros/sermones-alemanes]]
## El desacuerdo
La nota sigue aqui, en prosa normal.El parser lee esa sección, resuelve los alias, deriva las inversas y escribe el grafo. Las relaciones no se infieren: se declaran. Es la decisión que sostiene todo lo demás.
Quartz fijado por SHA, y nunca dentro del repositorio
La base del sitio es Quartz v5, el generador estático de jardines digitales de Jacky Zhao: búsqueda, backlinks, migas, tema y un ecosistema de plugins de comunidad. No tengo ningún interés en reescribir eso.
Lo que sí me importa es cómo se consume. El toolkit fija Quartz por SHA de commit en harness/quartz.ref y los 44 plugins de comunidad por commit en harness/quartz.lock.json. El repositorio del corpus fija a su vez el toolkit en okf/quartz-okf.ref. Dos pines encadenados: el corpus elige versión de motor y el motor elige versión de Quartz.
Nada de eso se copia dentro del repositorio del corpus: en el build se descargan los dos tarballs a una caché local y se ensamblan ahí. Fuera de content/, el repositorio del corpus público tiene once ficheros.
Vendorizar habría sido más rápido de arrancar y una condena a plazo: si copias el generador dentro, tu corpus pasa a ser un fork de un generador de sitios y cada actualización es un merge. Con el pin, actualizar son cuarenta caracteres, y ese fichero registra además qué versión produjo qué build.
Las piezas
@zetesis/okf-core es el contrato, independiente del renderizador: validación, resolución de enlaces, parser de topología, grafo y exportador. Node 20 o superior, cero dependencias en runtime. Trae cuatro binarios —okf-check, okf-export, okf-impact y okf-diagram— que validan, exportan un bundle conforme, calculan qué documentación toca revisar desde el último head mantenido y dibujan mermaid.
Encima hay cuatro plugins de Quartz. quartz-okf es el adaptador: valida en build con la misma implementación que usa el CLI y emite static/okf-graph.json más una copia en crudo de cada nota. quartz-graph-okf dibuja el grafo pequeño junto a la nota, quartz-okf-panels monta el panel de radio de impacto y quartz-okf-explorer es el visor a pantalla completa.
El visor es una página autocontenida sobre un <canvas> 2D con d3 servido desde el propio sitio, no desde un CDN. No consulta ninguna base de datos porque no hay ninguna: hace fetch del okf-graph.json que el plugin escribió durante el build. Que el grafo sea un artefacto y no un servicio tiene una consecuencia práctica: no puede quedar desfasado del contenido, porque se regenera entero en cada despliegue.
Lo que hay que escribir para arrancar
Un consumidor aporta tres cosas: su content/ con las notas, un okf.config.mjs y un script de build que descarga el toolkit por SHA. El vocabulario vive entero en el config. Extracto del corpus público, recortado pero literal.
export const profile = {
// Set cerrado. Los cuatro primeros son el armazon de cualquier corpus de lectura;
// los tres siguientes son propios de este: una tradicion doctrinal no se describe
// solo con conceptos, sino con las corrientes que los sostienen, las divergencias
// que el canon no declara y los frentes que quedan abiertos.
types: ["book", "author", "concept", "claim", "current", "divergence", "front", "report"],
edgeLabels: [
"Part of", "Contains", "Authored by", "Cites",
"About", "Depends on", "Opposes", "Continues",
],
// El motor deriva el espejo, asi que cada relacion se declara una sola vez.
inverseLabels: { "Part of": "Contains", Contains: "Part of", Opposes: "Opposes" },
}
export const explorer = {
typeLabels: { book: "obra", author: "autor", concept: "concepto", claim: "tesis" },
typeColors: { book: "#8a8a8a", author: "#b58b6a", concept: "#4c7ecf", claim: "#c2544d" },
// Anillos por tipo: los frentes y las divergencias al centro, porque son lo que el
// corpus tiene de vivo; las obras y sus autores fuera, que son el sustrato.
layout: {
charge: -45,
link: {
"*": { distance: 28, strength: 0.12 },
Cites: { distance: 70, strength: 0.03 },
"Authored by": { distance: 30, strength: 0.25 },
},
radial: {
strength: 0.9,
byType: { front: 0, divergence: 0.16, claim: 0.3, concept: 0.44, book: 0.74, author: 0.98 },
},
},
modes: [
{ id: "full", label: "Grafo completo", edges: "*" },
{
id: "fundamentacion",
label: "Fundamentacion",
desc: "<b>Cuantas obras sostienen cada nota.</b> Rojo si se apoya en poco, verde si esta bien respaldada.",
edges: ["Cites"],
colorBy: {
countEdge: "Cites",
scale: [
{ max: 1, color: "#c2544d", label: "1 obra o ninguna" },
{ max: 3, color: "#e0a03c", label: "2 o 3 obras" },
{ max: 999, color: "#3fa34d", label: "4 o mas" },
],
},
},
],
}Hay tres cosas que leer con calma ahí.
- El set de tipos y de etiquetas es cerrado. Ocho y ocho en este corpus. Lo que se salga lo reporta el validador con nombre y fichero.
- Cada relación se declara una sola vez.
inverseLabelsle dice al motor qué espejo derivar. De las 902 relaciones del grafo público, 808 están escritas a mano y 94 las deriva el motor. - Los modos son datos, no código. Un modo es una pregunta al corpus: qué aristas conserva, por qué propiedad colorea, qué determina el tamaño del nodo. El de arriba pinta en rojo las notas que se apoyan en una obra o en ninguna. Una pregunta nueva son ocho líneas.
Son datos: el motor no sabe nada de HERM, dibuja las preguntas que se declaren aquí.
La línea de comandos hace lo mismo que el build, sin build.
$ npm test
# tests 31 # pass 31 # fail 0
$ node core/bin/okf-check.js .
[okf] checked 218 markdown files: 0 error(s), 6 warning(s)
$ node core/bin/okf-check.js content/
[okf] checked 218 markdown files: 165 error(s), 310 warning(s)
La última línea no es un fallo: es el mecanismo. El mismo corpus, sin su okf.config.mjs al lado, se valida contra el perfil de referencia del toolkit, donde no existe ni divergence ni Cites. Sin el overlay del consumidor, el motor no sabe de qué le hablas.
Tres corpus que no se parecen en nada
El público es el corpus del perennialismo hispano, en Zetesis-Labs: 217 notas y 902 relaciones sobre una tradición doctrinal, cero relaciones sin resolver. Ocho tipos, ocho etiquetas, cuatro modos. Es el de los ejemplos de arriba.
El segundo es una wiki profesional privada: 134 notas de conocimiento destiladas de una biblioteca de terapia familiar, para un equipo clínico. Nueve tipos, siete etiquetas, y un gate de CI que no publica si el grafo baja de 300 nodos o si falta alguno de los tres ficheros del visor. El día que un generador se rompe en silencio, el sitio se publica igual y con menos contenido.
El tercero es el análisis de un estándar sectorial de arquitectura empresarial, para Singular Solving: 427 nodos y 571 aristas, el catálogo entero del estándar como jerarquía más las notas que lo citan. Dieciséis tipos, porque el estándar trae su propia taxonomía. Lo conté completo en el post sobre desmontar un marco de referencia.
Los tres descargan el mismo tarball, fijado por el mismo SHA. Lo único que cambia entre ellos es el config.
Los validadores son parte del diseño
Cada regla tiene un nivel por defecto que el consumidor puede subir o bajar. Son error el frontmatter que no parsea, la nota sin type y el tipo fuera del set cerrado. Son aviso la etiqueta desconocida, la nota sin título y —mi favorita— la relación declarada por los dos extremos, el error de quien no se fía del espejo derivado. Una viene apagada a propósito: apuntar a una nota que aún no existe es una forma legítima de anotar trabajo pendiente.
Cuando falla en build, falla del todo. El plugin corre en modo estricto por defecto y lanza [okf] build failed: N OKF conformance error(s); el sitio no se publica. Fuera del build, okf-check imprime una línea por violación y sale con código 1.
El motivo por el que insisto tanto lo aprendí perdiendo. La convención escribe # Topology y el cuerpo de la nota en ##, y por contención de Markdown un # contiene a los ## que le siguen: el extractor se tragaba la nota entera y cualquier viñeta de la prosa con la forma * **Algo**: [[enlace]] se convertía en relación tipada. En la wiki clínica eso inyectó 154 aristas que nadie había declarado, con 68 etiquetas inexistentes, sobre un grafo de 1.871.
Lo peor es que se veía plausible. Más denso, incluso. En un grafo de conocimiento, un fallo de parser es indistinguible de una alucinación de un modelo: nadie lo detecta mirando el dibujo. Lo que lo delató fue que las etiquetas no estaban en la lista.
Qué está estable y qué no
El contrato del núcleo lo doy por estable: el formato okf-graph/v1, las reglas y la derivación de inversas. Hay tres corpus publicados encima y 31 tests de Node que cubren el parser de topología, la resolución de enlaces, el grafo, el exportador y las reglas. Pasan.
Lo demás no lo vendo como maduro, porque no lo es.
- No hay CI en el repositorio del toolkit. Los tests pasan, pero los ejecuto yo a mano. Los consumidores sí tienen CI; el motor, no. Es lo primero de la lista.
- No está publicado.
private: true, versión0.1.0, sin tags ni releases. Se consume por tarball de GitHub fijado por SHA, que funciona pero no es un paquete. - La licencia ya está. MIT, declarada en el
package.jsony con su ficheroLICENSEen la raíz. - El perfil de referencia arrastra su origen. Los catorce tipos que trae por defecto son de infraestructura, y ningún consumidor los usa tal cual.
Treinta y ocho commits en catorce días, un solo autor. No es un producto: es un activo interno reutilizable, publicado en abierto, con cuatro consumidores dentro de casa: los tres corpus publicados y la base de infraestructura donde nació el toolkit.
Por qué esto le importa a una empresa
El conocimiento de una organización suele vivir en un wiki de terceros. Funciona hasta el día que cambias de herramienta y descubres que lo que tienes no es conocimiento, es un export. Aquí el corpus son ficheros markdown en git, en un formato con especificación pública, y la herramienta que los publica es código abierto, fijada por SHA. Si mañana desaparece el toolkit, el conocimiento se sigue leyendo con cat.
Lo segundo se nota más en el día a día. Obligar a que cada relación se declare dentro de un vocabulario cerrado convierte la documentación en algo auditable: puedes preguntarle al grafo qué notas se apoyan en una sola fuente, qué se rompe si retiras una pieza, dónde el corpus afirma más de lo que cita. Y las respuestas salen de lo que alguien escribió a propósito, no de una inferencia.
Preguntas frecuentes
¿Puedo usarlo hoy en mi repositorio?+
Técnicamente sí: el repositorio es público y tiene licencia, se consume por SHA y el núcleo no tiene dependencias en runtime. Pero no está en npm y no tiene CI propia. Si lo pruebas ahora, hazlo sabiendo eso.
¿Hace falta OKF para tener un grafo de conocimiento?+
No. Hace falta un vocabulario cerrado y algo que lo haga cumplir en cada build. Lo que aporta apoyarse en una especificación ajena es que el formato no es mío: tu corpus son ficheros markdown que se leen sin mi herramienta y sin mi permiso.
¿El grafo lo construye un modelo de lenguaje?+
No hay una línea de IA en el motor. Las relaciones no se extraen: se declaran a mano en la prosa de la nota, y el validador rechaza las que se salen del vocabulario. Un modelo puede ayudar a escribir las notas; la topología es determinista y se audita leyendo el markdown.
¿Y si mi documentación vive en Confluence o en Notion?+
La exportación a markdown es trabajo, pero es trabajo conocido. La parte que no se automatiza viene después: decidir qué tipos y qué relaciones describen tu dominio. Ahí es donde se gana o se pierde el proyecto, y por eso el motor no trae vocabulario.
Contrato independiente del renderizador para grafos de conocimiento: validación, resolución y topología tipada sobre Quartz.
JavaScriptMás del laboratorio

Que el grafo del conocimiento lo construya el que sabe del dominio

Del estándar al grafo y al proyecto

Huygens: un grafo en SurrealDB como memoria para agentes

Coder: entornos de desarrollo remotos con devcontainers para todo el equipo

Postiz: programar publicaciones en redes sociales desde tu propio servidor

Un Harbor propio para las imágenes y los charts del clúster
¿Tienes documentación que nadie consulta porque no se le puede preguntar nada?
Una biblioteca interna, un archivo de proyectos, una norma con cientos de controles. Si de ahí tienen que salir decisiones, un buscador no basta. Cuéntame qué corpus tienes y te digo qué vocabulario le pondría.