- .claude/agents/fn-analizador/SKILL.md - .claude/agents/fn-constructor/SKILL.md - .claude/agents/fn-executor/SKILL.md - .claude/agents/fn-mejorador/SKILL.md - .claude/agents/fn-orquestador/SKILL.md - .claude/agents/fn-recopilador/SKILL.md - .claude/commands/app.md - .claude/commands/compile.md - .claude/commands/cpp-app.md - .claude/commands/create_functions.md - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
13 KiB
Estandar de Ejecucion: YAML + Exit Codes + Hooks
Contrato comun para apps, pipelines y automatizaciones del fn-registry. Define como declarar flujos en YAML, que exit codes devolver, como reportar resultados y como integrarse con dag_engine y operations.db.
Motivacion
Tenemos dos patrones que ya funcionan:
- dag_engine (
apps/dag_engine/): orquesta DAGs concommand/function, usa exit codes para decidir flujo, soportahandlers(init/success/failure/exit) y scheduling cron viaschedule:. - script_navegador: ejecuta pasos tipados (CDP actions) desde YAML, registra todo en operations.db, sale con 0/1.
Ambos comparten la misma idea: YAML declara el flujo, exit code señala el resultado, logs estructurados permiten analisis posterior. Este documento formaliza ese contrato para que cualquier app nueva lo siga desde el inicio.
1. Estructura YAML
Toda app que ejecute un flujo debe leer un YAML con esta estructura minima:
# Cabecera obligatoria
name: nombre_del_flujo
description: "Que hace este flujo" # opcional pero recomendado
# Variables de entorno disponibles para todos los pasos
env: # opcional
KEY: value
# Pasos del flujo
steps:
- name: paso_1 # identificador unico dentro del flujo
action: tipo_de_accion # dispatch key (navigate, command, query, etc.)
# ... parametros especificos del action
continue_on_error: false # default: false
timeout_ms: 30000 # default: 30s, 0 = sin timeout
depends: [] # IDs de pasos previos (vacio = secuencial)
# Hooks opcionales
hooks:
on_success: "comando o script" # se ejecuta si exit code = 0
on_failure: "comando o script" # se ejecuta si exit code = 1
on_partial: "comando o script" # se ejecuta si exit code = 2
Campos de la cabecera
| Campo | Requerido | Descripcion |
|---|---|---|
name |
si | Identificador del flujo (snake_case) |
description |
no | Descripcion legible |
env |
no | Variables de entorno (mapa key-value) |
steps |
si | Lista de pasos a ejecutar |
hooks |
no | Comandos a ejecutar segun resultado |
Campos de cada step
| Campo | Requerido | Default | Descripcion |
|---|---|---|---|
name |
si | — | ID unico del paso dentro del flujo |
action |
si | — | Tipo de accion (cada app define sus propios actions) |
continue_on_error |
no | false |
Si true, un fallo no detiene el flujo |
timeout_ms |
no | 30000 |
Timeout del paso en ms (0 = sin timeout) |
depends |
no | [] |
Pasos que deben completarse antes |
Los campos adicionales dependen del action y los define cada app/dominio.
Actions por dominio
Cada app registra sus actions validos. Ejemplos:
| Dominio | Actions | App de referencia |
|---|---|---|
| navegacion | navigate, click, type, wait, screenshot, evaluate, get_html, wait_load, sleep |
script_navegador |
| shell | command, script |
dag_engine / apps genericas |
| registry | function, args |
dag_engine (invoca fn run <id> y captura function_id en dag_step_results) |
| database | query, migrate, backup |
futuras apps |
| http | request, assert_status, extract |
futuras apps |
2. Exit Codes
Toda app/script que siga este estandar DEBE salir con uno de estos tres codigos:
| Codigo | Constante | Significado |
|---|---|---|
0 |
EXIT_SUCCESS |
Todos los pasos completaron sin error |
1 |
EXIT_FAILURE |
Al menos un paso fallo y el flujo aborto |
2 |
EXIT_PARTIAL |
Algunos pasos fallaron pero el flujo continuo (continue_on_error) |
Reglas
- Si todos los pasos son exitosos →
0 - Si algun paso falla y tiene
continue_on_error: false→1(aborta) - Si algun paso falla pero todos los fallos tenian
continue_on_error: true→2 - Un timeout agotado cuenta como fallo del paso
Compatibilidad con dag_engine
dag_engine interpreta exit code != 0 como fallo. Para que 2 (partial) no dispare el handler de failure, el DAG que llama a la app puede usar continue_on.exit_code:
steps:
- name: run_app
command: ./mi_app --script flujo.yaml
continue_on:
exit_code: [2] # exit 2 no aborta el DAG (partial es tolerado)
- name: check_result
command: test $? -le 1 # falla solo si fue error fatal
depends: [run_app]
O alternativamente, la app puede configurarse con --strict para mapear partial → failure (exit 1).
3. Output Estructurado
Toda app DEBE escribir un resumen JSON en stdout al finalizar (despues de su output humano). El formato:
{
"name": "nombre_del_flujo",
"status": "success|failure|partial",
"exit_code": 0,
"started_at": "2026-04-01T10:00:00Z",
"ended_at": "2026-04-01T10:00:05Z",
"duration_ms": 5000,
"steps_total": 5,
"steps_ok": 4,
"steps_failed": 1,
"steps": [
{
"name": "paso_1",
"action": "navigate",
"status": "ok",
"elapsed_ms": 1200,
"output": "optional output"
},
{
"name": "paso_2",
"action": "click",
"status": "error",
"elapsed_ms": 500,
"error": "elemento no encontrado: #btn-submit"
}
]
}
Separacion humano / maquina
- stderr: logs legibles para humanos (progreso, warnings)
- stdout: output JSON estructurado al final (para dag_engine, hooks, pipes)
- Flag
--jsono--quiet: suprime output humano, solo JSON en stdout
Esto permite que dag_engine capture el JSON:
steps:
- name: navegacion
command: ./script_navegador --script busqueda.yaml --json
output: RESULT
- name: analizar
command: echo "$RESULT" | jq '.steps[] | select(.status=="error")'
depends: [navegacion]
4. Hooks
Los hooks se ejecutan despues de todos los pasos, segun el exit code resultante.
En el YAML del flujo
hooks:
on_success: "notify-send 'Flujo completado'"
on_failure: "echo 'FALLO: {{.Name}}' >> /tmp/failures.log"
on_partial: "echo 'PARCIAL: {{.StepsFailed}} pasos fallaron' >> /tmp/warnings.log"
on_step_error: "echo 'Step {{.StepName}} fallo: {{.Error}}'" # se ejecuta por cada paso fallido
Variables disponibles en hooks
| Variable | Tipo | Descripcion |
|---|---|---|
{{.Name}} |
string | Nombre del flujo |
{{.Status}} |
string | success, failure, partial |
{{.ExitCode}} |
int | 0, 1, 2 |
{{.DurationMs}} |
int | Duracion total en ms |
{{.StepsTotal}} |
int | Total de pasos |
{{.StepsOk}} |
int | Pasos exitosos |
{{.StepsFailed}} |
int | Pasos fallidos |
{{.StepName}} |
string | Nombre del paso (solo en on_step_error) |
{{.Error}} |
string | Mensaje de error (solo en on_step_error/on_failure) |
En dag_engine (handlers globales)
Los hooks del YAML son locales al flujo. Para orquestacion, dag_engine maneja sus propios handlers (init/success/failure/exit, alias handler_on):
# apps/dag_engine/dags_migrated/mi_automatizacion.yaml
name: mi_automatizacion
handlers:
failure:
- name: alerta
command: echo "Fallo en mi_automatizacion" >> /var/log/fn_registry/failures.log
success:
- name: registrar
command: echo "OK $(date)" >> /var/log/fn_registry/success.log
steps:
- name: ejecutar
function: mi_pipeline_bash_pipelines
args: ["flujo.yaml"]
5. Integracion con operations.db
Las apps que siguen el bucle reactivo DEBEN registrar la ejecucion en operations.db:
Mapping del estandar a operations.db
| Concepto estandar | Tabla operations.db | Campo |
|---|---|---|
| Flujo completo | executions |
status: success/failure/partial |
| Cada paso | logs |
level: info/error, message: resultado |
| Exit code | executions.metrics |
{"exit_code": N} |
| Output JSON | executions.metrics |
{"steps": [...]} |
| Duracion | executions.duration_ms |
milisegundos |
| Pasos in/out | executions.records_in/out |
total/exitosos |
Patron de registro (referencia: script_navegador/ops.go)
1. initOpsDB() → abrir/crear operations.db
2. EnsureEntities() → registrar recursos involucrados
3. EnsureRelations() → registrar como se conectan
4. [ejecutar flujo]
5. RecordRun() → insertar execution con metricas
6. LogStep() por paso → insertar log por cada paso
7. UpdateRelation() → status final de la relation
Este patron ya esta implementado en script_navegador/ops.go y sirve como referencia para nuevas apps.
6. Flujo completo: App → dag_engine → Hooks
dag_engine (scheduler)
│
cron / manual / API
│
┌────▼────┐
│ DAG │ apps/dag_engine/dags_migrated/mi_dag.yaml
│ step │ command: ./mi_app --script flujo.yaml --json
└────┬────┘
│
┌────▼────────────────┐
│ App (mi_app) │
│ │
│ 1. Load YAML │
│ 2. Validate steps │
│ 3. Init ops.db │
│ 4. Execute steps │
│ 5. Run hooks │
│ 6. Record to ops │
│ 7. JSON → stdout │
│ 8. Exit code │
└────┬────────────────┘
│
┌──────────┼──────────┐
│ │ │
exit 0 exit 1 exit 2
success failure partial
│ │ │
▼ ▼ ▼
engine OK engine FAIL engine FAIL*
handler handler (configurable)
*Con continue_on: { exit_code: [2] } en el step, exit 2 no aborta el DAG.
7. Implementacion: funciones del registry
Existentes (ya en uso por script_navegador)
Todas las funciones CDP del registry (cdp_*_go_browser, chrome_launch_go_browser) ya siguen el patron de retornar error. El runner de script_navegador las compone.
Nuevas (por construir)
| Funcion | Lang | Domain | Descripcion |
|---|---|---|---|
run_steps |
bash | shell | Ejecuta pasos de un YAML generico (action=command/script), reporta JSON y sale con 0/1/2 |
report_execution_json |
bash | shell | Genera el JSON de salida estandar dado los resultados de pasos |
exit_with_status |
bash | shell | Calcula exit code (0/1/2) a partir de contadores ok/fail/partial |
Estas funciones Bash permiten que cualquier script nuevo siga el estandar sin reimplementar la logica de reporte y exit codes.
8. Ejemplo completo
Script YAML (flujo.yaml)
name: backup_db
description: "Backup de operations.db y push a remoto"
steps:
- name: check_db
action: command
command: "test -f operations.db"
- name: backup
action: command
command: "cp operations.db backups/operations_$(date +%Y%m%d).db"
depends: [check_db]
- name: push
action: command
command: "git add backups/ && git commit -m 'backup' && git push"
depends: [backup]
continue_on_error: true
hooks:
on_failure: "echo 'Backup fallo' >> /tmp/backup_errors.log"
Invocacion desde dag_engine
name: backup_diario
schedule: "0 2 * * *"
working_dir: $HOME/fn_registry/apps/mi_app
steps:
- name: backup
function: backup_db_bash_pipelines
args: ["flujo.yaml", "--json"]
handlers:
failure:
- name: alerta
command: echo "Backup fallo $(date)" >> /var/log/fn_registry/failures.log
Output esperado (stdout)
{
"name": "backup_db",
"status": "partial",
"exit_code": 2,
"started_at": "2026-04-01T02:00:00Z",
"ended_at": "2026-04-01T02:00:03Z",
"duration_ms": 3000,
"steps_total": 3,
"steps_ok": 2,
"steps_failed": 1,
"steps": [
{"name": "check_db", "action": "command", "status": "ok", "elapsed_ms": 10},
{"name": "backup", "action": "command", "status": "ok", "elapsed_ms": 450},
{"name": "push", "action": "command", "status": "error", "elapsed_ms": 2540, "error": "remote rejected"}
]
}
Exit code: 2 (partial — push fallo pero tenia continue_on_error).
Resumen del contrato
| Aspecto | Regla |
|---|---|
| Formato de flujo | YAML con name + steps[] |
| Exit codes | 0=success, 1=failure, 2=partial |
| Output maquina | JSON en stdout (con --json) |
| Output humano | stderr (progreso, warnings) |
| Error handling | continue_on_error por paso, hooks por resultado |
| Trazabilidad | operations.db (executions + logs) |
| Orquestacion | dag_engine como scheduler (apps/dag_engine/), apps/funciones del registry como ejecutores |
| Funciones | Reutilizar del registry, actions por dominio |