Back to blog

Flowgraph: un motor de flujos construido spec-driven

LaboratorioFlowgraphSpec-drivenTypeScript

Llevo años montando sistemas de encuestas dinámicas. El último fue la Survey App de Naiz Fit: offline-first, Kotlin Multiplatform, los desvíos complejos en el cliente y cada campaña provisionada como un único JSON. Funcionó bien. Y aun así, su post-mortem dejó sobre la mesa varias decisiones que, con perspectiva, eran deuda.

Flowgraph es la reescritura pensada desde cero. El sitio donde estoy comprobando hasta dónde llega construir un motor de flujos trabajando spec-driven, con un agente implementando contra especificación. Monorepo TypeScript con tres paquetes —flow-core, flow-session, flow-react— y una demo navegable. Todo lo que cuento aquí está en el repo: en sus specs, en su constitución y en sus tests.

El diseño viene de un post-mortem

La spec del motor lo dice sin adornos: el diseño deriva del post-mortem de un motor de encuestas de generación anterior. La lección central era que los defectos del grafo eran silenciosos en ejecución. Una encuesta mal definida no fallaba al publicarse; fallaba en casa de un tester, sin cobertura y sin nadie mirando. Añade un estado mutable imposible de auditar y la lógica pegada a la plataforma, y tienes la lista de heridas.

De ahí salen las tres apuestas:

  • El flujo es un grafo serializable, no un árbol con ramas duplicadas. Las páginas compartidas se definen una sola vez y los caminos reconvergen.
  • El estado es un log de eventos, no un snapshot. Cinco tipos en v1 —SESSION_STARTED, ANSWERED, ADVANCED, WENT_BACK, SESSION_FINISHED—, cada evento con su procedencia (humano, agente o importación) y el hash SHA-256 del esquema grabado en el arranque. El estado se reconstruye con replay: no existe otra vía de hidratación.
  • Toda la lógica vive en un core puro: funciones totales (schema, estado, comando) → Result, sin DOM, sin reloj, sin aleatoriedad, sin IO.

Semántica cerrada por especificación

Dos decisiones separan este motor de todo lo que había montado antes.

La primera es la lógica de tres valores (Kleene): si una guarda no puede evaluarse con los datos disponibles, el resultado es unknown y la arista no se dispara. El sistema falla pidiendo más información, nunca fabricando una conclusión. La clase de bug «concluir sin datos» —de manual en el motor anterior— aquí no se puede reproducir, y hay un test que lo demuestra.

La segunda es la verdad activa: una respuesta solo cuenta si su página está en el camino actual. Si avanzas por la rama A, vuelves atrás y eliges la B, tus respuestas de A quedan en el log pero dejan de contar; si regresas, reaparecen precumplimentadas al reentrar por su página. El log guarda todos los hechos; la interpretación se deriva.

La constitución y el enforcement mecánico

El proyecto se rige por una constitución de 12 reglas —versionada, como el software— y siete features numeradas en specs/: el motor, los subflows repetibles, el adapter MCP, el adapter React, el LLM authoring loop y las demos. Código que contradice una spec, pierde.

Lo relevante es que el cumplimiento no depende de disciplina:

  • flow-core compila con "lib": ["ES2022"]: los tipos del DOM directamente no existen ahí dentro.
  • ESLint prohíbe Date, Math.random, crypto o fetch en el core.
  • dependency-cruiser vigila las fronteras físicas entre paquetes: un import ilegal no compila.

flow-session, la única pieza con mutación permitida, no llega a 100 líneas. Los adapters —React hoy, MCP mañana— son suscriptores: no contienen lógica de negocio.

Qué está construido ya

El núcleo está implementado y verificado: decide/apply/replay, check estructural, probe de exploración acotada y goldens con cobertura de aristas medida por el propio motor —no declarada por quien escribe el test—. Los números de la última validación:

  • 198 tests en verde y cobertura del 100 % —sentencias, ramas, funciones y líneas— en core y session.
  • Adapter React con useSyncExternalStore, renderers y persistencia en navegador.
  • Una demo navegable: una encuesta ficticia para pacientes de psicología —tres ramas de partida: sueño, estrés, un cambio importante— elegida porque ramificación, visibilidad condicional, retroceso, reconvergencia, restauración y cierre inmutable se entienden bien en ese contexto. 14 tests de Playwright en Chromium de escritorio y móvil, cero violaciones de accesibilidad serias o críticas, 97 KiB de JS comprimido.

Los goldens de la demo recorren el 100 % de las transiciones del grafo: 14 de 14.

Qué se está validando

Tres piezas tienen spec pero aún no código, y son las interesantes:

  • Adapter MCP (spec 003): el motor expuesto como herramientas para agentes. La inversión que le da sentido: normalmente las herramientas son tontas y el agente carga con el protocolo; aquí el motor es el guardarraíl del agente —no puede saltarse una pregunta obligatoria ni inventarse una rama, porque el protocolo vive en el grafo y decide rechaza cualquier otra cosa—.
  • LLM authoring loop (spec 005): un LLM que escriba los flujos, con la escalera de validación como bucle de feedback:
generate → check() → probe() → regenerate → goldens

El humano revisa comportamiento, no cableado. Y el anti-humo viene diseñado: la cobertura la mide el motor, así que un LLM no puede falsearla.

  • Subflows repetibles (v1.1): el formato de evento ya reserva su hueco desde v1.

Lo que está por demostrar

La horizontalidad es una hipótesis, no un resultado. El mismo núcleo debería valer para retail, logística, gobernanza o protocolos clínicos; de momento lo que hay son fixtures muy verificados. Tampoco sé todavía cómo se empaquetará el authoring —¿CLI, herramientas MCP, ambos?— ni dónde se registrará un waiver humano de cobertura. Son preguntas abiertas de la spec, y prefiero dejarlas abiertas antes que cerrarlas con humo.

Es la misma forma de trabajar que aplico en consultoría de desarrollo agéntico: la especificación manda, el agente implementa contra ella y los tests cierran el trato. La diferencia es que este experimento es mío y va a crecer a la vista. Cuando aterrice el adapter MCP y el authoring loop empiece a escribir flujos de verdad, lo contaré aquí.