Motor de workflows event-driven basado en grafos acíclicos dirigidos (DAG), con ejecución paralela asíncrona, expresiones JSONPath para payloads dinámicos y Time-Travel Debugging: recorré, auditá y reproducí el estado histórico de cualquier ejecución, nodo por nodo.
Stack: React Flow · FastAPI · PostgreSQL · asyncio
La demo pública y su deploy (Dokku en un VPS propio) fueron dados de baja: el proyecto ya no está
desplegado en ningún lado. Se corre completo en local, con la base y los workflows de ejemplo,
con un solo comando (ver Cómo correr). El CI (.github/workflows/ci.yml) corre
los tests de API y web y el build del front.
| Time-Travel Debugger | Editor de DAG |
|---|---|
![]() |
![]() |
| Workflows Hub (oscuro) | Workflows Hub (claro) |
|---|---|
![]() |
![]() |
Las herramientas de automatización (Zapier, n8n, Airflow) ejecutan grafos de tareas, pero depurar por qué una corrida produjo cierto resultado suele ser opaco. ChronoFlow trata cada ejecución como una secuencia de snapshots inmutables: podés rebobinar la corrida y ver, en cada instante, qué nodos corrieron, en qué orden (incluido el paralelismo) y con qué payload de entrada/salida. Es un "debugger con viaje en el tiempo" para workflows.
No es un CRUD con formularios. El valor está en tres piezas de ingeniería real:
- Paralelismo real, no simulado — el scheduler corre por ready-set (no por niveles): dos
ramas independientes con
delay(3s)ydelay(1s)terminan en ~3s, no 4s. Comprobable en la demo con un cronómetro. - Time-Travel Debugging sobre un log
ExecutionEventappend-only — el scrubber no re-ejecuta nada, reconstruye el estado del DAG en cualquier instante a partir de snapshots inmutables. La función que lo hace es pura y está testeada. - Seguridad tratada como feature, no como afterthought: evaluador de condiciones propio
(nunca
eval), autorización por recurso conowner_id(un IDOR real se encontró y se cerró, documentado sin vueltas), guard anti-SSRF en el nodohttp, rate limiting. VerARCHITECTURE.md§7.
- Editor visual de DAG (React Flow): arrastrá nodos, conectá edges, configurá cada paso. Undo/redo, copy/paste, import/export JSON, auto-arrange.
- Ejecución paralela asíncrona: scheduler por ready-set — ramas independientes corren a la vez (dos
delay(3s)ydelay(1s)en paralelo ⇒ ~3s, no 4s). - Time-Travel Debugging: timeline scrubber paso a paso sobre
ExecutionEventappend-only. - Replay: reproducí una corrida idéntica desde su payload de disparo.
- Live: seguimiento en vivo de la corrida por WebSocket.
- Nodos:
start · transform · http · delay · branch · end. - Expresiones JSONPath para mapear payloads entre nodos + plantillas en URLs/bodies.
- Branches condicionales con evaluador propio y seguro (sin
eval). - Auth + multi-tenant: registro/login con JWT en cookie httpOnly; cada cuenta ve solo sus propios workflows/runs (autorización por recurso, no solo gate de UI).
- SSRF guard + rate limiting: el nodo
httpbloquea IPs privadas/loopback/metadata de cloud; login/registro/runs están rate-limiteados. - UI "Conductor OS": estética Swiss Minimalist / consola industrial, con tema claro/oscuro (toggle persistido, respeta
prefers-color-scheme) e íconos SVG (lucide-react).
165 tests (118 backend + 47 frontend) · PRs mergeadas con revisión propia (historial real, no un solo commit gigante) · CI en GitHub Actions · 0 desalineaciones en la auditoría de contrato front↔back.
Monorepo de 3 componentes. El detalle —modelo de dominio, contrato de API, algoritmo del
scheduler y decisiones técnicas— está en ARCHITECTURE.md.
flowchart LR
subgraph Client["apps/web — React + Vite"]
Editor["Editor (React Flow)"]
Debugger["Time-Travel Debugger"]
end
subgraph Server["apps/api — FastAPI"]
REST["REST routes\n/workflows /runs /auth"]
WS["WebSocket\n/ws/runs/:id"]
Engine["Execution Engine\nasyncio ready-set scheduler"]
end
DB[("PostgreSQL\nworkflows · runs · execution_events")]
HTTP[["Servicio externo\n(nodo http, SSRF-guarded)"]]
Editor -- "POST /workflows, /run" --> REST
Debugger -- "eventos en vivo" --> WS
REST -- "asyncio.Task" --> Engine
WS -- "push por ExecutionEvent" --> Engine
Engine -- "SQLAlchemy async" --> DB
Engine -- "httpx (nodo http)" --> HTTP
REST -- "SQLAlchemy async" --> DB
chronoflow/
├── apps/
│ ├── web/ # React + Vite + TS + React Flow (UI: editor + debugger)
│ └── api/ # FastAPI + SQLAlchemy 2.x async (engine + REST + WS)
├── docker-compose.yml # db + api + web (todo local)
├── ARCHITECTURE.md # contrato central
└── docs/ # capturas, diagramas
docker compose up --build
# web → http://127.0.0.1:8080
# api → http://127.0.0.1:8000/docs (Swagger)No hace falta .env. Postgres no se publica al host (solo la API lo alcanza por la red interna).
Abrí la web en 127.0.0.1 (no localhost): la cookie de sesión es SameSite=Lax y el front
llama a la API en 127.0.0.1:8000.
# Backend
cd apps/api
python -m venv .venv && .venv\Scripts\activate # Unix: source .venv/bin/activate
pip install -r requirements.txt
alembic upgrade head
uvicorn app.main:app --reload # http://localhost:8000/docs
# Frontend (otra terminal)
cd apps/web
npm install
npm run dev # http://localhost:5173Con el stack levantado, abrí http://127.0.0.1:8080 (o :5173 en modo dev) y registrate con cualquier email — no hay verificación, es una demo. Al crear la
cuenta el backend siembra 3 workflows de ejemplo automáticamente. No hace falta armar nada
para ver las features clave:
1. Paralelismo real — abrí "Parallel Delays Demo" → Run con payload {}.
Dos delay (3s y 1s) corren a la vez: la corrida termina en ~3s, no 4s. En /runs/:id movés
el scrubber y ves ambos nodos arrancar en el mismo instante.
2. Branch + JSONPath + HTTP — abrí "Branch + Transform + HTTP" (condición $.trigger.amount > 100):
| Payload de disparo | Qué pasa |
|---|---|
{"amount": 150} |
rama true → fetch HTTP real + normaliza con JSONPath |
{"amount": 50, "note": "low"} |
rama false → pasa derecho (ves la poda de la rama no tomada) |
3. Time-Travel — abrí "Simple Pipeline" → Run con {"user_id": 1, "action": "login"}.
En /runs/:id recorré los snapshots inmutables por nodo (input/output en cada paso) y probá Replay.
4. Editor desde cero — desde el hub creás un workflow y armás el grafo con la paleta de la izquierda:
start → … → end. Recordá la regla del validador: exactamente un start y al menos un end.
Guía de pruebas exhaustiva (rutas de smoke-test, errores esperados, WS en vivo):
docs/QA-CHECKLIST.md.
cd apps/api && pytest # 118 tests: engine (paralelismo, ciclos, JSONPath, time-travel),
# auth, autorización (IDOR), SSRF guard, endpoints
cd apps/web && npm run test # 47 tests: Vitest (lib puras, cliente API, componentes)
npx playwright test --config e2e/playwright.config.ts # crear → run → time-travel, 3 viewports (requiere stack levantado)- El task manager es in-process (
asyncio.create_task): ideal para la demo, no sobrevive reinicios ni escala multi-worker. En producción se reemplaza por una cola durable (Arq/Celery + Redis). - El nodo
httpes no-determinista en replay (depende de un servicio externo).
© 2026 Mateo Pavoni. Todos los derechos reservados. Software propietario, publicado solo con fines de evaluación/portfolio. Prohibida su copia, redistribución o reuso sin autorización escrita. Ver LICENSE.



