Saltar a contenido

Nodos de workflow

Cada paso que puede contener un workflow: con qué se configura, qué lee, qué produce y qué significa un fallo. La paleta del editor muestra los mismos nodos del mismo registro. La documentación de campos de abajo se genera desde el código, por eso sigue en inglés.

Algunas reglas valen para todos los nodos:

  • Una arista fija el orden y un binding lleva un valor. Los campos de entrada de un nodo se enlazan a salidas anteriores, a un literal o a una referencia de archivo o de tabla. Ver Configurar un nodo.
  • Una ruta dentro de un valor de forma libre se comprueba cuando el nodo se ejecuta. La carga de un trigger, un registro mapeado y la respuesta estructurada de un agent no tienen forma fija, así que un binding como payload.email se acepta al publicar y se valida contra su destino cuando el nodo se despacha. Un valor que no encaja hace fallar el run con INVALID_BINDING, y el handler nunca lo ve.
  • Los recursos se comprueban dos veces. Publicar rechaza una colección, una versión de agent, una credencial o un destinatario que el autor del grafo no puede alcanzar. Cada run lo vuelve a comprobar con su propio principal, porque el acceso puede retirarse entre medias.
  • El tipo de efecto y los reintentos deciden qué puede hacer el motor tras un fallo. Un paso pure o idempotent se reintenta. Un paso que puede haber actuado ya y no da ninguna garantía se detiene para una persona. La política propia de un nodo fija cuántas veces se reintenta, cuánto puede durar una llamada y adónde va un fallo.

Triggers

Un workflow empieza por un trigger, el nodo en el que empieza su grafo. Publicar una versión enciende su trigger; consulta Iniciar un workflow desde fuera de la consola. Cada trigger pasa a los pasos siguientes aquello con lo que su superficie inició el run, congelado al admitir el run y como mucho WORKFLOW_RUN_MAX_INPUT_BYTES. Un run de prueba iniciado con una entrada de otra forma hace fallar el trigger con TRIGGER_INPUT_INVALID. Un segundo trigger, o un trigger por el que no empieza el grafo, se rechaza al publicar.

core.input

API request. Se inicia con una petición HTTP o por un WebSocket. Su id sigue siendo core.input, así que un grafo escrito antes de que Manual y API se separaran sigue empezando desde la API. Entrega al grafo la entrada del run como payload, lo que haya enviado quien llama, y nombra la superficie en triggered_by.

app.workflows.contracts.io.WorkflowInputPayload

Bases: BaseModel

What a run was started with, as core.input hands it to the graph.

payload is whatever the invoking surface supplied - an API body, a chat message, a webhook's JSON, a table record's snapshot - untyped here because the graph, not the node, decides its shape: a binding reads a field out of it by path and is checked against the target when the run dispatches.

Sus campos de entrada convierten la entrada en un contrato. Un campo tiene un nombre, un tipo - texto, número, número entero, sí o no, fecha u opción - y si es obligatorio. Sin campos, un run acepta cualquier objeto JSON. Con campos, Start a run pide cada uno por su nombre, un run cuya entrada omite uno, envía uno del tipo equivocado o uno no declarado se rechaza antes de empezar con WORKFLOW_RUN_INPUT_INVALID, y un binding a payload.<campo> se comprueba por tipo al publicar.

app.workflows.contracts.fields.InputField

Bases: BaseModel

One value a run starts with, asked for by name in the Run dialog.

trigger.manual

Manual. Lo inicia una persona - Test draft en el editor, o Start a run en su página de runs. Entrega al grafo lo mismo que API request y acepta los mismos campos de entrada, que el editor pide antes de empezar el run.

trigger.chat

Chat message. Se inicia con un mensaje en el chat, con este workflow elegido para responder. El texto de su core.output se escribe de vuelta en esa conversación.

app.workflows.nodes._triggers.ChatTriggerOutput

Bases: _Frozen

The message a member sent in the chat, and where the answer goes.

trigger.webhook

Webhook. Se inicia con una entrega firmada a la dirección propia del workflow, que la primera publicación del nodo crea junto con su secreto de firma.

Antes de publicarlo, Listen for test event en el panel Output del disparador abre una URL de prueba para el borrador durante dos minutos. La única llamada que se le envía, un objeto JSON, queda fijada como salida del disparador, así que cada paso posterior se puede probar con una entrega real. La URL de prueba no comprueba ninguna firma y nunca inicia un run.

app.workflows.nodes._triggers.WebhookTriggerOutput

Bases: _Frozen

One signed delivery: its JSON body and the id the sender gave it.

trigger.schedule

Schedule. Se inicia según un reloj, en la zona horaria del workflow y como mucho una vez por minuto.

app.workflows.nodes._triggers.ScheduleTriggerConfig

Bases: _Frozen

How often the workflow runs, and what every run starts with.

app.workflows.nodes._triggers.ScheduleTriggerOutput

Bases: _Frozen

When the tick fired, and the input the schedule starts every run with.

trigger.table_record

Table record. Se inicia cuando un registro de su tabla se añade, cambia o borra - según nombre events - y cumple cada filtro. Un cambio solo lo inicia cuando cambió una columna elegida para la tabla, si hay columnas elegidas. Publicarlo requiere acceso de lectura a la tabla.

app.workflows.nodes._triggers.TableRecordTriggerConfig

Bases: _Frozen

Which table, which of its records' events, and which records, start the workflow.

app.workflows.nodes._triggers.TableRecordTriggerOutput

Bases: _Frozen

The record the event is about: its values by column id and by label, who did it, and - for a change or a deletion - what it held before.

trigger.workflow_call

Called by a workflow. Lo inicia el paso workflow.run de otro workflow, con los campos que declara - los mismos que Manual - y nunca a mano ni por la API. La entrada de quien llama se comprueba contra ellos antes de que empiece la ejecución.

trigger.form

Form. Se inicia cuando alguien rellena la página pública del workflow, en el enlace que crea la primera publicación del nodo. La página muestra su título, su descripción y un campo por cada campo - los mismos que Manual -, y un envío que encaja con ellos inicia una ejecución como el miembro que lo publicó. triggered_by es form.

app.workflows.nodes.trigger_form._handler.FormTriggerConfig

Bases: TriggerInputConfig

The page a form shows, and the fields it asks for.

trigger.github

GitHub. Se inicia con una entrega que GitHub firma para el repositorio, uno de los eventos y - si el nodo nombra alguna - una de las acciones que nombra. La publicación registra el hook a través de la cuenta de GitHub conectada de la organización cuando puede, o muestra el secreto una vez para pegarlo. triggered_by es github.

app.workflows.nodes.trigger_github._handler.GithubTriggerConfig

Bases: BaseModel

Which repository, and which of its events, start the workflow.

takes

takes(*, event, action, repository)

Whether a delivery of event and action on repository starts the workflow.

app.workflows.nodes._triggers.GithubTriggerOutput

Bases: _Frozen

One GitHub delivery: which event and action, where, by whom, and all of it.

trigger.gmail

Gmail. Se inicia con un mensaje nuevo en el buzón de Gmail conectado de la organización que encaja con todos los filtros puestos, leído en el intervalo Check for new mail every del buzón (por defecto un minuto). La publicación se rechaza hasta que haya un buzón conectado. triggered_by es gmail. Sender contains compara la dirección del remitente, no el nombre visible: @acme.com es exactamente ese dominio, sin subdominios. Con él puesto, el mensaje debe además pasar la comprobación de remitente autenticado (DMARC o una firma DKIM del dominio del remitente). Los mensajes con la etiqueta SENT, DRAFT, SPAM o TRASH se omiten, salvo que Label los nombre.

La salida lleva el hilo - thread_id, rfc_message_id, in_reply_to, references, received_at - y el cuerpo html, leído hasta 20.000 caracteres. Los campos de cabecera los escribe el remitente: enlazan una respuesta con el hilo y no prueban quién envió el mensaje.

Una ejecución que inició el buzón guarda los adjuntos del mensaje como archivos del run, pero solo cuando el workflow los lee: un grafo que no enlaza ni los attachments del trigger ni toda su salida deja cada adjunto como not_fetched. Cada archivo guardado cuenta para la cuota de archivos de la organización, y cualquiera puede escribir a un trigger sin Sender contains, así que enlaza attachments solo cuando un paso necesite los archivos.

Un buzón lista como máximo 20 por mensaje y solo se consideran los 10 primeros listados, hasta 25 MiB en total, cada uno dentro del límite de tamaño de subida del chat, de la lista de tipos de archivo permitidos y de la cuota de almacenamiento de la organización. Un adjunto que se deja fuera conserva su sitio en attachments con un reason: too_many, too_large, type_not_allowed, quota, unreadable o not_fetched. Una ejecución que el buzón no inició - una de prueba - no lee nada, así que cada adjunto es not_fetched.

unreadable es una respuesta 400, 401, 403 o 404 para el adjunto, o un grant del buzón que falta, está apagado o no se puede renovar. Cualquier otro fallo del proveedor hace fallar el paso con MAIL_ATTACHMENT_UNREACHABLE, que se reintenta. Los dos triggers de correo aceptan entrada de fuera.

app.workflows.nodes.trigger_gmail._handler.GmailTriggerConfig

Bases: BaseModel

Which messages start the workflow; every new one when nothing is set.

The same filter an agent's Gmail trigger applies, read by the same matcher. A sender filter also needs the message to pass the authenticated-sender check (DMARC, or a DKIM signature from the sender's domain).

app.workflows.nodes._triggers.EmailTriggerOutput

Bases: _Frozen

One message that arrived in the connected mailbox.

app.workflows.nodes._triggers.MailAttachment

Bases: _Frozen

One attachment the message listed, and the stored file when the trigger staged it.

trigger.outlook

Outlook. Se inicia con un mensaje nuevo en una carpeta del buzón de Microsoft 365 conectado de la organización que encaja con todos los filtros puestos, leído en el intervalo Check for new mail every del buzón (por defecto un minuto). La publicación se rechaza hasta que haya un buzón conectado. triggered_by es outlook. Microsoft Graph se lee carpeta a carpeta, así que Folder y Shared mailbox son a la vez donde mira el poller y un filtro, y la primera lectura de una carpeta empieza en el momento en que se publicó o se encendió por última vez el workflow. Nombrar un buzón compartido que el grant de Microsoft de la organización pueda abrir exige el permiso para gestionar conexiones, como en los triggers de agente de Outlook; un workflow que ya escucha uno lo conserva en publicaciones posteriores, y cambiarlo exige el permiso otra vez. Sender contains compara la dirección del remitente, como en Gmail, y exige correo que el dominio del remitente autenticó o que vino de dentro del tenant. Una carpeta o un buzón compartido que Outlook dice que no se pueden leer apaga el workflow, con mailbox_folder_unreadable en el registro de auditoría. Una configuración guardada que ya no valida la omite el poller. Los adjuntos se guardan como en trigger.gmail, y labels contiene las categorías del mensaje. Su salida es la misma que la de trigger.gmail.

app.workflows.nodes.trigger_outlook._handler.OutlookTriggerConfig

Bases: BaseModel

Which folder is read, and which of its messages start the workflow.

The same filter an agent's Outlook trigger applies, read by the same matcher: every key here is one that trigger accepts. A sender filter also needs the message to pass the authenticated-sender check (DMARC, a DKIM signature from the sender's domain, or mail from inside the tenant).

trigger.channel_message

Channel message. Se inicia con un mensaje a uno de los bots de Slack, Mattermost o Telegram de la organización que el bot habría respondido con su propio agente y que encaja con el filtro del nodo. Publicarlo requiere channels:manage, y los mensajes de un bot inician como mucho un workflow. triggered_by es channel_message. Un bot de Teams se rechaza al publicar y en una ejecución de prueba; un oyente de Teams publicado antes devuelve sus mensajes al agente del bot.

app.workflows.nodes.trigger_channel_message._handler.ChannelMessageTriggerConfig

Which bot's messages start the workflow, and which of them.

takes

takes(*, chat_type, text)

Whether a message in a chat_type chat saying text starts the workflow.

app.workflows.nodes._triggers.ChannelMessageTriggerOutput

Bases: _Frozen

One message to the bot: what it said, who said it, and where to answer it.

channel_id and thread_id are what a Send a message step of the same platform takes, so a reply goes back into the thread the message came from.

trigger.workflow_failed

On failure of a workflow. Se inicia una vez por cada ejecución real fallida de un workflow cuyos ajustes nombran este como su workflow de errores, como el miembro que lo eligió. Una ejecución iniciada por este disparador nunca inicia a su vez un workflow de errores.

app.workflows.nodes._triggers.WorkflowFailedTriggerOutput

Bases: _Frozen

The run that failed: which workflow, which step, and with what error.

core.output

Lo que responde el workflow. Sus campos enlazados pasan a ser el output del run, que devuelve la API y entrega la superficie que lo invoca. Tiene los mismos campos que la salida de agent.run, así que la respuesta de un agent se enlaza directamente. Una salida sin nada enlazado es una respuesta vacía. Un run responde una vez, así que publicar rechaza un grafo con dos pasos así que puedan ejecutarse ambos en un mismo run.

app.workflows.contracts.io.WorkflowOutputPayload

Bases: BaseModel

What a run answers with, as core.output records it.

The same fields agent.run produces, so the common case - an agent feeding the workflow's answer - type-checks with nothing in between. Every field is optional: a workflow that only writes to a table still ends, with nothing to say.

webhook.respond

Respond to webhook. Responde a la entrega que inició el run con un estado (de 200 a 599), cabeceras y un body JSON vinculado desde un paso anterior. Una entrega a un grafo con este paso lo espera en lugar de recibir 202, como mucho WORKFLOW_WEBHOOK_RESPONSE_TIMEOUT_SECONDS. La respuesta es el primer paso respond que termina, un reintento de la misma entrega la recibe de nuevo y el run sigue después. Un run que termina sin llegar al paso responde 202 si tuvo éxito y 500 si no. Publicar rechaza un grafo con dos pasos así que puedan ejecutarse ambos en un mismo run. Las cabeceras que pertenecen a la propia respuesta de la API se rechazan al publicar: el encuadre, Content-Type, Set-Cookie y las cabeceras CORS y de políticas del navegador.

app.workflows.nodes.webhook_respond.WebhookRespondConfig

Bases: BaseModel

The status and headers the delivery is answered with.

data.map

Construye un registro pequeño y tipado a partir de salidas anteriores. Cada mapeo lee un valor con una expresión JMESPath y lo convierte a string, number, integer, boolean, json, file_ref o table_ref. Un valor que no se puede convertir falla con MAPPING_COERCION_FAILED y nombra el campo.

app.workflows.nodes.data_map._handler.DataMapConfig

Bases: BaseModel

The fields to build, in order.

app.workflows.nodes.data_map._handler.FieldMapping

Bases: BaseModel

One output field: where its value comes from, and what type it becomes.

data.filter y data.combine

Filter a list conserva los elementos de una lista vinculada para los que se cumple una condición JMESPath sobre item e index, y dice cuántos descartó. Combine lists hace una lista de first y second: append pone una tras otra, by_position une los objetos de la misma posición, y by_key une a cada objeto de la primera el objeto de la segunda con el mismo key. Si ambos tienen un campo, gana la segunda; unir elemento a elemento necesita objetos y, si no, falla con COMBINE_NEEDS_OBJECTS.

app.workflows.nodes.data_filter._handler.DataFilterConfig

Bases: BaseModel

What an item must be to be kept.

app.workflows.nodes.data_combine._handler.DataCombineConfig

Bases: BaseModel

How the two lists become one.

text.compare

Compare texts puntúa cuán parecidos son dos textos, a y b: 1 cuando son iguales una vez apartadas las diferencias ignoradas, 0 cuando no tienen nada en común. No llama a ningún modelo y no cuesta nada, y los mismos dos textos siempre dan los mismos números (las puntuaciones se redondean a seis decimales). El método indica qué se cuenta:

  • char_ratio (el predeterminado) es la proporción de caracteres que un editor no tendría que tocar, a partir de la distancia de edición exacta entre caracteres.
  • token_f1 es la proporción de palabras que los dos textos tienen en común, sin importar el orden.
  • line_diff es la proporción de líneas sin cambios.

Los acentos compuestos y descompuestos nunca son una edición, ni tampoco los finales de línea. Lo demás lo decide el paso. Ignore whitespace está activado por defecto: colapsa el espaciado dentro de una línea, recorta cada línea y descarta las líneas en blanco. Los saltos de línea siguen contando, así que solo token_f1, que cuenta palabras, ignora dónde termina una línea. Ignore case e Ignore punctuation están desactivados por defecto. Las palabras de token_f1 son series de letras, dígitos y guiones bajos: divide en los apóstrofos y no segmenta el texto escrito sin espacios.

Leave out quoted mail and signatures descarta toda línea cuyo primer carácter no blanco es > (también las citas con sangría), la línea de atribución sobre una cita y todo lo que sigue a un separador de firma -- o a un -- solo, o a una línea ----- Original Message -----. Es una heurística para correo en texto plano: no detecta citas HTML, una respuesta cuya cita no está marcada con >, una atribución partida en dos líneas (se queda) ni una firma sin separador.

Cada texto tiene como máximo 100.000 caracteres, contados antes de normalizar; puntuar dos textos de ese tamaño lleva unos segundos. Un texto más largo termina el run con INVALID_BINDING antes de que se ejecute el paso, así que error.handle no puede encaminarlo. Un paso sin nada vinculado a a y b falla con TEXT_COMPARE_NOT_CONFIGURED; si solo uno está vinculado, el run termina con INVALID_BINDING. Se permiten textos vacíos: dos vacíos puntúan 1, uno vacío 0. La paleta dibuja el paso con un icono de diferencias, entre los pasos de datos.

app.workflows.nodes.text_compare._handler.TextCompareConfig

Bases: BaseModel

How to score the two texts and what to ignore while doing it.

app.workflows.nodes.text_compare._handler.TextCompareOutput

Bases: BaseModel

The score, how much changed, and what was counted.

html.parse

Parse HTML lee HTML como su texto, sus enlaces y los elementos que elige un selector CSS. Sirve para el HTML con el que se topa un flujo: el cuerpo de un correo, el cuerpo de un http.request, una página que guardó http.download. text.extract lee documentos, no marcado: devolvería las etiquetas como texto. Vincula html (como máximo 2.000.000 de caracteres) o file (como máximo 5.000.000 de bytes, comprobado antes de leer el archivo), no ambos. Un archivo se decodifica a partir de su marca de orden de bytes, su <meta charset> o sus bytes, con pérdida si hace falta, y su tipo declarado no se comprueba: un PDF se lee como texto ilegible y no se rechaza. Un carácter NUL se lee como U+FFFD.

El paso tiene tres salidas, todas acotadas:

  • text es el texto legible como Markdown, cortado en Most characters of text (100.000 por defecto; text_truncated indica que se cortó). Se conservan los encabezados, las listas y el código; no los scripts, los estilos, los menús (nav), los pies de página ni el <head>. El conector web de una colección de conocimiento lee una página igual. title es el <title> de la página, cortado a 500 caracteres, o null si la página no lo tiene (también si está vacío).
  • links contiene cada a[href] una vez, en el orden del documento, con el texto de su primer ancla (cortado a 500 caracteres). Un enlace se resuelve contra Base address cuando hay una, y se deja como está escrito cuando no. Los enlaces a direcciones javascript:, vbscript: y data: se omiten, para que un paso posterior que solicite cada enlace nunca reciba un script; también un href de más de 2.048 caracteres y uno que no se puede leer como dirección. Se conservan como máximo 500 enlaces y se miran las primeras 5.000 anclas; links_truncated indica que la página tenía más.
  • matches contiene los elementos que elige CSS selector, en el orden del documento, como máximo Most matches (20 por defecto, hasta 200); match_count indica cuántos eligió el selector antes del corte. Cada coincidencia tiene su text y su html exterior (ambos cortados a 10.000 caracteres) y sus primeros 50 attributes, con uno multivalor como class unido por espacios y cada valor cortado a 2.000 caracteres. Sin selector no hay coincidencias. Un selector que no es CSS válido se rechaza al publicar, indicando el campo.

No se descarga nada y no se ejecuta nada. No se solicita ninguna imagen, hoja de estilos, Base address ni enlace, un script es solo texto, y el analizador es el de la biblioteca estándar, que no es un analizador XML, así que no se expande ninguna entidad de fuera de la página.

El análisis corre en un proceso hijo con 20 segundos de tiempo real, un límite de CPU y un límite de memoria, porque un selector como p div div sobre marcado muy anidado puede tardar minutos. Pasados los 20 segundos se mata el hijo y el paso falla con HTML_TOO_COMPLEX, sin reintento. El trabajo por ancla y por coincidencia también está acotado, así que una sola página no puede volver lento el paso ni grande su salida.

Qué pasó Resultado
Sin ajustes o sin entrada HTML_PARSE_NOT_CONFIGURED
html de más de 2.000.000 de caracteres, o html y file vinculados a la vez INVALID_BINDING, antes de que se ejecute el paso
El archivo no existe o pasa de 5.000.000 de bytes FILE_NOT_FOUND, FILE_TOO_LARGE
Ningún decodificador lee los bytes, o el proceso lector muere HTML_NOT_READABLE, el detalle reason dice cuál
La página tarda más de 20 segundos HTML_TOO_COMPLEX

INVALID_BINDING termina el run antes de que se ejecute el paso, así que error.handle no puede encaminarlo; un archivo demasiado grande es el FILE_TOO_LARGE con el que falla el paso, y ese sí se puede encaminar. La paleta dibuja el paso con un icono de código, entre los pasos de datos.

app.workflows.nodes.html_parse._handler.HtmlParseConfig

Bases: BaseModel

What to pick out of the page and how to resolve its links.

app.workflows.nodes.html_parse._handler.HtmlParseOutput

Bases: BaseModel

The page's title and text, its links and what the selector picked.

Transform

Los pasos Transform transforman una lista de objetos sin un paso de código. Cada uno toma items, una lista vinculada desde un paso anterior, y la mayoría entrega items, así que se encadenan.

Paso Hace
transform.edit_fields Fija campos con expresiones JMESPath sobre cada item, quita campos o conserva solo los fijados
transform.sort Ordena por campos en orden, ascendente o descendente
transform.limit Conserva los primeros o los últimos elementos
transform.remove_duplicates Conserva el primero de cada grupo de elementos iguales en los campos indicados, o enteros
transform.aggregate Reúne los valores de cada campo de todos los elementos en una lista por campo, como values
transform.split_out Convierte una lista dentro de cada elemento en elementos propios
transform.summarize Cuenta, suma, promedia, halla el menor o el mayor, o cuenta valores distintos, por grupo
transform.date_time Ahora, o un value vinculado desplazado una cantidad, escrito en una zona horaria - la del workflow salvo que el paso indique otra
transform.crypto Calcula el hash o codifica en base64 el text vinculado, o crea un UUID o hex aleatorio

Un campo es una ruta con puntos, customer.email, y un elemento sin él nunca es un error. Sort lo pone al final, Remove duplicates trata "falta" como un valor propio, Aggregate y Summarize lo omiten, Split out conserva el elemento tal cual, y Edit fields fija null donde su expresión no encuentra nada. Crypto no es para secretos: nada en él usa una clave.

Encadenados, responden preguntas habituales sin un paso de código:

  • Los mejores leads, una vez cada uno - Remove duplicates por email, Sort por score descendente, Limit a 10, y luego Edit fields dejando solo name y score, listos para un mensaje.
  • Totales por región - Split out lines, para que cada línea de pedido sea un elemento, y luego Summarize la sum de amount agrupada por region: un elemento por región con sum_amount.
  • Una lista de direcciones - Aggregate email entrega values.email, todas las direcciones en una lista, para cualquier ajuste que acepte una lista.

Cada paso lee items del anterior. tests/integration/test_workflow_transform_composed.py ejecuta los dos primeros tal cual.

app.workflows.nodes.transform._handler.EditFieldsConfig

Bases: _Config

The fields to set and the fields to remove, on every item.

app.workflows.nodes.transform._handler.SummarizeConfig

Bases: _Config

app.workflows.nodes.transform._handler.DateTimeConfig

Bases: _Config

logic.if y logic.merge

logic.if evalúa una condición JMESPath sobre su value enlazado y sigue por el puerto true o false. Cada nodo de la rama no tomada queda registrado como skipped. value debe estar enlazado: publicar, y una ejecución de prueba del borrador, rechazan el paso sin él; una versión publicada antes sigue funcionando y lee null.

logic.merge vuelve a unir ramas. Por defecto espera a la rama que se ejecutó: se ejecuta cuando esa rama llega a él y pasa la salida de la rama como value. Publicar comprueba que eso es seguro y rechaza un merge cuyas entradas pueden ejecutarse en el mismo run. No pueden las entradas que salen de un mismo paso por puertos distintos, si ese paso toma exactamente un puerto en cada run - un If, un Switch, una decisión, ai.classify, una comprobación de tabla, una aprobación o un paso que encamina sus errores. Tampoco las ramas de varios de esos pasos que se excluyen entre sí, como un If anidado que comparte su else.

Un merge puede tomar dos o más puertos de ese paso directamente, sin ningún paso entre medias, como needs_reply y unsure de ai.classify: se ejecuta una vez, para el puerto que tomó el paso. Dos aristas del mismo puerto siguen rechazándose.

Las ramas que se ejecutan a la vez, como las patas de un Split, necesitan Wait for en All branches (wait_for: all). El merge se ejecuta entonces cuando cada entrada ha terminado y pasa la salida de cada rama en branches, un objeto por nombre de rama, en lugar de value. El nombre de una rama lo da su entrada de branch_keys: el editor pone una para cada conexión, a partir del nombre del paso o de la rama del Split, y un paso posterior lee branches.<nombre>. Publicar rechaza un nombre que nada tiene y dos ramas con el mismo nombre.

Publicar rechaza además All branches cuando una entrada no tiene la certeza de llegar siempre que el merge se ejecuta - con su nombre - y cuando solo una entrada puede ejecutarse, pues entonces hace falta The branch that ran. branches se guarda como el resultado de cualquier paso: uno por encima del límite inline hace fallar el paso con NODE_OUTPUT_TOO_LARGE, así que pasa los archivos como referencias.

Un grafo guardado antes que espera a todas las ramas y al que llegan solo algunas hace fallar el run con JOIN_INCOMPLETE, nombrando el merge. Publicar rechaza un grafo así; el código queda como red de seguridad. wait_for y branch_keys tienen valores por defecto, así que un merge guardado sin configuración sigue esperando a la rama que se ejecutó.

Cualquier paso con varias aristas de entrada es un join y no necesita merge. Se ejecuta cuando cada paso que lleva a él ha terminado, si al menos una arista está activa, y se omite si ninguna lo está. También se omite si una fuente falló sin camino de error: ese fallo ya es del run. Los bindings de un join solo pueden leer pasos que seguro se ejecutaron antes; un join puede leer cada pata de un grupo paralelo, pero no una rama de un If.

Una regla, categoría, rama u opción a la que das nombre se convierte en un puerto de salida, así que no puede repetir un puerto que su paso ya tiene: in, otherwise, other, default, approved y rejected en los pasos que los tienen, unsure en un clasificador con confianza mínima y error en un paso que encamina sus errores. El editor marca el nombre y publicar lo rechaza, indicando qué nombre choca y pidiendo que se cambie.

Las expresiones pueden seleccionar, filtrar y comparar, y llamar a un conjunto fijo de funciones puras: abs, avg, ceil, contains, ends_with, floor, join, keys, length, max, merge, min, not_null, reverse, sort, starts_with, sum, to_array, to_number, to_string, type y values. Null, false y una cadena, lista u objeto vacíos son falsos, y todo lo demás es verdadero, incluido 0. Una expresión que no se puede analizar, o que llama a otra cosa, no se puede publicar.

app.workflows.nodes.logic_if._handler.LogicIfConfig

Bases: BaseModel

The decision, as a JMESPath expression over {"value": ...}.

app.workflows.nodes.logic_merge._handler.LogicMergeConfig

Bases: BaseModel

How the merge waits, and what each branch is called when it waits for all.

app.workflows.nodes.logic_merge._handler.MergeBranchKey

Bases: BaseModel

The name one incoming branch goes by in branches.

app.workflows.nodes.logic_merge._handler.LogicMergeOutput

Bases: BaseModel

What reached the merge: the branch that ran, or every branch by name.

logic.split

Split. Envía el run por cada una de sus ramas a la vez. Cada rama tiene un nombre, que es su puerto de salida, y hay de dos a veinte. No se elige nada ni se lee ninguna condición: toda rama se toma siempre y los pasos que siguen se ejecutan a la vez. El paso no pasa nada, así que un paso de una rama se enlaza con lo que se ejecutó antes del Split. El nombre de una rama no puede ser in ni repetir un nombre que el paso ya tiene.

Un Split no puede encaminar sus errores, porque un Split que falló no inició ninguna rama: publicar rechaza on_error: route en él, y los pasos de cada rama encaminan los suyos. Las ramas vuelven a encontrarse en un logic.merge que espera a todas, o en cualquier paso que las tenga como entradas. Cualquier otro paso también puede enviar un puerto a varios pasos, que se ejecutan a la vez del mismo modo; un Split da nombre a las ramas y las mantiene a la vista.

app.workflows.nodes.logic_split._handler.LogicSplitConfig

Bases: BaseModel

The branches the run is sent down together.

logic.switch

Switch. Envía la ejecución por la primera de muchas ramas cuya regla se cumple. Cada regla tiene un nombre, que es el puerto de su rama, y una condición JMESPath sobre el value vinculado, probadas en orden; otherwise se lleva la ejecución cuando ninguna se cumple. value debe estar enlazado: publicar, y una ejecución de prueba del borrador, rechazan el paso sin él; una versión publicada antes sigue funcionando y lee null. Las ramas se reúnen en un logic.merge, que espera a la rama que se ejecutó; publicar lo acepta porque siempre se toma exactamente una. Una regla que falla con sus datos hace fallar el paso con CONDITION_FAILED y nombra la regla.

app.workflows.nodes.logic_switch._handler.LogicSwitchConfig

Bases: BaseModel

The rules, tried in order; the first that holds wins.

app.workflows.nodes.logic_switch._handler.LogicSwitchOutput

Bases: BaseModel

Which branch was taken, and the value that decided it, passed on unchanged.

Busca en colecciones de conocimiento una query enlazada y devuelve los fragmentos como fuentes tipadas, la mejor primero. Un resultado vacío es una búsqueda correcta. Una colección que ya no existe, o que el principal del run ya no puede leer, hace fallar el paso con COLLECTION_NOT_ACCESSIBLE. El paso nunca busca en menos colecciones de las que nombra el grafo.

app.workflows.nodes.knowledge_search._handler.KnowledgeSearchConfig

Bases: BaseModel

Which collections to search, and how many passages to return.

app.workflows.contracts.io.SourceRef

Bases: BaseModel

One retrieved passage, as a node hands it on rather than as a model reads it.

What knowledge.search produces and agent.run/core.output carry: typed fields a later node can bind to, not a formatted citation string. content is the matched chunk; the page and chunk locate it.

knowledge.add

Añade un texto enlazado con title, o un archivo del run, a entre una y cinco colecciones de conocimiento, con la misma subida que usa la página de una colección: sus propios ajustes de parsing y troceado, la comprobación del presupuesto de la organización y una ingesta encolada en lugar de esperada. Un texto se guarda como <title>.md, y un documento con el mismo nombre se reemplaza en lugar de duplicarse, así que un reintento lo actualiza. Una colección a la que el principal no puede añadir hace fallar el paso con KNOWLEDGE_ADD_REFUSED.

app.workflows.nodes.knowledge_add._handler.KnowledgeAddConfig

Bases: BaseModel

Which collections the document goes into.

Pasos de modelo

Cuatro pasos preguntan una cosa a uno de los modelos de la organización, sin herramientas. Cada uno se ejecuta como el agent propio del paso - creado en su primer run y publicado de nuevo cuando cambian sus ajustes - con el mismo runner que agent.run, así que el max_cost_usd del paso, el presupuesto del workflow y el límite de la organización se comprueban antes de la petición, su coste se carga al run y aparece en Activity. Publicar comprueba que el autor puede usar el modelo.

Paso Hace Pasa
ai.prompt Responde a un prompt enlazado según las instructions del paso text, y structured cuando structured_output_schema pide un objeto
ai.classify Pone un text enlazado en una de las categories del paso, o en other Sale por el puerto de la categoría, con la category, un reason de una frase, el text y confidence; por debajo de min_confidence, por unsure
ai.extract Saca de un text enlazado los campos que nombra output_schema, omitiendo lo que no dice data, un objeto con esa forma
ai.rewrite Resume (short, medium, long), traduce a language o reescribe un text enlazado con un style text

Las categorías de un clasificador son sus puertos, en orden, y el modelo solo puede responder con uno de sus nombres o other. Una respuesta que no es el objeto que pidió el paso lo hace fallar con STRUCTURED_OUTPUT_MISMATCH; un límite agotado, con AGENT_BUDGET_EXCEEDED.

Con min_confidence fijado, el modelo informa además de su propia confianza en la categoría, de 0 a 1. Es lo que él dice, no una probabilidad, y no está calibrada: prueba un umbral con tus propios casos usando los evals del paso antes de fiarte de él. El mínimo añade un puerto unsure después de other: por debajo de él el paso sale por ese puerto, con la misma salida, así que la category sigue disponible para el workflow.

Sin mínimo no se pide un número al modelo, el prompt no cambia, confidence es null y siempre gana la categoría. Una respuesta sin número nunca sale por unsure, y mientras exista el puerto, unsure no puede ser el nombre de una categoría.

El paso sale por exactamente un puerto, así que un logic.merge puede volver a unir sus ramas: llevar unsure y una categoría por el mismo camino, o dos categorías a un mismo paso de registro.

app.workflows.nodes.ai_classify._handler.AiClassifyConfig

Bases: ModelStepConfig

The categories, in order, and anything else the model should know.

app.workflows.nodes.ai_classify._handler.AiClassifyOutput

Bases: BaseModel

The category chosen, why, and the text passed on unchanged.

confidence is filled in only when the step has a min_confidence, and unsure is true when it fell below that minimum, so the step left by its unsure port.

Decisiones

Tres pasos hacen a Jev, de TypeSafe, una pregunta tipada sobre un text enlazado, con una clave de API de TypeSafe del vault. Jev no escribe texto: responde la pregunta con una confianza de 0 a 1, en una sola petición, y solo puede responder con una de las respuestas que el paso permite. Por debajo del min_confidence del paso, sale por su puerto unsure, así que el workflow decide ahí qué hace una persona o un agent con un caso dudoso.

decide.* solo llama al Jev alojado de TypeSafe. Para encaminar los casos dudosos con un modelo autoalojado usa ai.classify con min_confidence, que sale por unsure de la misma manera.

Paso Pregunta Sale por
decide.yes_no una pregunta de sí o no yes, no o unsure
decide.choose cuál de hasta 255 opciones encaja out con la choice, o unsure
decide.score dónde queda el texto en una escala de 2 a 10 niveles out con el score, o unsure

La clave se comprueba al publicar y se vuelve a leer en cada run. Una clave que ya no existe o no está compartida hace fallar el paso con SECRET_NOT_USABLE, un modelo que no responde con DECISION_FAILED, que se reintenta según la política del paso, y un despliegue construido sin el extra browser con DECISION_MODEL_UNAVAILABLE. Un merge puede volver a unir las ramas de una decisión, como las de un If.

app.workflows.nodes._decide.DecisionConfig

Bases: BaseModel

The question, the model that answers it, and how sure it must be.

app.workflows.nodes.decide_choose._handler.ChooseConfig

The question, and the options it is answered from.

app.workflows.nodes.decide_score._handler.ScoreConfig

The question, and what each level of the rubric means, lowest first.

Canales

Slack, Mattermost y Telegram tienen cada uno su propio grupo de pasos, que actúan como uno de los bots de la organización en esa plataforma a través del mismo adaptador por el que van sus respuestas, así que un mensaje que envía un workflow llega de ese bot, y un paso puede leer lo que el bot puede leer. Una plataforma solo tiene los pasos que sus bots pueden dar.

Paso Slack Mattermost Telegram Pasa adelante
Send a message (<platform>.message.send) sí sí sí dónde se envió
Read messages (<platform>.messages.read) sí sí - messages, del más antiguo
List members (<platform>.members.list) sí sí administradores members, con sus ids de la plataforma
Find channels (<platform>.channels.find) sí sí - channels

Un paso solo acepta un bot de su propia plataforma, y un bot habla por toda la organización, así que actuar como él requiere channels:manage: al autor del grafo para publicar, y al principal del run en cada run. Un bot borrado, apagado o de otra plataforma hace fallar el paso con CHANNEL_NOT_USABLE. Una plataforma que rechaza una llamada al ejecutarse lo hace fallar con CHANNEL_UNSUPPORTED, y una que no responde, con CHANNEL_CALL_FAILED. El envío nunca se repite solo.

Send a message responde dentro de un hilo cuando thread_id está definido: la marca de tiempo del hilo en Slack, el id de la publicación raíz en Mattermost o el número de un tema de foro en Telegram. El mensaje se publica en el hilo, nunca como respuesta citada. Un thread_id vacío publica en el propio canal. Un id de canal o de hilo con un :, o un hilo de Telegram que no es un número, hace fallar el paso con CHANNEL_TARGET_INVALID antes de enviar nada.

app.workflows.nodes._channels.ChannelBotConfig

Bases: BaseModel

Which bot the step acts as.

app.workflows.nodes.channel_read._handler.ChannelReadConfig

The bot, and how many messages to read.

app.workflows.nodes.channel_read._handler.ChannelReadOutput

Bases: BaseModel

The messages, oldest first.

app.workflows.nodes.channel_members._handler.ChannelMembersOutput

Bases: BaseModel

The people in the channel.

app.workflows.nodes.channel_find._handler.ChannelFindOutput

Bases: BaseModel

The channels that match.

agent.run

Pregunta a un agent publicado, en la versión exacta que fija el paso, a través del mismo runner que el chat y la API, con el presupuesto, las aprobaciones, los guardrails y el historial de runs del agent. El run se registra con la superficie workflow. Las sources enlazadas se añaden al prompt como contexto numerado. Una llamada a una herramienta que requiere aprobación detiene el paso, y la decisión reanuda el mismo run del agent.

inline configura el agent en el paso en lugar de agent, y solo uno de los dos está fijado. La spec es del paso y se versiona con el grafo: el primer run crea un agent propiedad del workflow, fuera del catálogo y alcanzable mediante el acceso al workflow, y un run cuya spec difiere de la versión más reciente de ese agent publica primero una nueva, comprobada como cualquier publicación de un agent. Publicar el workflow comprueba la spec con el acceso de su autor. max_cost_usd limita una ejecución: el agent se detiene antes de una petición al modelo que lo supere, y el paso falla con AGENT_BUDGET_EXCEEDED.

Un agent con un formato de respuesta propio pasa su objeto como structured. structured_output_schema pide otra forma en su lugar: el agent se ejecuta con ese esquema, y una respuesta que lo incumple vuelve al modelo para corregirla. El objeto se comprueba una vez más antes de que se ejecute nada después. Un agent que nunca produce uno que encaje hace fallar el paso con AGENT_RUN_FAILED, y una respuesta sin objeto donde se pidió uno, con STRUCTURED_OUTPUT_MISMATCH.

Cómo terminó el run del agent Resultado del paso
Completed Completed
Esperando aprobación Espera y luego reanuda el mismo run
Presupuesto superado AGENT_BUDGET_EXCEEDED
Bloqueado por un guardrail AGENT_GUARDRAIL_BLOCKED
En otro caso AGENT_RUN_FAILED

Los attachments vinculados son imágenes de los archivos del run - una descarga, una página de PDF renderizada, una foto transformada - que se muestran al agent como imágenes y no como un enlace que no puede abrir. Solo se muestran PNG, JPEG, WebP y GIF. Cualquier otro archivo falla con UNSUPPORTED_ATTACHMENT_TYPE, así que lee primero el texto de un documento con text.extract.

Nunca se reintenta automáticamente, porque un agent puede haber llamado a herramientas con efectos secundarios.

app.workflows.nodes.agent_run._handler.AgentRunConfig

Bases: BaseModel

Which agent answers - pinned from the catalog or configured here - and how.

app.workflows.nodes.agent_run._handler.AgentRunOutput

Bases: BaseModel

The agent's answer, the sources it was given, and its structured value.

http.request

Llama a una API HTTP. Cada petición y cada redirección pasa la comprobación SSRF del despliegue y se envía a la dirección que esa comprobación aprobó. Un host privado que el operador haya listado en OUTBOUND_PRIVATE_ALLOWLIST también se admite, pero solo para una URL que empieza ahí: una redirección desde una URL pública nunca llega a él.

Una URL enlazada desde un disparador público (un webhook, un formulario, un correo) o desde los argumentos de una herramienta de un agente puede nombrar cualquier host de la lista, así que lista solo hosts que comprueben su propia credencial.

Una credencial es una HTTP credential del vault, y solo se envía a los orígenes que permite el secreto. La respuesta se lee dentro de max_response_bytes y se devuelve sin Set-Cookie, sin las cabeceras de autenticación y sin el token. Una respuesta HTML vuelve como texto en body; vincúlala a html.parse para leer su texto, enlaces y elementos.

La credencial va como token bearer, autenticación Basic, una cabecera con el nombre que indiques o, con query, un parámetro de URL con el nombre que indiques. Import cURL en el editor lee un comando pegado de la documentación de una API en el método, la URL, las cabeceras y el cuerpo JSON. Una credencial del comando nunca se queda en el paso: el paso queda configurado para enviarla igual, y el formulario del vault se abre con ella rellenada.

Un GET puede recorrer una lista por páginas con pagination: siguiendo una URL siguiente que nombra la respuesta, devolviendo un cursor o contando un parámetro de página. Los elementos de items_path de cada página se reúnen en items, en orden. La paginación se detiene cuando no hay página siguiente, cuando una página no tiene elementos o en max_pages, con complete en falso. Todas las páginas juntas leen como mucho max_response_bytes, y una página siguiente en otro origen no recibe la credencial.

Qué pasó Resultado
La URL es loopback, metadata, no es http(s) o es privada y no está en la OUTBOUND_PRIVATE_ALLOWLIST del operador URL_REFUSED, no se envía nada
La URL está fuera de los orígenes de la credencial SECRET_ORIGIN_DENIED, no se envía nada
La conexión nunca se abrió HTTP_UNREACHABLE, se reintenta
Enviada, sin respuesta, GET o cabecera de idempotencia HTTP_NO_RESPONSE, se reintenta
Enviada, sin respuesta, cualquier otra escritura Incierto: el run se detiene para una persona
Distinto de 2xx HTTP_ERROR_STATUS, o la respuesta como salida con on_error_status: complete
Mayor que el límite RESPONSE_TOO_LARGE
El items_path de una página encuentra algo que no es una lista PAGE_ITEMS_NOT_A_LIST

app.workflows.nodes.http_request._handler.HttpRequestConfig

Bases: BaseModel

Where to send the request, how, and what to accept back.

app.workflows.nodes.http_request._handler.HttpAuth

Bases: BaseModel

How the request authenticates, with a vault secret rather than a value.

app.workflows.nodes.http_request._handler.HttpPagination

Bases: BaseModel

How a GET fetches its next page, and when it stops.

next_url follows a URL the response names, cursor sends back a cursor it names in the param query parameter, and page counts param up from first_page. Each is read with a JMESPath expression over the page's body. The items items_path finds on every page are collected, in order; paging stops when there is no next page, when a page has no items, or at max_pages.

app.workflows.nodes.http_request._handler.HttpRequestOutput

Bases: HttpResponseOutput

What came back - the last page's, when it paged - and every page's items.

mcp.call

Call an MCP tool llama a una herramienta en uno de los servidores MCP de la organización, con arguments enlazados desde pasos anteriores. El editor ofrece los servidores con la marca de su servicio y luego las herramientas que el servidor listó en su última prueba; Refresh tools lo vuelve a probar. Cada argumento de la herramienta es entonces un campo propio, escrito o enlazado como arguments/<name>. Solo sirve un servidor de la organización, nunca el propio de un miembro, y publicar rechaza un servidor que no es de la organización, está apagado o no listó la herramienta. La credencial sale del vault, como para un agent.

approval decide cuándo una persona aprueba antes la llamada: writes, por defecto, antes de cualquier herramienta que el servidor no marque como de solo lectura; all antes de cada llamada; none nunca. La petición va a la pestaña Approvals, como con human.approval, a approvers o a quien tenga approvals:decide. Aprobada, se llama a la herramienta; rechazada, el paso falla con MCP_CALL_REJECTED. La marca de solo lectura es una afirmación del propio servidor, así que elige all para un servidor en el que no confías para hacerla.

La salida es el text de la respuesta, sus datos structured cuando la herramienta los devuelve, e is_error. Un error con el que responde la herramienta hace fallar el paso con MCP_TOOL_ERROR salvo que on_tool_error sea complete.

MCP no tiene una clave de idempotencia propia. Un servidor que acepta una como argumento de la herramienta puede hacerse seguro ante reintentos igualmente: pon Idempotency key argument (idempotency_key_argument) con el nombre de ese argumento, y el paso enviará ahí su clave de operación estable - la misma en cada intento del paso y distinta en cada iteración de un bucle -, sea cual sea el valor que se le hubiera enlazado. La herramienta debe declarar el argumento como texto.

Publicar rechaza un argumento que falta en el esquema registrado de la herramienta; una herramienta sin esquema registrado pasa, así que prueba antes el servidor. En ejecución el paso lista las herramientas del servidor antes de llamar y falla con MCP_IDEMPOTENCY_ARGUMENT_UNDECLARED, sin llamar a nada, cuando el esquema vigente no tiene ese argumento. Un paso con clave cuenta como idempotente: el motor puede ejecutarlo de nuevo.

Cuando la llamada se corta, lo que sigue depende de si pudo llegar al servidor:

  • Antes de la llamada - no se pudo llegar al servidor, o no respondió a la conexión ni a la lista de herramientas - no se envió nada y el paso falla con un MCP_CALL_FAILED que se reintenta.
  • Tras la llamada, con clave o con una herramienta que el servidor marca como de solo lectura (en la lista que el paso acaba de leer, o si no en la registrada al probar el servidor) - el mismo fallo reintentado, porque repetirla es seguro.
  • Tras la llamada, cualquier otra herramienta, sin clave - la herramienta pudo actuar y no llegó respuesta. El paso termina como Uncertain con The tool was called and no answer came back; it may have acted, y el run espera a una persona, que mira en el otro sistema antes de volver a ejecutar el paso.
Qué pasó Resultado
El servidor ya no existe o está apagado MCP_SERVER_UNAVAILABLE, nada enviado
Hay que volver a autorizar su credencial MCP_SERVER_UNAUTHORIZED, nada enviado
El servidor no ofrece la herramienta MCP_TOOL_NOT_FOUND
No se pudo llegar al servidor, o cortó antes de una llamada segura o después MCP_CALL_FAILED, reintentado
Cortó tras una llamada sin clave a una herramienta no conocida como de solo lectura Uncertain: el run se detiene para una persona
La herramienta no declara el Idempotency key argument MCP_IDEMPOTENCY_ARGUMENT_UNDECLARED, nada enviado
La herramienta respondió con un error MCP_TOOL_ERROR, o la respuesta como salida
La respuesta estructurada supera 1 MiB MCP_RESULT_TOO_LARGE
La respuesta estructurada no es JSON MCP_RESULT_INVALID
El paso no nombra servidor ni herramienta MCP_CALL_NOT_CONFIGURED, nada enviado
La herramienta no está permitida en esta conexión MCP_TOOL_NOT_ALLOWED, nada enviado
Un aprobador nombrado ya no puede decidir aprobaciones APPROVER_UNAVAILABLE, nada enviado
La llamada no fue aprobada MCP_CALL_REJECTED, nada enviado

app.workflows.nodes.mcp_call._handler.McpCallConfig

Bases: BaseModel

Which server, which tool, and when a person approves the call first.

app.workflows.nodes.mcp_call._handler.McpCallOutput

Bases: BaseModel

What the tool answered.

notification.send

Notifica a miembros de la organización en la aplicación, por email o por ambos, a través del centro de notificaciones. Los destinatarios son miembros indicados por id. En el momento de ejecutarse, cada uno debe seguir siendo un miembro activo que puede ver el workflow, y los demás se descartan. Si no queda nadie, el paso falla con NO_PERMITTED_RECIPIENTS. Las preferencias de cada persona para Workflow notifications siguen aplicándose. El paso termina cuando la notificación queda escrita, y el email se entrega después. Un paso reintentado no escribe una segunda notificación.

app.workflows.nodes.notification_send._handler.NotificationSendConfig

Bases: BaseModel

Who to tell, the headline, and which channels it may use.

email.send

Los proveedores que envían reservan cada destinatario único mediante límites Redis compartidos antes de enviar el lote completo: WORKFLOW_EMAIL_MAX_PER_PRINCIPAL_PER_HOUR (100), WORKFLOW_EMAIL_MAX_PER_ORG_PER_HOUR (500) y WORKFLOW_EMAIL_MAX_PER_DEPLOYMENT_PER_HOUR (1000). El límite del usuario abarca sus claves y organizaciones. Las ventanas fijas de una hora abarcan ejecuciones y workers; en su frontera puede admitirse el doble. Agotar el límite produce EMAIL_DELIVERY_LIMIT; contadores no disponibles, EMAIL_ALLOWANCE_UNAVAILABLE, sin envío. Las reservas son conservadoras y no se devuelven tras una reserva parcial, un fallo del proveedor o una cancelación. Los proveedores que solo registran no consumen cuota. Configura el límite global por debajo de la cuota SMTP para dejar margen al correo transaccional.

Send an email envía un correo a hasta diez direcciones desde el remitente y el proveedor de correo del despliegue, una copia a cada una, para que ningún destinatario vea la dirección de otro. Es para personas de fuera de la organización, como un lead o un cliente: a los miembros los alcanza notification.send según sus preferencias. to, subject y el texto plano body se escriben o se enlazan desde pasos anteriores, y reply_to manda las respuestas a otro sitio que la dirección de respuesta del despliegue.

La salida lista las direcciones que el proveedor aceptó. delivered es false cuando el proveedor solo registra el correo, lo predeterminado en desarrollo, así que un run nunca afirma haber llegado a alguien a quien no llegó. Una copia que el proveedor rechaza hace fallar el paso con EMAIL_NOT_SENT, nombrando lo ya enviado. El correo no tiene clave de idempotencia, así que un paso reintentado vuelve a enviar.

app.workflows.nodes.email_send._handler.EmailSendConfig

Bases: BaseModel

Which SMTP account sends it, and where replies go.

app.workflows.nodes.email_send._handler.EmailSendInput

Bases: BaseModel

Who it goes to, and what it says - typed in or bound from earlier steps.

app.workflows.nodes.email_send._handler.EmailSendOutput

Bases: BaseModel

Who the provider accepted the mail for, and whether it actually leaves.

human.approval

Ask for approval detiene el run hasta que una persona apruebe o rechace lo que está a punto de hacer, y sigue por approved o rejected. Quien aprueba lee el title del paso y los details vinculados en la pestaña Approvals de Activity o por GET /api/v1/workflow-approvals. approvers nombra quién puede decidir, y esas personas reciben un aviso en la aplicación; vacío, puede cualquiera con approvals:decide. Pasado timeout_hours la solicitud caduca y el paso sale por rejected con decision: "expired". Cancelar el run cancela lo que pidió. Cada ejecución del paso pregunta una vez, así que un reintento o una iteración de un bucle nunca preguntan dos veces lo mismo.

Los aprobadores nombrados deben ser miembros activos con approvals:decide. Se comprueba al publicar y de nuevo antes de pedir la aprobación. Si han perdido ese permiso, el paso falla con APPROVER_UNAVAILABLE en lugar de dejar una solicitud que nadie puede resolver.

Un paso puede llevar una propuesta: texto vinculado a proposal, como una respuesta redactada. Quien aprueba la lee junto a la solicitud. Con editable activado, la cola la muestra en un editor, como text o markdown (proposal_format), bajo proposal_label (Proposal si está vacío), y quien aprueba puede aprobar una versión modificada. Un paso sin nada vinculado abre un editor vacío, para el caso en que la redacción no produjo nada. details no cambia y sigue siendo corto; la propuesta puede tener hasta 100 000 caracteres.

La salida aprobada lleva tres campos además de la decisión:

  • final es la versión que la persona aprobó, la propia propuesta si no cambió nada.
  • original es la propuesta tal como se pidió, "" cuando un paso editable no tenía nada vinculado.
  • edited es verdadero cuando final difiere de original. Los finales de línea, los espacios al final de la línea y el espacio en blanco alrededor del texto no cuentan como diferencia.

Una versión editada se guarda sin espacios en blanco al principio ni al final, así que una edición que empieza con un bloque de código con sangría pierde esa primera sangría.

Una solicitud aprobada de un paso editable también registra su razón de edición (edit_ratio), cuánto cambió quien aprobó la propuesta, de 0 a 1. Las estadísticas del workflow la informan junto con los demás resultados de aprobación.

Un paso que no es editable muestra su propuesta de solo lectura y, una vez aprobada, la pasa sin cambios como final con edited en false, de modo que los pasos posteriores pueden vincular final sea cual sea el modo del paso. Un rechazo o una caducidad no lleva final. Enviar una versión editada a un paso que no es editable, o junto con un rechazo, se rechaza con 422. Quien puede decidir pero no está nombrado en el paso ve la propuesta de solo lectura.

Un paso puede ofrecer opciones (choices) en lugar de una aprobación simple, cada una una forma de aprobar: por ejemplo, conservar un borrador o enviarlo de inmediato. Se pueden dar hasta cinco, cada una con un id (minúsculas, dígitos y guiones bajos, que no sea in, approved ni rejected) y un label de hasta 40 caracteres. La cola muestra un botón por opción en lugar de Approve, con la etiqueta tal como se escribió y sin traducir, y Reject se queda. El paso tiene entonces un puerto de salida por opción, en orden, después rejected, y no tiene puerto approved: una arista desde approved se rechaza al publicar el workflow.

La opción de quien aprueba es el puerto por el que sale el paso, y su id es choice en la salida. Es null cuando el paso no tiene opciones, en un rechazo y en una caducidad. Un paso sin opciones no cambia: tiene approved y rejected. Aprobar un paso con opciones exige una de ellas como choice, y dar choice en cualquier otra decisión se rechaza con 422. Las estadísticas no se dividen por opción.

Si el workflow envía el final aprobado por mcp.call, pon el approval de ese paso en none, o se pregunta dos veces a la persona.

app.workflows.nodes.human_approval._handler.HumanApprovalConfig

Bases: BaseModel

What the approver is asked, who may decide, and for how long the run waits.

app.workflows.nodes.human_approval._handler.HumanApprovalInput

Bases: BaseModel

What the approver reads before deciding, bound from an earlier step.

app.workflows.nodes.human_approval._handler.HumanApprovalOutput

Bases: BaseModel

The decision, and who made it.

flow.wait

Wait. Detiene la ejecución en el paso durante seconds desde que se alcanza, o hasta un until vinculado, y luego sigue; como mucho treinta días. El paso queda aparcado en un reloj cuya fila de despacho vence entonces, así que la espera sobrevive a un reinicio del worker y no ocupa ninguno, y las demás ramas siguen mientras tanto. Un momento ya pasado sigue enseguida, y el plazo de la ejecución sigue vigente. Un until vinculado a más de treinta días desde que se llega al paso hace fallar el paso con WAIT_TOO_LONG.

Con Wait for a call to the run's resume link, el paso espera en cambio hasta que se llama la dirección que dio un paso Resume link, como mucho seconds (treinta días si no se indica). Envía a esa dirección un POST con un objeto JSON o sin contenido: el paso entrega el contenido como body, con called a true, y la ejecución sigue. Si nadie llama a tiempo, sigue con called a false. Una llamada cuando nada espera - antes de llegar al paso, después de que siguiera o cuando la ejecución terminó - se rechaza con WORKFLOW_NOT_WAITING (409), y un enlace que no es de la ejecución responde 404.

app.workflows.nodes.flow_wait._handler.FlowWaitConfig

Bases: BaseModel

How long to wait, when no time to wait until is bound, and whether for a call.

app.workflows.nodes.flow_wait._handler.FlowWaitOutput

Bases: BaseModel

When the wait ended, and what a call to the resume link sent.

Resume link. Entrega url, la dirección que reanuda los pasos Wait de esta ejecución que esperan una llamada - para un mensaje o una petición antes del Wait, de modo que quien responda pueda seguir con la ejecución. Cada ejecución tiene la suya, hecha con su id bajo el secreto del despliegue y no mostrada a nadie más: tenerla es lo que permite reanudar la ejecución, así que se envía solo adonde debe llegar la respuesta.

app.workflows.nodes.flow_resume_link._handler.ResumeLinkOutput

Bases: BaseModel

The run's resume link.

Gestión de errores

Cada nodo acepta una policy opcional junto a su configuración.

Campo Por defecto Efecto
timeout_seconds ninguno Una llamada que sigue en marcha pasado ese tiempo se corta. Un paso sin escritura externa, o cuya llamada es idempotente, falla con NODE_TIMEOUT y puede reintentarse. Una escritura que puede haber llegado queda incierta y se detiene para una persona
retry.max_attempts WORKFLOW_RETRY_CEILING Intentos en total, el primero incluido. Solo se reintenta un fallo que el nodo marca como retryable, y publicar rechaza más de un intento para un paso cuya llamada no es seguro repetir
retry.backoff, base_delay_seconds, max_delay_seconds exponential, 2, 60 La espera entre intentos: fija, o duplicándose hasta el techo
on_error fail_run route envía un fallo que los reintentos no resolvieron por el puerto error del nodo en lugar de hacer fallar el run

Un nodo cuya política encamina sus errores tiene un puerto de salida error adicional. Lleva el WorkflowError: code, message, details y retryable. La salida normal del nodo solo existe en sus otros puertos, así que publicar rechaza un binding que lee la salida en el camino de error, o el error en el camino de éxito. El puerto de error tiene que llevar a algún sitio, y los dos caminos pueden volver a unirse en un logic.merge.

Cuando los pasos se ejecutan en paralelo, cada rama tiene su propio handler. Una rama que falla sin camino de error hace fallar el run, y los pasos que están a su lado no empiezan nada más: se retiran sus aprobaciones y temporizadores pendientes, y también los runs de agent en que están aparcados.

Un paso cuyo trabajo ya estaba en marcha termina y queda registrado como terminado tras la parada del run, con su salida conservada. Un reintento del run lo reutiliza en lugar de hacerlo otra vez, y un paso Output o Respond to webhook reutilizado vuelve a informar de su respuesta. Publicar rechaza un error.handle alimentado por dos pasos que pueden fallar en el mismo run.

Un paso que necesita atención se puede resolver en el run en que está. Tratarlo como fallido lo termina con STEP_MARKED_FAILED, que sale por su puerto error si el paso encamina sus errores, o pasa a la política de errores de elemento de su bucle. Hacer fallar el run lo termina con ese error, se conecte como se conecte el grafo. Cancelar un run, o que falle, cierra también un paso que necesita atención.

error.handle recibe ese error en su puerto in y sale por la primera rama cuyos code y retryable coinciden, o por default, que debe estar conectada. error.raise hace fallar su rama con un código, un mensaje y unos detalles que fija el autor.

Algunos fallos nunca se encaminan. Un acceso revocado, un budget agotado (del run o de un agent), un run cancelado, un plazo vencido, el límite de nodos por run y un efecto de resultado desconocido terminan el run se conecte como se conecte el grafo. Un conflicto de revisión o un error de validación se puede encaminar, pero nunca se reintenta a ciegas, porque la misma entrada vuelve a fallar igual.

app.workflows.contracts.policy.NodePolicy

Bases: BaseModel

What happens around one node's call: its time limit, its retries, its failure.

app.workflows.contracts.policy.RetryPolicy

Bases: BaseModel

How many tries a failing step gets, and how long it waits between them.

delay_seconds

delay_seconds(failures)

The wait after the failures-th failed try.

app.workflows.nodes.error_handle._handler.ErrorHandleConfig

Bases: BaseModel

The branches, tried in order; the first match wins.

app.workflows.nodes.error_handle._handler.HandledError

Bases: BaseModel

The error that arrived, and the branch it was sent down.

app.workflows.nodes.error_raise._handler.ErrorRaiseConfig

Bases: BaseModel

The error this step fails with.

Bucles

control.foreach ejecuta su cuerpo una vez por cada elemento de una lista items enlazada, un elemento cada vez y en orden. Después continúa por su puerto done con results en el orden de entrada, errors y count. El cuerpo empieza en loop.item, conectado desde el puerto body del bucle, que ofrece item, index y count. Termina en loop.yield, cuyo value enlazado es el resultado de la iteración. Ninguna arista vuelve al bucle. Un paso del cuerpo puede enlazarse a cualquier cosa que se ejecutara antes del bucle, y nada fuera del cuerpo puede enlazarse a su interior.

La lista se congela cuando arranca el bucle, así que una iteración nunca ve una fuente que cambió durante el run. Una lista más larga que WORKFLOW_FOREACH_MAX_ITEMS, o más grande que WORKFLOW_FOREACH_MAX_MANIFEST_BYTES, se rechaza en lugar de truncarse. Una lista vacía da results: [] sin ejecutar el cuerpo. Los pasos de cada iteración se ejecutan en su propio ámbito, con sus propios intentos, claves de idempotencia y costes. La siguiente iteración se programa en la transacción que termina la anterior, así que un reinicio continúa en el índice correcto y nunca repite una escritura confirmada. Una aprobación dentro de una iteración reanuda esa iteración.

Con item_error_policy: stop, el valor por defecto, el bucle falla en la primera iteración fallida y los detalles del error llevan el scope_path de esa iteración. Con collect, el resultado del elemento es null, el error se añade a errors y el bucle sigue. Los bucles se anidan como mucho WORKFLOW_FOREACH_MAX_DEPTH niveles, y cada ejecución de nodo que crea un run cuenta para WORKFLOW_RUN_MAX_NODE_RUNS. No hay bucle while ni map paralelo.

Un cuerpo puede ramificarse en patas paralelas, que se ejecutan a la vez dentro de su iteración, y una iteración termina cuando ninguno de sus pasos sigue activo. Cuando un paso de una iteración falla sin camino de error, collect deja que sus vecinos de esa iteración terminen y pasa al siguiente elemento. stop los cancela, retira sus aprobaciones, temporizadores y runs de agent aparcados, y hace fallar el bucle. La siguiente iteración nunca empieza mientras una anterior tenga aún un paso en ejecución.

app.workflows.nodes.control_foreach._handler.ForeachConfig

Bases: BaseModel

What a failed iteration does to the loop.

app.workflows.nodes.control_foreach._handler.ForeachOutput

Bases: BaseModel

Every iteration's result, in input order, and the ones that failed.

app.workflows.nodes.loop_item._handler.LoopItemOutput

Bases: BaseModel

The current element, where it sits in the list, and how long the list is.

Virtual Tables

Siete nodos leen y escriben Virtual Tables a través del mismo servicio que usan la consola, la API y las herramientas de tablas de un agent. La validación, los conflictos de revisión, las cuotas, el historial, los recibos y la auditoría son los mismos en cada superficie.

Nodo Hace Efecto
table.record.create Añade un registro write
table.record.upsert Crea o actualiza el registro con un external id write
table.record.update Cambia algunas celdas de un registro write
table.record.delete Borra un registro y conserva su historial write
table.record.get Busca un registro por id o external id read
table.record.query Lee una página de registros, filtrada y ordenada read
table.create Crea una tabla nueva con un esquema tipado write

Cada nodo de registros fija su tabla en la configuración. Al publicar se comprueba contra el autor del grafo, y en cada run se vuelve a comprobar como el principal del run. Los valores se enlazan y se indican por id o por etiqueta de columna. Un registro vuelve con sus valores dos veces: values por id de columna, para los bindings, y fields por etiqueta, para leer. Una clave que no nombra ninguna columna viva falla con UNKNOWN_COLUMN.

Una escritura lleva la clave de operación del paso, así que un paso reintentado repite su primera escritura. Un update, upsert o delete sin revisión enlazada escribe en la revisión actual del registro, leída bajo el bloqueo del registro, y la repetición se mantiene aunque la primera escritura ya haya movido esa revisión. Una revisión que se movió es REVISION_CONFLICT, que no se reintenta: la misma revisión volvería a chocar, así que encamínalo con error.handle hacia una lectura nueva. Un registro que falta es found: false de table.record.get, no un fallo. table.record.query lee como mucho 100 registros por página e indica has_more. Nunca lee por su cuenta una tabla grande entera.

table.create es un nodo propio y necesita tables:create. Su salida lleva la tabla nueva como una referencia a la que se puede enlazar el table de un nodo posterior, y el id de cada columna por etiqueta. Una tabla que lee o escribe un workflow vivo, o una columna que fija, no se puede archivar mientras la versión actual de ese workflow la use.

Cuatro pasos más leen qué tablas tiene la organización, y no cambian nada.

Paso Hace Sale por
table.list enumera las tablas que ve el principal, buscadas por nombre out, con tables y total
table.describe lee el nombre y las columnas de una tabla out, con columns
table.exists si existe una tabla con exactamente este nombre yes, con su id, o no
table.record.exists si algún registro coincide con los filtros yes, con el id del primero, o no

table.exists compara el nombre entero sin distinguir mayúsculas, así que un workflow puede crear su tabla la primera vez que se ejecuta y reutilizarla después. Las dos preguntas salen por exactamente un puerto, y un logic.merge puede volver a unir las ramas.

app.workflows.nodes.table_list._handler.TableListOutput

Bases: BaseModel

The tables found, and how many match in all.

app.workflows.nodes.table_describe._handler.TableDescribeOutput

Bases: BaseModel

The table and its live columns, in order.

app.workflows.nodes.table_record_exists._handler.TableRecordExistsConfig

Bases: BaseModel

Which table, and what a record must match.

app.workflows.nodes._tables.TableRecordOutput

Bases: BaseModel

One record as a step hands it on: by column id for bindings, by label to read.

app.workflows.nodes.table_record_get._handler.TableRecordLookup

Bases: BaseModel

Whether the record exists, and it when it does.

app.workflows.nodes.table_record_query._handler.TableRecordQueryConfig

Bases: BaseModel

Which records, in what order, and how many.

app.workflows.nodes.table_create._handler.TableCreateConfig

Bases: BaseModel

The new table: its name, what it is for, its columns, who sees it.

app.workflows.nodes.table_create._handler.TableCreatedOutput

Bases: BaseModel

The table made, as a reference later steps bind to, and its columns by label.

Archivos

Un archivo que crea un paso se guarda como archivo de su run y se pasa como FileRef: un id, el tipo que resultaron ser sus bytes y un tamaño. Un paso lee un archivo solo si lo creó su propio run o el run se inició con él - un FileRef vinculado en el grafo, comprobado al publicar contra un run que el autor puede ver. Cualquier otro archivo, de otra organización o de otro run, es FILE_NOT_FOUND, así que conocer un id no concede nada. Los archivos de un run se listan y se descargan desde su página.

Nodo Hace Efecto
http.download Descarga un archivo por HTTP, en streaming, y lo guarda write
http.upload Envía un archivo a un endpoint HTTP, en streaming desde el almacenamiento write
file.read Lee un archivo como texto, un valor JSON o filas CSV read
file.write Guarda texto, un valor JSON o filas como archivo write
text.extract El texto de un archivo TXT, JSON, CSV, PDF con texto o DOCX read
convert.csv_to_json Un archivo CSV como archivo JSON de filas write
convert.json_to_csv Una lista JSON de objetos planos como archivo CSV write
convert.text_to_file Texto como archivo TXT write
convert.pdf_to_png Páginas elegidas de un PDF como imágenes PNG write
image.transform Recorta, redimensiona, gira o convierte una imagen write

Una descarga sigue las mismas reglas de SSRF y credenciales que http.request, hasta cinco redirecciones. Su cuerpo se cuenta a medida que llega y se rechaza por encima de max_bytes, y su tipo se detecta por los bytes, así que una cabecera no puede engañar a expected_content_types. text.extract no hace OCR: una página escaneada hace fallar el paso con TEXT_EXTRACTION_NEEDS_OCR y nombra las páginas. Un documento dañado es DOCUMENT_CORRUPT, un documento de Word que se descomprime más allá de los límites de archivo de una subida al chat es DOCUMENT_TOO_LARGE, y un PDF protegido con contraseña DOCUMENT_ENCRYPTED.

Una imagen se mide antes de decodificarse. Su ancho por alto, una zona de recorte y un tamaño solicitado se comprueban cada uno contra CHAT_IMAGE_MAX_PIXELS, y el resultado no lleva ningún metadato del origen. Todo paso que guarda un archivo guarda uno nuevo en cada intento, así que es at_least_once. Los archivos de un run pueden sumar como máximo WORKFLOW_RUN_MAX_FILE_BYTES, y los de una organización WORKFLOW_ORG_MAX_FILE_BYTES. Un paso cuyo archivo superaría alguno de los dos falla con FILE_QUOTA_EXCEEDED y no guarda nada. Para liberar espacio, borra runs antiguos o fija una retención de runs en los workflows que crean archivos.

app.workflows.nodes.http_download._handler.HttpDownloadConfig

Bases: BaseModel

What to fetch, and what to accept.

app.workflows.nodes.http_upload._handler.HttpUploadConfig

Bases: BaseModel

Where to send the file, and how.

app.workflows.nodes.file_read._handler.FileReadConfig

Bases: BaseModel

Las conversiones CSV admiten como máximo 10.000 filas, 1.000 columnas y 1.000.000 de celdas. Ambos sentidos limitan la salida codificada a max_bytes del paso antes de guardarla. La extracción de texto PDF rechaza más de 2.000 páginas y deja de leer páginas cuando supera max_chars. Los archivos indicados directamente se comprueban de nuevo al admitir el run contra el acceso actual de su ejecutor al workflow de origen.

Las salidas del sandbox también se limitan durante su descarga en streaming. Un archivo que crece después de medirse no evita el límite agregado de bytes restante; el JSON del resultado también cuenta. Los runs invocados ya terminados se conservan hasta que termine su invocador, para que la retención no provoque repetir sus efectos.

app.workflows.nodes.text_extract._handler.TextExtractOutput

Bases: BaseModel

app.workflows.nodes.convert_pdf_to_png._handler.ConvertPdfToPngConfig

Bases: BaseModel

app.workflows.nodes.image_transform._handler.ImageTransformConfig

Bases: BaseModel

Python

Dos nodos ejecutan Python, para dos tipos de trabajo.

code.python.simple ejecuta un script corto en el sandbox de Monty, que no tiene sistema de archivos, ni red, y tiene una biblioteca estándar pequeña. El script lee los valores vinculados como args, y su última expresión es el result del paso, que debe ser un valor JSON. Solo calcula, así que es pure y requiere code:execute.

En el editor, el script de un paso de código se escribe en un editor de código: Python o JavaScript resaltado en ambos temas, Tab y Shift+Tab para sangrar, Enter que conserva la sangría, paréntesis y comillas que se cierran al escribirlos, el paréntesis junto al cursor enmarcado con su pareja, y las claves del args vinculado ofrecidas al escribir args[" o, en JavaScript, args.. Esc y luego Tab sale del editor. Test step ejecuta solo el script con lo que entregaron los pasos anteriores.

code.python.sandbox ejecuta Python completo con paquetes y los archivos del run en la conexión sandboxd de la organización, y requiere sandbox:execute. El script encuentra sus archivos de entrada en inputs, escribe archivos en outputs y fija result. Es un trabajo duradero: el primer despacho lo inicia en segundo plano, y cada uno posterior lo comprueba en la misma sesión, así que un worker reiniciado se reconecta en lugar de volver a iniciarlo. Ninguna credencial de la plataforma llega nunca al sandbox, y lo que el script alcanza más allá de sus archivos es la propia configuración del runtime del host - elige un runtime sin red para trabajo que no es de confianza.

Qué pasó Resultado
El resultado no es JSON PYTHON_OUTPUT_NOT_JSON
El script lanzó una excepción o superó un límite PYTHON_ERROR
El trabajo superó timeout_seconds PYTHON_SANDBOX_TIMEOUT, la sesión purgada
No hay una conexión sandboxd utilizable SANDBOX_UNAVAILABLE
Escribió más de 20 archivos o 100 MB, o imprimió más de 10 MB PYTHON_OUTPUT_TOO_LARGE, medido en la sandbox antes de traer nada, y la sesión purgada
No se pudo alcanzar el host SANDBOX_UNREACHABLE, se reintenta

app.workflows.nodes.code_python_simple._handler.PythonSimpleConfig

Bases: BaseModel

app.workflows.nodes.code_python_sandbox._handler.PythonSandboxConfig

Bases: BaseModel

app.workflows.nodes.code_python_sandbox._handler.PythonSandboxOutput

Bases: BaseModel

JavaScript

code.javascript.sandbox ejecuta JavaScript en Node como el mismo trabajo duradero, en la misma conexión sandboxd, y necesita sandbox:execute. El script es el cuerpo de una función asíncrona: lee los valores vinculados como args, sus archivos de entrada en inputs, escribe archivos en outputs, puede usar await, y lo que devuelve con return es el result del paso - null cuando no devuelve nada. require carga los módulos propios de Node y lo que el runtime tenga instalado. Elige un runtime con Node.

Sus fallos son los del sandbox de Python, con nombre de JavaScript: un error lanzado es JAVASCRIPT_ERROR, un resultado que no es un valor JSON - una función, un BigInt - es JAVASCRIPT_OUTPUT_NOT_JSON, y el límite de tiempo y los límites de salida son JAVASCRIPT_SANDBOX_TIMEOUT y JAVASCRIPT_OUTPUT_TOO_LARGE.

app.workflows.nodes.code_javascript_sandbox._handler.JavaScriptSandboxConfig

Bases: BaseModel

app.workflows.nodes.code_javascript_sandbox._handler.JavaScriptSandboxOutput

Bases: BaseModel

workflow.run

Run a workflow. Ejecuta la versión publicada de otro workflow, una que empieza por Called by a workflow, con el input vinculado, como actúa esta ejecución. El paso espera a que termine la ejecución llamada y entrega su output, o falla con CALLED_WORKFLOW_FAILED y el error de esa ejecución; con Wait for it to finish desactivado, entrega enseguida la ejecución iniciada. La ejecución llamada queda vinculada al paso y a la cadena de esta ejecución. Llamar a un workflow que ya se ejecuta más arriba en la cadena falla con WORKFLOW_CALL_LOOP, y una llamada con más de cinco niveles con WORKFLOW_CALL_TOO_DEEP. Una ejecución de prueba del borrador llama de verdad al workflow publicado.

app.workflows.nodes.workflow_run._handler.WorkflowRunConfig

Bases: BaseModel

Which workflow to run, and whether to wait for it.

app.workflows.nodes.workflow_run._handler.WorkflowRunOutput

Bases: BaseModel

The run it started: how it stands, and what it answered once it has.

Añadir un nodo

Un nodo es un paquete bajo backend/app/workflows/nodes/: __init__.py registra una NodeDefinition, _handler.py la implementa y README.md explica por qué existe. load_builtins importa el paquete. tests/test_workflow_node_layout.py hace cumplir esa estructura. Un handler devuelve Completed, Waiting, Failed o Uncertain y nunca lanza una excepción. Lee el run para el que se ejecuta desde app.services.workflow_execution.context.current().

Un nodo tipado declara tres modelos de Pydantic: su configuración, que se fija en el editor y se congela al publicar; su entrada, cuyos campos rellenan los enlaces; y su salida, a la que se enlazan los pasos posteriores. extra="forbid" en cada uno mantiene una errata fuera de un grafo publicado. Elige retry_guarantee según lo que haría una repetición de la llamada: idempotent cuando el propio handler hace inofensiva una repetición - una escritura en una tabla pasa operation_key() -, at_least_once cuando una repetición es aceptable, y none cuando no lo es. Un nodo que lee o escribe un recurso declara check_resources, que la publicación ejecuta contra el autor y cada run contra su principal.

La consola dibuja un nodo a partir de su definición, con su icono y su tono en frontend/src/components/workflows/node-visuals.ts. Prueba el handler directamente en sus rechazos, y llévalo una vez por tests/integration/workflow_run_support.py para que su configuración, sus enlaces y su salida queden demostrados en un run real.

app.workflows.contracts.definition.NodeDefinition dataclass

A node type, as _registry.get() returns it and the catalog serializes it.

id is permanent, like a capability id: it is what a graph stores and what a client's exported workflow names. version is bumped on a breaking config/IO change; a graph pins (id, version), so an old published graph may still reference a version this deployment has since superseded - _registry.REGISTRY keeps every version it has ever seen, never only the latest.

retry_guarantee_for class-attribute instance-attribute

retry_guarantee_for = None

The guarantee one configured call gives, when it is not the kind's own.

retry_guarantee above is the conservative default for the kind; http.request is at_least_once as a kind, but a GET - or a write sent with an idempotency header the far side honours - is idempotent. Called with the resolved config when an attempt is created, before the handler.

ports_for class-attribute instance-attribute

ports_for = None

This instance's ports, when its config declares some of them.

error.handle's branches are the author's, one output port each, so they cannot live in ports, which is fixed per kind. Called with the validated config (None if it does not validate); ports is what the catalog shows.

loop_body_only class-attribute instance-attribute

loop_body_only = False

Whether the node exists only inside a control.foreach body - loop.item and loop.yield, which mean nothing outside an iteration.

spends_budget class-attribute instance-attribute

spends_budget = False

Whether an attempt may spend money out of the run's budget - a model call, a knowledge search, an agent, a called workflow. Such an attempt is handed the most it may spend when it begins, and waits when parallel steps already hold all that is left; see app.services.workflow_execution.budget.

exclusive_branches class-attribute instance-attribute

exclusive_branches = False

Whether routes always answers with exactly one output port, so a logic.merge may rejoin the node's branches - logic.if, a decision, a classification. control.foreach routes too, but leaves by body and done in one run, so it is not exclusive.