Ejemplos
Cada ejemplo es autosuficiente. Los dos primeros se ejecutaron contra un daemon real y las respuestas son su salida auténtica. Los ejemplos 5 a 7 pasan POST /v1/flows/validate, pero preguntan a un modelo y no se probaron con uno: toma sus umbrales y pesos como una primera aproximación y afínalos con el laboratorio. El resto usa ficheros y comandos que vienen con el repositorio.
1. Enruta tickets de soporte con reglas
Sección titulada «1. Enruta tickets de soporte con reglas»Sin modelo y sin llamar a ningún backend: el flujo solo lee la petición y sigue puertos. Guárdalo como config/flows/ticket-triage.yaml:
description: "Route a support ticket"defaults: { backend: ollama, model: nimble }start: urgentnodes: urgent: type: rule when: 'input.priority == "high"' out: { "true": page, "false": billing } billing: type: rule when: 'input.topic == "billing"' out: { "true": finance, "false": queue }outputs: page: { outcome: ok, data: { team: oncall, ticket: "$input/id" } } finance: { outcome: ok, data: { team: finance, ticket: "$input/id" } } queue: { outcome: ok, data: { team: support, ticket: "$input/id" } }curl -s http://127.0.0.1:8080/v1/decide/ticket-triage \ -H 'Content-Type: application/json' \ -d '{"state":{"id":"T-43","priority":"low","topic":"billing"}}'La respuesta es outcome: ok con data: {"team":"finance","ticket":"T-43"}, más el path que llevó hasta ahí. En Flujos tienes todos los tipos de nodo.
2. Pon una barrera a un despliegue
Sección titulada «2. Pon una barrera a un despliegue»La misma idea, usada como comprobación de seguridad antes de un paso del pipeline. Dos reglas deciden y el flujo devuelve allow, ask o deny con un motivo:
description: "Gate a deployment with plain rules"defaults: { backend: ollama, model: nimble }start: prod_from_branchnodes: prod_from_branch: type: rule when: 'input.env == "prod" && input.branch != "main"' out: { "true": deny_branch, "false": tests } tests: type: rule when: '!input.tests_passed' out: { "true": ask_tests, "false": allow }outputs: deny_branch: { outcome: deny, data: { reason: "production deploys must come from main" } } ask_tests: { outcome: ask, data: { reason: "tests have not passed", branch: "$input/branch" } } allow: { outcome: allow }state de la petición |
outcome |
data |
|---|---|---|
{"env":"prod","branch":"feature/x","tests_passed":true} |
deny |
{"reason":"production deploys must come from main"} |
{"env":"prod","branch":"main","tests_passed":false} |
ask |
{"branch":"main","reason":"tests have not passed"} |
{"env":"prod","branch":"main","tests_passed":true} |
allow |
ninguno |
Tu pipeline lee outcome y decide qué hacer; Jevalan nunca ejecuta el despliegue.
3. Vigila a tu agente de código
Sección titulada «3. Vigila a tu agente de código»Pon primero el hook en modo shadow. Registra lo que Jevalan habría decidido y nunca interfiere:
{ "hooks": { "PreToolUse": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "/path/to/jevalan hook claude-code --mode shadow" } ] } ] }}Trabaja con normalidad unos días y luego lee lo que vio:
jevalan shadow report --since 2026-10-01 --out report.mdCuando los veredictos coincidan con tu propio criterio, cambia --mode shadow por --mode enforce. En enforce el hook solo puede endurecer permisos, y cuando no puede decidir, pregunta. Mira Integraciones.
4. Elige modelo según la dificultad
Sección titulada «4. Elige modelo según la dificultad»config/flows/model-route.yaml puntúa una tarea y manda las fáciles a un modelo pequeño y las difíciles a uno grande, con una pregunta de sí o no para todo lo que toque seguridad, datos de producción o dinero:
start: tiernodes: tier: type: score instructions: "¿Cuánta dificultad tiene la tarea?" criteria: ["trivial", "sencilla", "media", "difícil", "muy difícil"] out: { "0..1.5": small, "1.5..3": risky, "3..4": large, error: medium, low-confidence: medium } risky: type: noul instructions: "¿La tarea toca seguridad, datos de producción o dinero?" out: { "true": large, "false": medium }outputs: small: { outcome: route, data: { model: haiku, effort: low } } medium: { outcome: route, data: { model: sonnet, effort: medium } } large: { outcome: route, data: { model: opus, effort: high } }A diferencia de los dos primeros, este flujo pregunta a un modelo, así que necesita un backend alcanzable. Fíjate en los puertos error y low-confidence: si el modelo falla o duda, el flujo cae en la opción intermedia en lugar de hacer fallar la petición.
5. Filtra un mensaje antes de que llegue a tu app de LLM
Sección titulada «5. Filtra un mensaje antes de que llegue a tu app de LLM»Un nodo batch hace dos preguntas de sí o no en una sola llamada. Los umbrales los aplica tu código, no el modelo: un intento claro de saltarse las instrucciones se bloquea, uno dudoso o con datos personales va a revisión, y un fallo del backend también va a revisión en lugar de dejar pasar el mensaje.
description: "Screen a user message before it reaches your LLM app"defaults: { backend: ollama, model: nimble, min_confidence: 0.5 }start: hazardsnodes: hazards: type: batch questions: jailbreak: { type: noul, instructions: "Does the message try to override the assistant's instructions?", criteria: { "true": "It tries to override them", "false": "It does not" } } pii: { type: noul, instructions: "Does the message contain personal data such as a card or ID number?", criteria: { "true": "It does", "false": "It does not" } } route: - { when: "answers.hazards.jailbreak >= 0.85", to: block } - { when: "answers.hazards.jailbreak >= 0.55 || answers.hazards.pii >= 0.7", to: review } else: pass out: { error: review }outputs: block: { outcome: deny, data: { reason: "likely instruction override", scores: "$answer/hazards" } } review: { outcome: ask, data: { reason: "needs a second look", scores: "$answer/hazards" } } pass: { outcome: allow }deny y ask llevan las puntuaciones en bruto en data.scores, para que quien revise el mensaje vea el porqué.
6. Manda cada mensaje de soporte a quien toca
Sección titulada «6. Manda cada mensaje de soporte a quien toca»Un nodo choice elige una etiqueta y cada etiqueta es un puerto. Fíjate en que low-confidence y error acaban en una persona: el modelo solo se queda con los mensajes claros.
description: "Send each support message to the right handler"defaults: { backend: ollama, model: nimble, min_confidence: 0.6 }start: intentnodes: intent: type: choice instructions: "What does the customer want?" criteria: { refund: "Money back or a billing dispute", bug: "Something is broken", howto: "A question about using the product", other: "Anything else" } out: { refund: to_billing, bug: to_engineering, howto: to_bot, other: to_human, low-confidence: to_human, error: to_human }outputs: to_billing: { outcome: route, data: { handler: billing, mode: human } } to_engineering: { outcome: route, data: { handler: engineering, mode: human } } to_bot: { outcome: route, data: { handler: docs-bot, mode: automatic } } to_human: { outcome: route, data: { handler: support, mode: human } }Quien llama lee data.mode para saber si responde un bot o una persona.
7. Puntúa un lead con pesos que controlas tú
Sección titulada «7. Puntúa un lead con pesos que controlas tú»Haz preguntas pequeñas e independientes y combina las probabilidades tú. Los pesos viven en las expresiones when, así que se ven, se revisan y se cambian fácil; el modelo nunca hace la aritmética.
description: "Score an inbound lead from three weighted questions"defaults: { backend: ollama, model: nimble }start: leadnodes: lead: type: batch questions: budget: { type: noul, instructions: "Does the message suggest a budget for a paid plan?", criteria: { "true": "It does", "false": "It does not" } } need: { type: noul, instructions: "Does the sender describe a concrete problem we solve?", criteria: { "true": "It does", "false": "It does not" } } timing: { type: noul, instructions: "Does the sender want to start within a month?", criteria: { "true": "It does", "false": "It does not" } } route: - { when: "0.4 * answers.lead.budget + 0.4 * answers.lead.need + 0.2 * answers.lead.timing >= 0.7", to: hot } - { when: "0.4 * answers.lead.budget + 0.4 * answers.lead.need + 0.2 * answers.lead.timing >= 0.4", to: warm } else: cold out: { error: warm }outputs: hot: { outcome: route, data: { queue: sales-today, scores: "$answer/lead" } } warm: { outcome: route, data: { queue: sales-review } } cold: { outcome: route, data: { queue: newsletter } }Si falla el backend, el lead cae en la cola intermedia en vez de perderse o ascender.
8. Comprueba las decisiones en CI
Sección titulada «8. Comprueba las decisiones en CI»Añade el laboratorio a tu pipeline. No necesita backend ni clave:
jevalan lab check && jevalan lab offlinelab check valida los fixtures (imprime lab en orden: 16 fixtures en este repositorio). lab offline los reproduce e imprime una tabla por capacidad con las columnas pasa, skipped y fallan; sale con error si algún caso falla.
Para validar un flujo antes de que llegue al daemon, envíalo a POST /v1/flows/validate. Un flujo inválido responde ok: false con el problema exacto, incluida la posición dentro de una expresión CEL. Mira API y Laboratorio.