Cómo funcionan los workflows
Un agente termina su ejecución respondiendo con JSON. Cuando esa respuesta trae la clave workflow, la plataforma la ejecuta: una cadena de peticiones HTTP, en orden, contra sus sistemas.
Es lo que convierte "el agente leyó la factura" en "la cuenta a pagar existe en el ERP" — sin que usted consulte nuestra API ni escriba un servicio que escuche webhooks solo para llamarse a sí mismo.
Dónde encaja en el pipeline
SES ──▶ Email (received)
│
├─ filtros ──▶ rejected
│
▼
approved ──▶ AgentRun (llamada al LLM) ──▶ Email processed
│ │
│ respuesta = { result, workflow }
│ │
└──────────────────▶ WorkflowRun ──▶ step 0 ──▶ step 1 ──▶ …
│
workflow.run.succeeded | failed
De esa posición se desprenden dos cosas, y son todo el diseño:
- El workflow corre después de que el análisis ya está registrado. El
AgentRunestá ensuccessy elEmailenprocessedantes de que salga la primera petición. Un destino caído es un fallo del workflow, nunca del agente — usted no pierde el análisis que ya le pagó al modelo. - El workflow es un job aparte. Tiene su propio registro (
WorkflowRun), su propio status, sus propios eventos. Nada en él bloquea la recepción de correo.
El formato de la respuesta
{
"result": { "supplier": "ACME", "amount": 1250.5, "due_date": "2026-10-01" },
"workflow": [
{
"type": "request",
"method": "POST",
"url": "https://erp.example.com/payables",
"headers": { "Authorization": "Bearer ${{secrets.FINANCE_TOKEN}}" },
"body": { "supplier": "ACME", "amount": 1250.5, "email_id": "${{kobana.email_id}}" },
"expected_status": 201
},
{
"type": "request",
"method": "POST",
"url": "https://erp.example.com/payables/${{steps.0.response.body.data.id}}/attachments",
"headers": { "Authorization": "Bearer ${{secrets.FINANCE_TOKEN}}" },
"form": { "file": "${{email.attachments.0}}" }
}
]
}
result conserva el significado de siempre y se sigue guardando en Email.agent_metadata. workflow es opcional — un agente que no devuelve workflow se comporta exactamente como antes.
Vea Steps y placeholders para todos los campos.
Modelo de ejecución
Secuencial. Los pasos corren en el orden del array, uno a la vez. El paso 1 empieza solo cuando el paso 0 terminó.
Un fallo queda aislado. Todos los pasos se intentan, en orden. Un paso que falla queda failed por sí solo; los siguientes siguen ejecutándose. La excepción es un paso que referencia (${{steps.N.…}}) un paso que no tuvo éxito — falló o se saltó: queda skipped, nunca se envía, y su error dice depends on step N, which did not succeed. "Adjunte el documento a la cuenta recién creada" está mal enviarlo cuando la cuenta nunca se creó, así que se salta; una segunda cuenta, independiente, en el mismo workflow se crea igual. La ejecución queda failed si algún paso falló. Ponga primero los pasos independientes que quiere ver ocurrir aunque otros fallen — un paso dependiente solo corre cuando lo que usa salió bien.
Exactamente una vez. No hay reintento automático, ni por paso ni por ejecución. Cada paso es una petición arbitraria a sus sistemas; repetirla podría duplicar una cuenta, una notificación o un cobro. Una ejecución que muere a mitad (reinicio de worker) queda en running y se muestra así, en vez de reproducirse a ciegas — reejecutar es una decisión humana, tomada con POST /v1/emails/{id}/reprocess.
Un paso falla cuando se cumple cualquiera de estas:
| Condición | error guardado |
|---|---|
El status no coincide con expected_status (o no es 2xx cuando está ausente) | expected status 201, got 422 |
| El destino no respondió en 30s | timeout after 30000ms |
| Fallo de conexión, de DNS o de TLS | ECONNREFUSED …, ENOTFOUND … |
| El destino respondió una redirección | expected status 2xx, got 302 — un 3xx nunca se sigue |
| Un placeholder no pudo resolverse | secret `X` is not defined in this workspace |
| La URL resuelta no está permitida | url not allowed: private_ip |
Lo que la plataforma garantiza
- Las credenciales nunca llegan al modelo. El agente escribe
${{secrets.FINANCE_TOKEN}}; el valor se sustituye al enviar, desde el almacén de Secrets. - Las credenciales nunca llegan a la base de datos. La petición persistida en cada paso tiene todo valor de secret reemplazado por
[REDACTED]. Lo que lee enGET /v1/workflow-runs/{id}no es el texto original, y no hay endpoint que lo devuelva. - La URL se verifica después de la sustitución. Solo
https://(fuera de desarrollo), y las direcciones privadas, de loopback, link-local y de metadatos de nube se rechazan — así un secret con una URL base no sirve para apuntar un paso a una red interna. - El intento completo queda registrado. Petición tal como se envió, status de la respuesta, cuerpo de la respuesta (hasta 4000 caracteres), duración y el error. Suficiente para diagnosticar sin reejecutar.
- Un workflow mal formado es ruidoso. Si el agente emite algo que no valida, se registra un
WorkflowRunenfailedcon el motivo (invalid workflow at `0.url`: …). Un agente que quiso disparar tres peticiones nunca puede parecer uno que no quiso disparar ninguna.
Límites
| Límite | Valor |
|---|---|
| Pasos por workflow | 20 |
| Timeout por paso | 30s |
| Respuesta leída por paso | 1 MiB |
| Cuerpo de petición/respuesta guardado | 4000 caracteres |
| Reintentos | ninguno |
Siguientes pasos
- Primeros pasos — montar uno de punta a punta.
- Steps y placeholders — la referencia completa de campos y placeholders.
- Diagnóstico — qué significa cada fallo y qué hacer.