Przejdź do treści

Węzły workflow

Każdy krok, jaki może zawierać workflow: czym się go konfiguruje, co czyta, co produkuje i co znaczy jego błąd. Paleta w edytorze pokazuje te same węzły z tego samego rejestru. Dokumentacja pól poniżej jest generowana ze źródła, dlatego zostaje po angielsku.

Kilka zasad obowiązuje każdy węzeł:

  • Krawędź ustala kolejność, a binding niesie wartość. Pola wejściowe węzła są wiązane z wcześniejszymi wyjściami, z literałem albo z referencją do pliku lub tabeli. Zobacz Konfigurowanie węzła.
  • Ścieżka w głąb wartości o dowolnym kształcie jest sprawdzana, gdy węzeł działa. Ładunek triggera, zmapowany rekord i ustrukturyzowana odpowiedź agenta nie mają stałego kształtu, więc binding taki jak payload.email jest przyjmowany przy publikacji i sprawdzany względem celu, gdy węzeł zostaje wysłany. Wartość, która nie pasuje, kończy run błędem INVALID_BINDING, a handler nigdy jej nie widzi.
  • Zasoby są sprawdzane dwa razy. Publikacja odrzuca kolekcję, wersję agenta, poświadczenie albo odbiorcę, do których autor grafu nie ma dostępu. Każdy run sprawdza je ponownie względem własnego principala, bo dostęp może zostać odebrany w międzyczasie.
  • Rodzaj efektu i ponowienia decydują, co silnik może zrobić po błędzie. Krok pure albo idempotent jest ponawiany. Krok, który mógł już zadziałać i nie daje żadnej gwarancji, zatrzymuje się dla człowieka. Własna polityka węzła ustala, jak często jest ponawiany, jak długo może trwać wywołanie i dokąd trafia błąd.

Wyzwalacze

Workflow startuje od jednego wyzwalacza, węzła, od którego zaczyna się jego graf. Publikacja wersji włącza jej wyzwalacz; zobacz Uruchamianie workflow spoza konsoli. Każdy wyzwalacz przekazuje kolejnym krokom to, od czego jego powierzchnia uruchomiła run, zamrożone przy przyjęciu runa i najwyżej WORKFLOW_RUN_MAX_INPUT_BYTES. Run testowy uruchomiony z wejściem innego kształtu kończy wyzwalacz błędem TRIGGER_INPUT_INVALID. Drugi wyzwalacz albo wyzwalacz, od którego graf się nie zaczyna, jest odrzucany przy publikacji.

core.input

API request. Uruchamiany żądaniem HTTP albo przez WebSocket. Jego id pozostaje core.input, więc graf zapisany, zanim Manual i API się rozdzieliły, dalej startuje z API. Przekazuje grafowi wejście runa jako payload, cokolwiek wysłał wywołujący, i nazywa powierzchnię w 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.

Jego pola wejścia robią z wejścia kontrakt. Pole ma nazwę, typ - tekst, liczba, liczba całkowita, tak lub nie, data albo wybór - i to, czy jest wymagane. Bez pól run przyjmuje dowolny obiekt JSON. Z polami Start a run prosi o każde po nazwie, run, którego wejściu brakuje pola, który wysyła pole złego typu albo niezadeklarowane, jest odrzucany przed startem błędem WORKFLOW_RUN_INPUT_INVALID, a binding do payload.<pole> jest sprawdzany pod kątem typu przy publikacji.

app.workflows.contracts.fields.InputField

Bases: BaseModel

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

trigger.manual

Manual. Uruchamia go osoba - Test draft w edytorze albo Start a run na stronie runów. Przekazuje grafowi to samo, co API request, i przyjmuje te same pola wejścia, o które edytor prosi przed startem runa.

trigger.chat

Chat message. Uruchamiany wiadomością na czacie, gdy ten workflow wybrano do odpowiedzi. Tekst jego core.output trafia z powrotem do tej rozmowy.

app.workflows.nodes._triggers.ChatTriggerOutput

Bases: _Frozen

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

trigger.webhook

Webhook. Uruchamiany podpisanym dostarczeniem na własny adres workflow, który pierwsza publikacja węzła tworzy razem z sekretem do podpisu.

Przed publikacją Listen for test event w panelu Output wyzwalacza otwiera testowy URL dla szkicu na dwie minuty. Jedno wysłane na niego wywołanie, obiekt JSON, zostaje przypięte jako wynik wyzwalacza, więc każdy kolejny krok da się przetestować na prawdziwym dostarczeniu. Testowy URL nie sprawdza podpisu i nigdy nie uruchamia runu.

app.workflows.nodes._triggers.WebhookTriggerOutput

Bases: _Frozen

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

trigger.schedule

Schedule. Uruchamiany według zegara, w strefie czasowej workflow i najczęściej raz na minutę.

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. Uruchamiany dodaniem, zmianą lub usunięciem rekordu jego tabeli - tym, co wymienia events - gdy rekord pasuje do każdego filtra. Zmiana uruchamia go tylko wtedy, gdy zmieniła się kolumna wybrana dla tabeli, o ile kolumny są wybrane. Publikacja wymaga dostępu do odczytu tabeli.

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. Uruchamiany przez krok workflow.run innego workflow, z polami, które deklaruje - tymi samymi co Manual - i nigdy ręcznie ani przez API. Dane wejściowe wywołującego są sprawdzane względem nich, zanim przebieg się zacznie.

trigger.form

Form. Startuje, gdy ktoś wypełni publiczną stronę workflow pod linkiem, który tworzy pierwsza publikacja węzła. Strona pokazuje tytuł, opis i jedno pole na każde pole - te same pola co Manual - a zgłoszenie, które do nich pasuje, uruchamia run jako członek, który go opublikował. triggered_by to form.

app.workflows.nodes.trigger_form._handler.FormTriggerConfig

Bases: TriggerInputConfig

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

trigger.github

GitHub. Startuje od dostawy podpisanej przez GitHub dla repozytorium, jednego ze zdarzeń i - jeśli węzeł je wymienia - jednej z akcji, które wskazuje węzeł. Publikacja rejestruje hook przez podłączone konto GitHub organizacji, gdy może, albo raz pokazuje sekret do wklejenia. triggered_by to 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. Startuje od nowej wiadomości w podłączonej skrzynce Gmail organizacji, pasującej do wszystkich ustawionych filtrów, czytanej w odstępie Check for new mail every skrzynki (domyślnie minuta). Publikacja jest odrzucana, dopóki skrzynka nie jest podłączona. triggered_by to gmail. Sender contains dopasowuje adres nadawcy, nie nazwę wyświetlaną: @acme.com to dokładnie ta domena, bez poddomen. Gdy jest ustawiony, wiadomość musi też przejść sprawdzenie uwierzytelnienia nadawcy (DMARC albo podpis DKIM domeny nadawcy). Wiadomości z etykietą SENT, DRAFT, SPAM lub TRASH są pomijane, chyba że Label je wskazuje.

Wyjście niesie wątek - thread_id, rfc_message_id, in_reply_to, references, received_at - oraz treść html, czytaną do 20 000 znaków. Pola nagłówkowe pisze nadawca, więc wiążą odpowiedź z wątkiem, ale nic nie dowodzą o tym, kto wysłał wiadomość.

Run uruchomiony przez skrzynkę zapisuje załączniki wiadomości jako pliki runa, ale tylko gdy workflow je czyta: graf, który nie wiąże ani attachments triggera, ani całego jego wyjścia, zostawia każdy załącznik jako not_fetched. Każdy zapisany plik wlicza się do limitu plików organizacji, a do triggera bez Sender contains może napisać każdy, więc wiąż attachments tylko wtedy, gdy krok potrzebuje plików.

Skrzynka podaje najwyżej 20 na wiadomość, a brane jest pod uwagę tylko pierwsze 10 z wymienionych, do 25 MiB łącznie, każdy w limicie rozmiaru uploadu czatu, na liście dozwolonych typów plików i w limicie miejsca organizacji. Pominięty załącznik zostaje w attachments z polem reason: too_many, too_large, type_not_allowed, quota, unreadable lub not_fetched. Run, którego skrzynka nie uruchomiła - testowy - niczego nie czyta, więc każdy załącznik jest not_fetched.

unreadable oznacza odpowiedź 400, 401, 403 lub 404 dla załącznika albo grant skrzynki, który jest brakujący, wyłączony lub nie da się go odnowić. Każda inna awaria dostawcy kończy krok błędem MAIL_ATTACHMENT_UNREACHABLE, który jest ponawiany. Oba wyzwalacze poczty przyjmują dane z zewnątrz.

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. Startuje od nowej wiadomości w folderze podłączonej skrzynki Microsoft 365 organizacji, pasującej do wszystkich ustawionych filtrów, czytanej w odstępie Check for new mail every skrzynki (domyślnie minuta). Publikacja jest odrzucana, dopóki skrzynka nie jest podłączona. triggered_by to outlook. Microsoft Graph jest czytany folder po folderze, więc Folder i Shared mailbox są miejscem, w którym poller szuka, a zarazem filtrem, a pierwszy odczyt folderu zaczyna się od chwili opublikowania lub ostatniego włączenia workflow. Wskazanie skrzynki współdzielonej, którą otwiera grant Microsoft organizacji, wymaga uprawnienia do zarządzania połączeniami, tak jak w triggerach agentów Outlook; workflow, który już nasłuchuje takiej skrzynki, zachowuje ją przy kolejnych publikacjach, a jej zmiana znowu wymaga tego uprawnienia. Sender contains dopasowuje adres nadawcy, jak w Gmailu, i wymaga poczty, którą uwierzytelniła domena nadawcy albo która przyszła z wnętrza tenanta. Folder lub skrzynka współdzielona, które według Outlooka nie dają się odczytać, wyłączają workflow, z mailbox_folder_unreadable w dzienniku audytu. Zapisana konfiguracja, która przestała przechodzić walidację, jest pomijana przez poller. Załączniki są zapisywane jak w trigger.gmail, a labels zawiera kategorie wiadomości. Jej wyjście jest takie samo jak 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. Startuje od wiadomości do jednego z botów organizacji na Slacku, Mattermost lub Telegramie, na którą bot odpowiedziałby własnym agentem i która pasuje do filtra węzła. Publikacja wymaga channels:manage, a wiadomości jednego bota uruchamiają najwyżej jeden workflow. triggered_by to channel_message. Bot Teams jest odrzucany przy publikacji i w przebiegu testowym; nasłuch Teams opublikowany wcześniej oddaje swoje wiadomości agentowi bota.

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. Uruchamiany raz dla każdego nieudanego prawdziwego przebiegu workflow, którego ustawienia wskazują ten jako workflow błędów, jako członek, który go wybrał. Przebieg uruchomiony tym wyzwalaczem sam nigdy nie uruchamia workflow błędów.

app.workflows.nodes._triggers.WorkflowFailedTriggerOutput

Bases: _Frozen

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

core.output

To, czym workflow odpowiada. Jego zbindowane pola stają się output runa, który zwraca API i dostarcza wywołująca powierzchnia. Ma te same pola co wyjście agent.run, więc odpowiedź agenta binduje się wprost. Wyjście bez żadnego bindingu to pusta odpowiedź. Run odpowiada raz, więc publikacja odrzuca graf z dwoma takimi krokami, które mogą oba działać w jednym runie.

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. Odpowiada na dostarczenie, które uruchomiło run, statusem (od 200 do 599), nagłówkami i JSON-owym body powiązanym z wcześniejszego kroku. Dostarczenie do grafu z tym krokiem czeka na niego zamiast dostać 202, najwyżej przez WORKFLOW_WEBHOOK_RESPONSE_TIMEOUT_SECONDS. Odpowiedzią jest pierwszy ukończony krok respond, ponowienie tego samego dostarczenia dostaje ją jeszcze raz, a run toczy się dalej. Run, który kończy się bez dojścia do kroku, odpowiada 202, jeśli się powiódł, i 500, jeśli nie. Publikacja odrzuca graf z dwoma takimi krokami, które mogą oba działać w jednym runie. Nagłówki należące do własnej odpowiedzi API są odrzucane przy publikacji: ramkowanie, Content-Type, Set-Cookie oraz nagłówki CORS i polityk przeglądarki.

app.workflows.nodes.webhook_respond.WebhookRespondConfig

Bases: BaseModel

The status and headers the delivery is answered with.

data.map

Buduje mały, typowany rekord z wcześniejszych wyjść. Każde mapowanie czyta jedną wartość wyrażeniem JMESPath i zamienia ją na string, number, integer, boolean, json, file_ref albo table_ref. Wartość, której nie da się zamienić, kończy się błędem MAPPING_COERCION_FAILED z nazwą pola.

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 i data.combine

Filter a list zostawia te elementy powiązanej listy, dla których spełniony jest warunek JMESPath nad item i index, i mówi, ile odrzucił. Combine lists tworzy jedną listę z first i second: append stawia jedną po drugiej, by_position łączy obiekty na tej samej pozycji, a by_key dołącza do każdego obiektu pierwszej obiekt drugiej o tym samym key. Gdy oba mają pole, wygrywa druga; łączenie element po elemencie wymaga obiektów, inaczej kończy się 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 ocenia, jak bardzo podobne są dwa teksty, a i b: 1, gdy są takie same po pominięciu ignorowanych różnic, 0, gdy nie mają nic wspólnego. Nie wywołuje modelu i nic nie kosztuje, a te same dwa teksty zawsze dają te same liczby (wyniki są zaokrąglane do sześciu miejsc). Metoda określa, co jest liczone:

  • char_ratio (domyślna) to udział znaków, których edytor nie musiałby ruszać, liczony z dokładnej odległości edycyjnej po znakach.
  • token_f1 to udział słów wspólnych dla obu tekstów, bez względu na kolejność.
  • line_diff to udział linii, które się nie zmieniły.

Złożone i rozłożone akcenty nigdy nie są edycją, podobnie jak zakończenia linii. Resztę ustawia krok. Ignore whitespace jest domyślnie włączone: zwija odstępy w obrębie linii, przycina każdą linię i usuwa puste linie. Podziały linii nadal się liczą, więc miejsca końca linii ignoruje tylko token_f1, który liczy słowa. Ignore case i Ignore punctuation są domyślnie wyłączone. Słowa w token_f1 to ciągi liter, cyfr i podkreślników: dzieli na apostrofach i nie dzieli na słowa tekstu pisanego bez spacji.

Leave out quoted mail and signatures pomija każdą linię, której pierwszy niebiały znak to > (także cytaty z wcięciem), wiersz z atrybucją nad cytatem oraz wszystko po separatorze podpisu -- lub samym -- albo po linii ----- Original Message -----. To heurystyka dla poczty w czystym tekście: nie wykrywa cytatów HTML, odpowiedzi, w której cytat nie jest oznaczony >, atrybucji zawiniętej na dwie linie (zostaje) ani podpisu bez separatora.

Każdy tekst ma co najwyżej 100 000 znaków, liczonych przed jakąkolwiek normalizacją; ocena dwóch takich tekstów trwa kilka sekund. Dłuższy tekst kończy run błędem INVALID_BINDING jeszcze przed uruchomieniem kroku, więc error.handle nie może go przekierować. Krok, w którym nic nie jest powiązane z a i b, kończy się błędem TEXT_COMPARE_NOT_CONFIGURED; gdy jeden jest powiązany, a drugi nie, run kończy się błędem INVALID_BINDING. Puste teksty są dozwolone: dwa puste dają wynik 1, jeden pusty daje 0. Paleta rysuje krok z ikoną różnic, wśród kroków danych.

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 zamienia HTML na jego tekst, linki i elementy wybrane selektorem CSS. Służy do HTML-a, który spotyka przepływ: treści maila, body z http.request, strony zapisanej przez http.download. text.extract czyta dokumenty, nie znaczniki - oddałby tagi jako tekst. Powiąż albo html (najwyżej 2 000 000 znaków), albo file (najwyżej 5 000 000 bajtów, sprawdzane przed odczytem pliku), nie oba naraz. Plik jest dekodowany ze znacznika kolejności bajtów, z <meta charset> lub z samych bajtów, w razie potrzeby stratnie, a jego zadeklarowany typ nie jest sprawdzany: PDF czyta się jako zniekształcony tekst i nie jest odrzucany. Znak NUL czyta się jako U+FFFD.

Krok ma trzy wyjścia, każde ograniczone:

  • text to czytelny tekst jako Markdown, obcięty na Most characters of text (domyślnie 100 000; text_truncated mówi, że go obcięto). Nagłówki, listy i kod zostają; skrypty, style, menu (nav), stopki i <head> nie. Tak samo czyta stronę konektor webowy kolekcji wiedzy. title to <title> strony, obcięty do 500 znaków, albo null, gdy go nie ma (także pusty <title>).
  • links zawiera każde a[href] raz, w kolejności dokumentu, z tekstem jego pierwszej kotwicy (obciętym do 500 znaków). Link jest rozwiązywany względem Base address, gdy jest ustawiony, a bez niego zostaje tak, jak napisano. Linki do adresów javascript:, vbscript: i data: są pomijane, żeby kolejny krok, który żąda każdego linku, nigdy nie dostał skryptu; pomijany jest też href dłuższy niż 2048 znaków i taki, którego nie da się odczytać jako adresu. Krok zachowuje najwyżej 500 linków i sprawdza pierwsze 5000 kotwic; links_truncated mówi, że strona miała więcej.
  • matches zawiera elementy wybrane przez CSS selector, w kolejności dokumentu, najwyżej Most matches (domyślnie 20, do 200); match_count mówi, ile wybrał selektor przed obcięciem. Każde dopasowanie ma text i zewnętrzny html (oba obcięte do 10 000 znaków) oraz pierwszych 50 attributes, przy czym atrybut wielowartościowy, np. class, jest łączony spacjami, a każda wartość obcięta do 2000 znaków. Bez selektora nie ma dopasowań. Selektor, który nie jest poprawnym CSS-em, jest odrzucany przy publikacji z podaniem pola.

Nic nie jest pobierane i nic się nie uruchamia. Nie jest żądany żaden obraz, arkusz stylów, Base address ani link, skrypt jest tylko tekstem, a parser pochodzi z biblioteki standardowej - to nie parser XML, więc żadna encja spoza strony nie jest rozwijana.

Parsowanie działa w procesie potomnym z 20 sekundami czasu zegarowego, limitem CPU i limitem pamięci, bo selektor taki jak p div div na głęboko zagnieżdżonym znaczniku może trwać minuty. Po 20 sekundach proces jest zabijany, a krok kończy się błędem HTML_TOO_COMPLEX, bez ponawiania. Praca na każdą kotwicę i każde dopasowanie jest również ograniczona, więc jedna strona nie spowolni kroku ani nie rozdmie jego wyjścia.

Co się stało Wynik
Brak ustawień lub wejścia HTML_PARSE_NOT_CONFIGURED
html ponad 2 000 000 znaków albo powiązane naraz html i file INVALID_BINDING, jeszcze przed uruchomieniem kroku
Plik nie istnieje albo ma ponad 5 000 000 bajtów FILE_NOT_FOUND, FILE_TOO_LARGE
Żaden dekoder nie czyta bajtów albo proces czytający pada HTML_NOT_READABLE, szczegół reason mówi który przypadek
Strona zajmuje ponad 20 sekund HTML_TOO_COMPLEX

INVALID_BINDING kończy run, zanim krok się uruchomi, więc error.handle nie może go przekierować; za duży plik to FILE_TOO_LARGE, którym krok kończy się błędem i który da się przekierować. Paleta rysuje krok z ikoną kodu, wśród kroków danych.

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

Kroki Transform przekształcają listę obiektów bez kroku z kodem. Każdy przyjmuje items, listę powiązaną z wcześniejszego kroku, a większość przekazuje dalej items, więc da się je łączyć w łańcuch.

Krok Robi
transform.edit_fields Ustawia pola z wyrażeń JMESPath nad każdym item, usuwa pola albo zostawia tylko ustawione
transform.sort Sortuje po kolejnych polach, rosnąco lub malejąco
transform.limit Zostawia pierwsze albo ostatnie elementy
transform.remove_duplicates Zostawia pierwszy z każdej grupy elementów równych na wskazanych polach albo w całości
transform.aggregate Zbiera wartości każdego pola ze wszystkich elementów w jedną listę na pole, jako values
transform.split_out Zamienia listę wewnątrz każdego elementu w osobne elementy
transform.summarize Liczy, sumuje, uśrednia, znajduje najmniejszą lub największą albo liczy różne wartości, w grupach
transform.date_time Teraz albo powiązana value przesunięta o wartość, zapisana w strefie czasowej - workflow, chyba że krok wskazuje własną
transform.crypto Liczy skrót lub koduje base64 powiązany text albo tworzy UUID lub losowy hex

Pole to ścieżka z kropkami, customer.email, a element bez niego nigdy nie jest błędem. Sort stawia go na końcu, Remove duplicates traktuje „brak” jako osobną wartość, Aggregate i Summarize go pomijają, Split out zostawia element bez zmian, a Edit fields ustawia null, gdy wyrażenie nic nie znajdzie. Crypto nie służy do sekretów: nic w nim nie używa klucza.

Połączone w łańcuch odpowiadają na częste pytania bez kroku z kodem:

  • Najlepsze leady, każdy raz - Remove duplicates po email, Sort po score malejąco, Limit do 10, potem Edit fields zostawiające tylko name i score, gotowe do wiadomości.
  • Sumy według regionu - Split out lines, aby każda pozycja zamówienia była elementem, potem Summarize z sum pola amount grupowane po region: jeden element na region z sum_amount.
  • Jedna lista adresów - Aggregate email przekazuje values.email, wszystkie adresy na jednej liście, dla każdego ustawienia, które przyjmuje listę.

Każdy krok czyta items z poprzedniego. tests/integration/test_workflow_transform_composed.py uruchamia dwa pierwsze dokładnie tak.

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 i logic.merge

logic.if wylicza warunek JMESPath na zbindowanej wartości value i prowadzi dalej portem true albo false. Każdy węzeł na nieobranej gałęzi zostaje zapisany jako skipped. value musi być zbindowane: publikacja i przebieg testowy wersji roboczej odrzucają krok bez niego; wersja opublikowana wcześniej działa dalej i czyta null.

logic.merge łączy gałęzie z powrotem. Domyślnie czeka na gałąź, która się wykonała: działa, gdy ta gałąź do niego dotrze, i przekazuje jej wyjście dalej jako value. Publikacja sprawdza, czy to bezpieczne, i odrzuca merge, którego wejścia mogą działać w tym samym runie. Nie mogą tego wejścia wychodzące z jednego kroku różnymi portami, jeśli ten krok w każdym przebiegu wybiera dokładnie jeden port - If, Switch, decyzja, ai.classify, sprawdzenie tabeli, zatwierdzenie albo krok kierujący swoje błędy. Nie mogą też gałęzie różnych takich kroków, które się wzajemnie wykluczają, na przykład zagnieżdżony If ze wspólnym else.

Merge może przyjąć dwa lub więcej portów tego kroku bezpośrednio, bez kroku pomiędzy, na przykład needs_reply i unsure z ai.classify: uruchamia się raz, dla portu, który krok wybrał. Dwie krawędzie z tego samego portu nadal są odrzucane.

Gałęzie działające obok siebie, takie jak odnogi Split, wymagają ustawienia Wait for na All branches (wait_for: all). Merge uruchamia się wtedy, gdy każde wejście się zakończy, i przekazuje wyjście każdej gałęzi w branches, obiekcie według nazw gałęzi, zamiast w value. Nazwę gałęzi daje wpis w branch_keys: edytor uzupełnia go dla każdego połączenia, nazwą kroku albo gałęzi Split, a krok dalej czyta branches.<nazwa>. Publikacja odrzuca nazwę, której nic nie ma, oraz dwie gałęzie o tej samej nazwie.

Publikacja odrzuca też All branches, gdy jakieś wejście nie ma pewności dotarcia, ilekroć merge działa - z jego nazwą - oraz gdy może działać tylko jedno wejście, bo wtedy potrzebne jest The branch that ran. branches jest przechowywane jak wynik każdego kroku: wynik ponad limit inline kończy krok błędem NODE_OUTPUT_TOO_LARGE, więc pliki przekazuj dalej jako referencje.

Starszy zapisany graf, który czeka na wszystkie gałęzie, a dociera do niego tylko część z nich, kończy run błędem JOIN_INCOMPLETE ze wskazaniem merge'a. Publikacja takiego grafu jest odrzucana; kod zostaje jako zabezpieczenie. wait_for i branch_keys mają wartości domyślne, więc merge zapisany bez konfiguracji nadal czeka na gałąź, która się wykonała.

Każdy krok z kilkoma krawędziami wejściowymi jest złączeniem (join) i nie potrzebuje merge'a. Uruchamia się, gdy każdy krok prowadzący do niego się zakończy, o ile choć jedna krawędź jest aktywna, a pomijany jest, gdy żadna nie jest. Jest też pomijany, gdy źródło zawiodło bez ścieżki błędu: ta porażka należy już do runa. Bindingi złączenia mogą czytać tylko kroki, które na pewno wykonały się wcześniej, więc złączenie może czytać każdą odnogę grupy równoległej, ale nie jedną gałąź If.

Reguła, kategoria, gałąź albo wybór, któremu nadasz nazwę, staje się portem wyjściowym, więc nie może powtarzać portu, który krok już ma: in, otherwise, other, default, approved i rejected w krokach, które je mają, unsure w klasyfikatorze z minimalną pewnością oraz error w kroku, który kieruje swoje błędy. Edytor oznacza taką nazwę, a publikacja ją odrzuca, wskazując, która nazwa koliduje i że trzeba ją zmienić.

Wyrażenia mogą wybierać, filtrować i porównywać oraz wywoływać stały zestaw czystych funkcji: 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 i values. Null, false oraz pusty napis, lista albo obiekt są fałszem, a wszystko inne prawdą, także 0. Wyrażenia, które się nie parsuje albo które wywołuje coś innego, nie da się opublikować.

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. Kieruje przebieg każdą ze swoich gałęzi naraz. Każda gałąź ma nazwę, będącą jej portem wyjściowym, a gałęzi jest od dwóch do dwudziestu. Nic nie jest wybierane i żaden warunek nie jest czytany: każda gałąź jest zawsze obierana, a kroki po nich działają obok siebie. Krok nie przekazuje niczego dalej, więc krok w gałęzi binduje się do tego, co działo się przed Split. Nazwa gałęzi nie może być in ani powtarzać nazwy, którą krok już ma.

Split nie może kierować swoich błędów, bo Split, który zawiódł, nie uruchomił żadnej gałęzi: publikacja odrzuca na nim on_error: route, a kroki każdej gałęzi kierują własne. Gałęzie spotykają się znów w logic.merge, który czeka na wszystkie, albo w dowolnym kroku mającym je jako wejścia. Także każdy inny krok może wysłać jeden port do kilku kroków, które działają naraz tak samo; Split nadaje gałęziom nazwy i trzyma je na widoku.

app.workflows.nodes.logic_split._handler.LogicSplitConfig

Bases: BaseModel

The branches the run is sent down together.

logic.switch

Switch. Kieruje przebieg pierwszą z wielu gałęzi, której reguła jest spełniona. Każda reguła ma nazwę, będącą portem jej gałęzi, i warunek JMESPath nad powiązaną value, sprawdzane po kolei; otherwise przejmuje przebieg, gdy żadna nie jest spełniona. value musi być zbindowane: publikacja i przebieg testowy wersji roboczej odrzucają krok bez niego; wersja opublikowana wcześniej działa dalej i czyta null. Gałęzie łączą się w logic.merge czekającym na gałąź, która się wykonała; publikacja go przyjmuje, bo zawsze biegnie dokładnie jedna. Reguła, która zawodzi na swoich danych, kończy krok błędem CONDITION_FAILED i wskazuje regułę.

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.

Przeszukuje kolekcje wiedzy według zbindowanego query i zwraca fragmenty jako typowane źródła, od najlepszego. Pusty wynik to udane wyszukiwanie. Kolekcja, której już nie ma albo której principal runa nie może już czytać, kończy krok błędem COLLECTION_NOT_ACCESSIBLE. Krok nigdy nie przeszukuje mniej kolekcji, niż wskazuje graf.

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

Dodaje zbindowany tekst z title albo plik runu do jednej do pięciu kolekcji wiedzy, tym samym uploadem co strona kolekcji: z jej własnymi ustawieniami parsowania i dzielenia na fragmenty, sprawdzeniem budżetu organizacji i ingestią kolejkowaną, a nie oczekiwaną. Tekst jest zapisywany jako <title>.md, a dokument o tej samej nazwie jest zastępowany, a nie dublowany, więc ponowienie go aktualizuje. Kolekcja, do której principal nie może dodawać, kończy krok błędem KNOWLEDGE_ADD_REFUSED.

app.workflows.nodes.knowledge_add._handler.KnowledgeAddConfig

Bases: BaseModel

Which collections the document goes into.

Kroki modelu

Cztery kroki pytają jeden z modeli organizacji o jedną rzecz, bez narzędzi. Każdy działa jako własny agent kroku - tworzony przy pierwszym runie, publikowany ponownie, gdy zmienią się jego ustawienia - przez ten sam runner co agent.run, więc max_cost_usd kroku, budżet workflow i limit organizacji są sprawdzane przed zapytaniem, koszt trafia do runu, a run widać w Activity. Publikacja sprawdza, czy autor może używać modelu.

Krok Robi Przekazuje dalej
ai.prompt Odpowiada na zbindowany prompt według instructions kroku text, a structured, gdy structured_output_schema prosi o obiekt
ai.classify Przypisuje zbindowany text do jednej z categories kroku albo do other Wychodzi portem kategorii, z category, jednozdaniowym reason, text i confidence; poniżej min_confidence - portem unsure
ai.extract Wyciąga ze zbindowanego text pola nazwane w output_schema, pomijając to, czego tekst nie mówi data, obiekt tego kształtu
ai.rewrite Streszcza (short, medium, long), tłumaczy na language albo przepisuje zbindowany text w danym style text

Kategorie klasyfikatora są jego portami, w kolejności, a model może odpowiedzieć tylko nazwą jednej z nich albo other. Odpowiedź, która nie jest obiektem, o który prosił krok, kończy go błędem STRUCTURED_OUTPUT_MISMATCH; wyczerpany limit - błędem AGENT_BUDGET_EXCEEDED.

Gdy ustawiono min_confidence, model podaje też własną pewność co do kategorii, od 0 do 1. To jego deklaracja, nie prawdopodobieństwo, i nie jest skalibrowana: zanim zaufasz progowi, sprawdź go na własnych przypadkach za pomocą ewaluacji kroku. Minimum dodaje port unsure po other: poniżej niego krok wychodzi tym portem, niosąc to samo wyjście, więc category nadal jest do dyspozycji workflow.

Bez minimum model nie jest pytany o liczbę, prompt się nie zmienia, confidence jest null, a kategoria zawsze wygrywa. Odpowiedź bez liczby nigdy nie wychodzi portem unsure, a gdy ten port istnieje, unsure nie może być nazwą kategorii.

Krok wychodzi dokładnie jednym portem, więc logic.merge może połączyć jego gałęzie: poprowadzić unsure i jedną kategorię tą samą ścieżką albo dwie kategorie do jednego kroku zapisu.

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.

Decyzje

Trzy kroki zadają Jev od TypeSafe typowane pytanie o powiązany text, z kluczem API TypeSafe z vaulta. Jev nie pisze tekstu: odpowiada na pytanie z pewnością od 0 do 1, w jednym żądaniu, i może odpowiedzieć tylko jedną z odpowiedzi, na które krok pozwala. Poniżej min_confidence kroku wychodzi on portem unsure, więc to workflow decyduje, co człowiek albo agent zrobi z wątpliwym przypadkiem.

decide.* wywołuje wyłącznie hostowanego Jev od TypeSafe. Wątpliwe przypadki na modelu hostowanym u siebie kieruj przez ai.classify z min_confidence, który wychodzi portem unsure tak samo.

Krok Pyta Wychodzi portem
decide.yes_no o tak albo nie yes, no albo unsure
decide.choose która z maksymalnie 255 opcji pasuje out z choice albo unsure
decide.score gdzie tekst leży na skali od 2 do 10 poziomów out ze score albo unsure

Klucz jest sprawdzany przy publikacji i odczytywany ponownie przy każdym runie. Klucz, którego już nie ma albo nie jest udostępniony, kończy krok błędem SECRET_NOT_USABLE, model, który nie odpowiada, błędem DECISION_FAILED, ponawianym zgodnie z polityką kroku, a wdrożenie zbudowane bez extra browser błędem DECISION_MODEL_UNAVAILABLE. Merge może połączyć gałęzie decyzji tak jak gałęzie kroku 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.

Kanały

Slack, Mattermost i Telegram mają każdy własną grupę kroków, które działają jako jeden z botów organizacji na tej platformie, przez ten sam adapter, którym idą jego odpowiedzi, więc wiadomość wysłana przez workflow przychodzi od tego bota, a krok może odczytać to, co bot może czytać. Platforma ma tylko te kroki, które jej boty mogą wykonać.

Krok Slack Mattermost Telegram Przekazuje dalej
Send a message (<platform>.message.send) tak tak tak gdzie ją wysłano
Read messages (<platform>.messages.read) tak tak - messages, od najstarszej
List members (<platform>.members.list) tak tak administratorzy members, z ich id na platformie
Find channels (<platform>.channels.find) tak tak - channels

Krok przyjmuje tylko bota swojej platformy, a bot mówi w imieniu całej organizacji, więc działanie jako on wymaga channels:manage: od autora grafu przy publikacji i od podmiotu runa przy każdym runie. Bot usunięty, wyłączony albo z innej platformy kończy krok błędem CHANNEL_NOT_USABLE. Platforma, która odmawia wywołania w trakcie runa, kończy go błędem CHANNEL_UNSUPPORTED, a taka, która nie odpowiada, błędem CHANNEL_CALL_FAILED. Wysyłanie nigdy nie jest powtarzane samo.

Send a message odpowiada w wątku, gdy ustawiono thread_id: znacznik czasu wątku na Slacku, id posta głównego na Mattermost albo numer tematu forum na Telegramie. Wiadomość trafia do wątku, nigdy jako cytowana odpowiedź. Pusty thread_id wysyła na sam kanał. Id kanału lub wątku zawierające : albo wątek Telegrama, który nie jest liczbą, kończy krok błędem CHANNEL_TARGET_INVALID przed wysłaniem czegokolwiek.

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

Pyta opublikowanego agenta, dokładnie w wersji przypiętej w kroku, przez ten sam runner co chat i API, z budżetem, zatwierdzeniami, guardrailami i historią runów agenta. Run jest zapisywany z powierzchnią workflow. Zbindowane sources są dopisywane do promptu jako numerowany kontekst. Wywołanie narzędzia wymagające zatwierdzenia zatrzymuje krok, a decyzja wznawia ten sam run agenta.

inline konfiguruje agenta w kroku zamiast agent i ustawione jest dokładnie jedno z nich. Spec należy do kroku i jest wersjonowany razem z grafem: pierwszy run tworzy agenta należącego do workflow, ukrytego w katalogu i osiągalnego przez dostęp do workflow, a run, którego spec różni się od najnowszej wersji tego agenta, najpierw publikuje nową, sprawdzaną tak jak publikacja agenta. Publikacja workflow sprawdza spec względem dostępu autora. max_cost_usd ogranicza jedno wykonanie: agent zatrzymuje się przed zapytaniem do modelu, które by je przekroczyło, a krok kończy się błędem AGENT_BUDGET_EXCEEDED.

Agent z własnym formatem odpowiedzi przekazuje swój obiekt jako structured. structured_output_schema prosi w zamian o inny kształt: agent jest uruchamiany z tym schematem, a odpowiedź, która go łamie, wraca do modelu do poprawki. Obiekt jest sprawdzany jeszcze raz, zanim cokolwiek dalej się wykona. Agent, który nigdy nie da pasującego obiektu, kończy krok błędem AGENT_RUN_FAILED, a odpowiedź bez obiektu tam, gdzie o niego proszono, błędem STRUCTURED_OUTPUT_MISMATCH.

Jak zakończył się run agenta Wynik kroku
Completed Completed
Czeka na zatwierdzenie Czeka, potem wznawia ten sam run
Przekroczony budżet AGENT_BUDGET_EXCEEDED
Zablokowany przez guardrail AGENT_GUARDRAIL_BLOCKED
Inaczej AGENT_RUN_FAILED

Powiązane attachments to obrazy z plików runa - pobrany plik, wyrenderowana strona PDF, przekształcone zdjęcie - pokazywane agentowi jako obrazy, a nie jako link, którego nie może otworzyć. Pokazywane są tylko PNG, JPEG, WebP i GIF. Każdy inny plik kończy się UNSUPPORTED_ATTACHMENT_TYPE, więc tekst dokumentu najpierw odczytaj przez text.extract.

Nigdy nie jest ponawiany automatycznie, bo agent mógł wywołać narzędzia z efektami ubocznymi.

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

Wywołuje API HTTP. Każde żądanie i każde przekierowanie przechodzi kontrolę SSRF wdrożenia i jest wysyłane pod adres, który ta kontrola zatwierdziła. Prywatny host, który operator wpisał do OUTBOUND_PRIVATE_ALLOWLIST, też jest dopuszczany, ale tylko dla URL-a, który od niego zaczyna: przekierowanie z URL-a publicznego nigdy do niego nie dociera.

URL podpięty z publicznego wyzwalacza (webhooka, formularza, e-maila) albo z argumentów narzędzia agenta może wskazać dowolny host z listy, więc wpisuj tylko hosty, które same sprawdzają swoje poświadczenie.

Poświadczenie to HTTP credential z vaulta i jest wysyłane tylko do originów, na które pozwala sekret. Odpowiedź jest czytana w granicy max_response_bytes i oddawana bez Set-Cookie, nagłówków uwierzytelniania i samego tokena. Odpowiedź w HTML wraca jako tekst w body; powiąż ją z html.parse, by odczytać jej tekst, linki i elementy.

Dane uwierzytelniające idą jako token bearer, uwierzytelnianie Basic, nagłówek o podanej nazwie albo, z query, parametr URL o podanej nazwie. Import cURL w edytorze wczytuje polecenie wklejone z dokumentacji API do metody, URL-a, nagłówków i treści JSON. Dane uwierzytelniające z polecenia nigdy nie zostają w kroku: krok zostaje ustawiony, by wysyłał je tak samo, a formularz vault otwiera się z nimi wypełniony.

GET może przechodzić listę stronami dzięki pagination: idąc za następnym URL-em, który podaje odpowiedź, odsyłając kursor albo zwiększając parametr strony. Elementy items_path każdej strony zbierane są do items, po kolei. Stronicowanie kończy się, gdy nie ma następnej strony, gdy strona nie ma elementów, albo na max_pages, kiedy complete jest fałszem. Wszystkie strony razem czytają nie więcej niż max_response_bytes, a następna strona na innym originie nie dostaje danych uwierzytelniających.

Co się stało Wynik
URL to loopback, metadata, nie http(s) albo prywatny i spoza OUTBOUND_PRIVATE_ALLOWLIST operatora URL_REFUSED, nic nie wysłano
URL spoza originów poświadczenia SECRET_ORIGIN_DENIED, nic nie wysłano
Połączenie nigdy się nie otworzyło HTTP_UNREACHABLE, ponawiane
Wysłane, brak odpowiedzi, GET albo nagłówek idempotencji HTTP_NO_RESPONSE, ponawiane
Wysłane, brak odpowiedzi, inny zapis Niepewne: run zatrzymuje się dla człowieka
Status inny niż 2xx HTTP_ERROR_STATUS albo odpowiedź jako wyjście przy on_error_status: complete
Większa niż limit RESPONSE_TOO_LARGE
items_path strony znajduje coś, co nie jest listą 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 wywołuje jedno narzędzie na jednym z serwerów MCP organizacji, z arguments zbindowanymi z wcześniejszych kroków. Edytor pokazuje serwery ze znakiem ich usługi, a potem narzędzia, które serwer wypisał przy ostatnim teście; Refresh tools testuje go ponownie. Każdy argument narzędzia jest wtedy osobnym polem, wpisanym albo zbindowanym jako arguments/<name>. Kwalifikuje się tylko serwer należący do organizacji, nigdy własny serwer członka, a publikacja odrzuca serwer, który nie jest organizacji, jest wyłączony albo nie wypisał tego narzędzia. Poświadczenie pochodzi z vaultu, tak jak dla agenta.

approval decyduje, kiedy osoba najpierw zatwierdza wywołanie: writes, domyślnie, przed każdym narzędziem, którego serwer nie oznacza jako tylko do odczytu; all przed każdym wywołaniem; none nigdy. Prośba trafia do zakładki Approvals, tak jak przy human.approval, do approvers albo do każdego z approvals:decide. Zatwierdzone - narzędzie jest wywoływane; odrzucone - krok kończy się błędem MCP_CALL_REJECTED. Oznaczenie „tylko do odczytu” to deklaracja samego serwera, więc dla serwera, któremu tego nie powierzasz, wybierz all.

Wyjście to text odpowiedzi, jej structured dane, gdy narzędzie je zwraca, i is_error. Błąd, którym odpowiada narzędzie, kończy krok błędem MCP_TOOL_ERROR, chyba że on_tool_error to complete.

MCP nie ma własnego klucza idempotencji. Serwer, który przyjmuje taki klucz jako argument narzędzia, można mimo to uczynić bezpiecznym przy ponowieniu: ustaw Idempotency key argument (idempotency_key_argument) na nazwę tego argumentu, a krok wyśle tam swój stały klucz operacji - taki sam przy każdej próbie kroku i inny w każdej iteracji pętli - bez względu na to, jaką wartość do niego podpięto. Narzędzie musi deklarować ten argument jako tekst.

Publikacja odrzuca argument, którego brakuje w zapisanym schemacie narzędzia, a narzędzie bez zapisanego schematu przechodzi, więc najpierw przetestuj serwer. W czasie runu krok przed wywołaniem wypisuje narzędzia serwera i kończy się błędem MCP_IDEMPOTENCY_ARGUMENT_UNDECLARED, niczego nie wywołując, gdy aktualny schemat nie ma takiego argumentu. Krok z kluczem liczy się jako idempotentny: silnik może go uruchomić ponownie.

Gdy wywołanie się urwie, dalszy przebieg zależy od tego, czy mogło dotrzeć do serwera:

  • Przed wywołaniem - nie udało się połączyć z serwerem albo nie odpowiedział na połączenie lub listę narzędzi - nic nie wysłano, a krok kończy się ponawianym MCP_CALL_FAILED.
  • Po wywołaniu, wywołanie z kluczem albo narzędzie oznaczone przez serwer jako tylko do odczytu (w liście, którą krok właśnie odczytał, a w razie braku w tej zapisanej przy teście serwera) - ten sam ponawiany błąd, bo powtórzenie jest bezpieczne.
  • Po wywołaniu, każde inne narzędzie, bez klucza - narzędzie mogło zadziałać, a odpowiedź nie nadeszła. Krok kończy się jako Uncertain z komunikatem The tool was called and no answer came back; it may have acted, a run czeka na człowieka, który sprawdza drugi system, zanim uruchomi krok ponownie.
Co się stało Wynik
Serwera nie ma albo jest wyłączony MCP_SERVER_UNAVAILABLE, nic nie wysłano
Jego poświadczenie trzeba ponownie autoryzować MCP_SERVER_UNAUTHORIZED, nic nie wysłano
Serwer nie oferuje narzędzia MCP_TOOL_NOT_FOUND
Nie udało się połączyć z serwerem albo połączenie zerwało się przed bezpiecznym wywołaniem lub po nim MCP_CALL_FAILED, ponawiany
Połączenie zerwało się po wywołaniu bez klucza narzędzia nieznanego jako tylko do odczytu Uncertain: run zatrzymuje się dla człowieka
Narzędzie nie deklaruje Idempotency key argument MCP_IDEMPOTENCY_ARGUMENT_UNDECLARED, nic nie wysłano
Narzędzie odpowiedziało błędem MCP_TOOL_ERROR albo odpowiedź jako wyjście
Ustrukturyzowana odpowiedź ma ponad 1 MiB MCP_RESULT_TOO_LARGE
Ustrukturyzowana odpowiedź nie jest JSON-em MCP_RESULT_INVALID
Krok nie wskazuje serwera ani narzędzia MCP_CALL_NOT_CONFIGURED, nic nie wysłano
Narzędzie nie jest dozwolone w tym połączeniu MCP_TOOL_NOT_ALLOWED, nic nie wysłano
Wskazany zatwierdzający nie może już podejmować decyzji APPROVER_UNAVAILABLE, nic nie wysłano
Wywołanie nie zostało zatwierdzone MCP_CALL_REJECTED, nic nie wysłano

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

Powiadamia członków organizacji w aplikacji, mailem albo na oba sposoby, przez centrum powiadomień. Odbiorcy to członkowie wskazani po id. W chwili działania każdy musi nadal być aktywnym członkiem, który widzi workflow, a pozostali są pomijani. Jeśli nie zostanie nikt, krok kończy się błędem NO_PERMITTED_RECIPIENTS. Preferencje każdej osoby dla Workflow notifications nadal obowiązują. Krok kończy się, gdy powiadomienie zostanie zapisane, a mail wychodzi później. Ponowiony krok nie zapisuje drugiego powiadomienia.

app.workflows.nodes.notification_send._handler.NotificationSendConfig

Bases: BaseModel

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

email.send

Dostawcy wysyłający wiadomości rezerwują każdego unikalnego odbiorcę we współdzielonych limitach Redis przed wysłaniem całej paczki: WORKFLOW_EMAIL_MAX_PER_PRINCIPAL_PER_HOUR (100), WORKFLOW_EMAIL_MAX_PER_ORG_PER_HOUR (500) i WORKFLOW_EMAIL_MAX_PER_DEPLOYMENT_PER_HOUR (1000). Limit użytkownika obejmuje jego klucze i organizacje. Stałe okna godzinne obejmują różne przebiegi i workery; na granicy okien można wykorzystać podwójny limit. Wyczerpanie daje EMAIL_DELIVERY_LIMIT, niedostępność liczników EMAIL_ALLOWANCE_UNAVAILABLE, bez wysyłki. Rezerwacje są konserwatywne i nie są zwracane po częściowej rezerwacji, błędzie dostawcy ani anulowaniu. Dostawca tylko logujący nie zużywa limitu. Limit deploymentu ustaw poniżej kwoty SMTP, zachowując zapas na pocztę transakcyjną.

Send an email wysyła maila na maksymalnie dziesięć adresów z nadawcy i dostawcy poczty wdrożenia, osobną kopię do każdego, więc żaden odbiorca nie widzi adresu innego. Jest dla osób spoza organizacji, takich jak lead czy klient: członków dosięga notification.send zgodnie z ich preferencjami. to, subject i zwykły tekst body wpisuje się albo binduje z wcześniejszych kroków, a reply_to kieruje odpowiedzi gdzie indziej niż na adres odpowiedzi wdrożenia.

Wyjście wymienia adresy, które dostawca przyjął. delivered jest fałszywe, gdy dostawca tylko loguje pocztę, co jest domyślne w środowisku deweloperskim, więc run nigdy nie twierdzi, że dotarł do kogoś, do kogo nie dotarł. Kopia odrzucona przez dostawcę kończy krok błędem EMAIL_NOT_SENT, wymieniając, co już wysłano. Poczta nie ma klucza idempotencji, więc ponowiony krok wysyła ponownie.

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 zatrzymuje run, dopóki osoba nie zatwierdzi albo nie odrzuci tego, co run ma zrobić, a potem idzie dalej przez approved albo rejected. Zatwierdzający czyta title kroku i powiązane z nim details w zakładce Approvals w Activity albo przez GET /api/v1/workflow-approvals. approvers wskazuje, kto może zdecydować, i ci ludzie dostają powiadomienie w aplikacji; gdy lista jest pusta, może każdy z approvals:decide. Po timeout_hours prośba wygasa, a krok wychodzi przez rejected z decision: "expired". Anulowanie runa anuluje jego prośby. Każdy przebieg kroku pyta raz, więc ponowienie ani iteracja pętli nigdy nie pytają dwa razy o to samo.

Wskazani zatwierdzający muszą być aktywnymi członkami z approvals:decide. Publikacja to sprawdza, a krok sprawdza ponownie przed wysłaniem prośby; utrata tego uprawnienia kończy krok kodem APPROVER_UNAVAILABLE zamiast zostawiać prośbę, której nikt nie może rozstrzygnąć.

Krok może nieść propozycję: tekst powiązany z proposal, na przykład przygotowaną odpowiedź. Zatwierdzający czyta ją obok prośby. Po włączeniu editable kolejka pokazuje ją w edytorze, jako text albo markdown (proposal_format), pod etykietą proposal_label („Propozycja (Proposal)”, gdy pusta), a zatwierdzający może zatwierdzić zmienioną wersję. Krok bez powiązanego tekstu otwiera pusty edytor, na wypadek gdy przygotowanie niczego nie dało. details się nie zmienia i ma pozostać krótkie; propozycja może mieć do 100 000 znaków.

Zatwierdzone wyjście niesie trzy pola obok decyzji:

  • final to wersja, którą zatwierdzający zatwierdził, a gdy niczego nie zmienił, sama propozycja.
  • original to propozycja w chwili prośby, "", gdy edytowalny krok nie miał nic powiązanego.
  • edited jest prawdą, gdy final różni się od original. Końce linii, spacje na końcu linii i puste miejsce wokół tekstu nie są różnicą.

Edytowana wersja jest zapisywana bez białych znaków na początku i na końcu, więc edycja zaczynająca się od wciętego bloku kodu traci to pierwsze wcięcie.

Zatwierdzona prośba kroku editable zapisuje też swój współczynnik zmiany (edit_ratio), czyli to, jak bardzo zatwierdzający zmienił propozycję, od 0 do 1. Statystyki workflow podają go razem z pozostałymi wynikami zatwierdzeń.

Krok, który nie jest editable, pokazuje propozycję tylko do odczytu, a po zatwierdzeniu przekazuje ją bez zmian jako final z edited równym false, więc kolejne kroki mogą wiązać final niezależnie od trybu kroku. Odrzucenie albo wygaśnięcie nie niesie final. Wysłanie edytowanej wersji do kroku, który nie jest editable, albo razem z odrzuceniem, kończy się odmową 422. Decydujący, którego krok nie wskazuje, widzi propozycję tylko do odczytu.

Krok może zamiast zwykłego zatwierdzenia oferować wybory (choices), z których każdy jest sposobem zatwierdzenia: na przykład zostawić szkic albo wysłać go od razu. Można podać do pięciu, każdy z id (małe litery, cyfry i podkreślenia, nie in, approved ani rejected) i label do 40 znaków. Kolejka pokazuje jeden przycisk na wybór zamiast Approve, z etykietą dokładnie tak, jak ją zapisano, bez tłumaczenia, a Reject zostaje. Krok ma wtedy po jednym porcie wyjściowym na wybór, w kolejności, potem rejected, i nie ma portu approved: krawędź z approved jest odrzucana przy publikacji workflow.

Wybór zatwierdzającego jest portem, którym krok wychodzi, a jego id to choice w wyjściu. Jest null, gdy krok nie ma wyborów, przy odrzuceniu i przy wygaśnięciu. Krok bez wyborów działa jak dotąd: ma approved i rejected. Zatwierdzenie kroku z wyborami wymaga jednego z nich jako choice, a podanie choice przy każdej innej decyzji kończy się odmową 422. Statystyki nie dzielą się według wyboru.

Jeśli workflow wysyła zatwierdzone final przez mcp.call, ustaw approval tego kroku na none, bo inaczej osoba zostanie zapytana dwa razy.

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. Zatrzymuje przebieg na kroku przez seconds od dotarcia do niego albo do powiązanego until, po czym idzie dalej; najwyżej trzydzieści dni. Krok jest zaparkowany na zegarze, którego wiersz dyspozycji przypada na ten moment, więc oczekiwanie przetrwa restart workera i nie zajmuje go, a inne gałęzie w tym czasie działają. Moment, który już minął, przechodzi od razu, a termin przebiegu nadal obowiązuje. Powiązany until późniejszy niż trzydzieści dni od dojścia do kroku kończy krok błędem WAIT_TOO_LONG.

Z Wait for a call to the run's resume link krok czeka zamiast tego, aż zostanie wywołany adres, który dał krok Resume link, najwyżej seconds (trzydzieści dni bez ustawienia). Wyślij na ten adres POST z obiektem JSON albo bez treści: krok przekazuje treść dalej jako body, z called równym true, i przebieg idzie dalej. Gdy nikt nie wywoła go na czas, idzie dalej z called równym false. Wywołanie, gdy nic nie czeka - przed dotarciem do kroku, po tym, jak poszedł dalej, albo po zakończeniu przebiegu - jest odrzucane z WORKFLOW_NOT_WAITING (409), a link, który nie należy do przebiegu, odpowiada 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. Przekazuje url, adres, który wznawia kroki Wait tego przebiegu czekające na wywołanie - dla wiadomości albo żądania przed Wait, aby ten, kto odpowiada, mógł kontynuować przebieg. Każdy przebieg ma własny, zrobiony z jego id pod sekretem wdrożenia i nikomu innemu niepokazywany: to jego posiadanie pozwala wznowić przebieg, więc wysyła się go tylko tam, skąd ma przyjść odpowiedź.

app.workflows.nodes.flow_resume_link._handler.ResumeLinkOutput

Bases: BaseModel

The run's resume link.

Obsługa błędów

Każdy węzeł przyjmuje opcjonalną policy obok swojej konfiguracji.

Pole Domyślnie Skutek
timeout_seconds brak Wywołanie, które trwa dłużej, zostaje przerwane. Krok bez zewnętrznego zapisu albo z idempotentnym wywołaniem kończy się błędem NODE_TIMEOUT i może zostać ponowiony. Zapis, który mógł już dojść, staje się niepewny i zatrzymuje się dla człowieka
retry.max_attempts WORKFLOW_RETRY_CEILING Łączna liczba prób, łącznie z pierwszą. Ponawiany jest tylko błąd, który węzeł oznacza jako retryable, a publikacja odrzuca więcej niż jedną próbę dla kroku, którego wywołania nie da się bezpiecznie powtórzyć
retry.backoff, base_delay_seconds, max_delay_seconds exponential, 2, 60 Odstęp między próbami: stały albo podwajany aż do sufitu
on_error fail_run route wysyła błąd, którego ponowienia nie rozwiązały, portem error węzła, zamiast kończyć run błędem

Węzeł, którego polityka kieruje błędy, ma dodatkowy port wyjściowy error. Niesie on WorkflowError: code, message, details i retryable. Zwykłe wyjście węzła istnieje tylko na pozostałych portach, więc publikacja odrzuca binding, który czyta wyjście na ścieżce błędu albo błąd na ścieżce sukcesu. Port błędu musi dokądś prowadzić, a obie ścieżki mogą się połączyć w logic.merge.

Gdy kroki działają równolegle, każda gałąź ma własny handler. Gałąź, która zawodzi bez ścieżki błędu, kończy run błędem, a kroki obok niej nie zaczynają niczego więcej: ich oczekujące zatwierdzenia i timery są wycofywane, tak samo jak runy agentów, na których są zaparkowane. Krok, którego praca już trwała, kończy się i jest zapisany jako zakończony po zatrzymaniu runa, z zachowanym wyjściem. Ponowienie runa używa go zamiast robić to jeszcze raz, a użyty ponownie krok Output albo Respond to webhook zgłasza swoją odpowiedź na nowo. Publikacja odrzuca error.handle zasilany przez dwa kroki, które mogą zawieść w tym samym runie.

Krok wymagający uwagi można rozstrzygnąć na runie, w którym jest. Uznanie go za nieudany kończy go kodem STEP_MARKED_FAILED, który wychodzi portem error kroku, jeśli ten kieruje swoje błędy, albo trafia do polityki błędów elementu jego pętli. Zakończenie runa błędem kończy run tym błędem bez względu na połączenia grafu. Anulowanie runa albo jego porażka zamyka też krok wymagający uwagi.

error.handle przyjmuje ten błąd na porcie in i wychodzi pierwszą gałęzią, której code i retryable pasują, albo gałęzią default, która musi być podłączona. error.raise kończy swoją gałąź błędem o kodzie, komunikacie i szczegółach ustalonych przez autora.

Niektórych błędów nigdy się nie kieruje. Odebrany dostęp, wyczerpany budżet (runa albo agenta), anulowany run, miniony termin, limit węzłów na run i efekt o nieznanym wyniku kończą run bez względu na to, jak połączono graf. Konflikt rewizji albo błąd walidacji można skierować, ale nigdy nie jest ponawiany na ślepo, bo te same dane wejściowe zawiodą tak samo.

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.

Pętle

control.foreach uruchamia swoje ciało raz dla każdego elementu zbindowanej listy items, po jednym elemencie i po kolei. Potem idzie dalej portem done z results w kolejności wejścia, errors i count. Ciało zaczyna się od loop.item, podłączonego z portu body pętli, który udostępnia item, index i count. Kończy się na loop.yield, którego zbindowane value jest wynikiem iteracji. Żadna krawędź nie prowadzi z powrotem do pętli. Krok w ciele może bindować do wszystkiego, co działało przed pętlą, a nic spoza ciała nie może bindować do jego wnętrza.

Lista zostaje zamrożona, gdy pętla startuje, więc iteracja nigdy nie widzi źródła, które zmieniło się w trakcie runa. Lista dłuższa niż WORKFLOW_FOREACH_MAX_ITEMS albo większa niż WORKFLOW_FOREACH_MAX_MANIFEST_BYTES jest odrzucana, a nie przycinana. Pusta lista daje results: [] bez uruchamiania ciała. Kroki każdej iteracji działają we własnym zakresie, z własnymi próbami, kluczami idempotencji i kosztami. Następna iteracja jest planowana w transakcji, która kończy poprzednią, więc restart wznawia od właściwego indeksu i nigdy nie powtarza potwierdzonego zapisu. Zatwierdzenie wewnątrz iteracji wznawia tę samą iterację.

Przy item_error_policy: stop, domyślnym, pętla kończy się błędem przy pierwszej nieudanej iteracji, a szczegóły błędu niosą scope_path tej iteracji. Przy collect wynik elementu to null, błąd trafia do errors, a pętla działa dalej. Pętle zagnieżdżają się najwyżej na WORKFLOW_FOREACH_MAX_DEPTH poziomów, a każdy przebieg węzła, który tworzy run, liczy się do WORKFLOW_RUN_MAX_NODE_RUNS. Nie ma pętli while ani równoległego map.

Ciało może rozgałęziać się na równoległe odnogi, które działają obok siebie w obrębie iteracji, a iteracja kończy się, gdy żaden jej krok nie jest aktywny. Gdy krok w iteracji zawodzi bez ścieżki błędu, collect pozwala jego sąsiadom w tej iteracji dokończyć, po czym przechodzi do następnego elementu. stop anuluje ich, wycofując ich zatwierdzenia, timery i zaparkowane runy agentów, i kończy pętlę błędem. Następna iteracja nigdy nie startuje, dopóki poprzednia ma jeszcze działający krok.

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

Siedem węzłów czyta i zapisuje Virtual Tables przez ten sam serwis, którego używają konsola, API i narzędzia tabel agenta. Walidacja, konflikty rewizji, limity, historia, paragony i audyt są takie same na każdej powierzchni.

Węzeł Co robi Efekt
table.record.create Dodaje rekord write
table.record.upsert Tworzy albo aktualizuje rekord z external id write
table.record.update Zmienia część komórek rekordu write
table.record.delete Usuwa rekord, zachowując jego historię write
table.record.get Znajduje jeden rekord po id albo external id read
table.record.query Czyta jedną stronę rekordów, z filtrem i sortowaniem read
table.create Tworzy nową tabelę z typowanym schematem write

Każdy węzeł rekordów przypina swoją tabelę w konfiguracji. Przy publikacji jest ona sprawdzana względem autora grafu, a przy każdym runie ponownie jako principal runa. Wartości są bindowane i podawane po id albo etykiecie kolumny. Rekord wraca z wartościami dwa razy: values po id kolumny, do bindingów, i fields po etykiecie, do czytania. Klucz, który nie wskazuje żywej kolumny, kończy się błędem UNKNOWN_COLUMN.

Zapis niesie klucz operacji kroku, więc ponowiony krok odtwarza swój pierwszy zapis. Update, upsert albo delete bez zbindowanej rewizji zapisuje przy bieżącej rewizji rekordu, odczytanej pod blokadą rekordu, a odtworzenie działa nawet wtedy, gdy pierwszy zapis już przesunął tę rewizję. Rewizja, która się przesunęła, to REVISION_CONFLICT, który nie jest ponawiany: ta sama rewizja skonfliktowałaby się znowu, więc skieruj go przez error.handle do świeżego odczytu. Brakujący rekord to found: false z table.record.get, a nie błąd. table.record.query czyta najwyżej 100 rekordów na stronę i podaje has_more. Nigdy sam nie czyta całej dużej tabeli.

table.create to osobny węzeł i wymaga tables:create. Jego wyjście niesie nową tabelę jako referencję, do której można zbindować table kolejnego węzła, oraz id każdej kolumny po etykiecie. Tabeli, którą czyta albo zapisuje żywy workflow, ani kolumny, którą przypina, nie da się zarchiwizować, dopóki używa jej bieżąca wersja tego workflow.

Cztery kolejne kroki odczytują, jakie tabele ma organizacja, i niczego nie zmieniają.

Krok Robi Wychodzi przez
table.list wymienia tabele widoczne dla podmiotu, szukane po nazwie out, z tables i total
table.describe czyta nazwę i kolumny jednej tabeli out, z columns
table.exists czy istnieje tabela o dokładnie tej nazwie yes, z jej id, albo no
table.record.exists czy jakikolwiek rekord pasuje do filtrów yes, z id pierwszego, albo no

table.exists porównuje całą nazwę bez względu na wielkość liter, więc workflow może utworzyć swoją tabelę przy pierwszym uruchomieniu, a potem jej używać. Oba pytania wychodzą dokładnie jednym portem, a logic.merge może z powrotem połączyć gałęzie.

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.

Pliki

Plik, który tworzy krok, jest zapisywany jako plik jego runa i przekazywany dalej jako FileRef: id, typ, jakim okazały się jego bajty, i rozmiar. Krok czyta plik tylko wtedy, gdy utworzył go jego własny run albo run został z nim uruchomiony - FileRef powiązany w grafie, sprawdzany przy publikacji wobec runa, który autor widzi. Każdy inny plik, innej organizacji albo innego runa, to FILE_NOT_FOUND, więc znajomość id niczego nie daje. Pliki runa są wymienione i do pobrania na jego stronie.

Węzeł Robi Efekt
http.download Pobiera plik przez HTTP, strumieniowo, i go zapisuje write
http.upload Wysyła plik do endpointu HTTP, strumieniowo z magazynu write
file.read Czyta plik jako tekst, wartość JSON albo wiersze CSV read
file.write Zapisuje tekst, wartość JSON albo wiersze jako plik write
text.extract Tekst pliku TXT, JSON, CSV, tekstowego PDF albo DOCX read
convert.csv_to_json Plik CSV jako plik JSON z wierszami write
convert.json_to_csv Lista płaskich obiektów JSON jako plik CSV write
convert.text_to_file Tekst jako plik TXT write
convert.pdf_to_png Wybrane strony PDF jako obrazy PNG write
image.transform Przycina, skaluje, obraca albo konwertuje obraz write

Pobieranie stosuje te same reguły SSRF i poświadczeń co http.request, do pięciu przekierowań. Jego treść jest liczona w trakcie napływu i odrzucana powyżej max_bytes, a typ jest rozpoznawany po bajtach, więc nagłówek nie oszuka expected_content_types. text.extract nie robi OCR: zeskanowana strona kończy krok błędem TEXT_EXTRACTION_NEEDS_OCR i wymienia strony. Uszkodzony dokument to DOCUMENT_CORRUPT, dokument Word, który rozpakowuje się ponad limity archiwum obowiązujące upload na czacie, to DOCUMENT_TOO_LARGE, a PDF chroniony hasłem DOCUMENT_ENCRYPTED.

Obraz jest mierzony, zanim zostanie zdekodowany. Jego szerokość razy wysokość, obszar przycięcia i żądany rozmiar są każdy sprawdzane wobec CHAT_IMAGE_MAX_PIXELS, a wynik nie niesie żadnych metadanych źródła. Każdy krok, który zapisuje plik, zapisuje nowy przy każdej próbie, więc jest at_least_once. Pliki runa mogą łącznie zająć WORKFLOW_RUN_MAX_FILE_BYTES, a pliki organizacji WORKFLOW_ORG_MAX_FILE_BYTES. Krok, którego plik przekroczyłby któryś z tych limitów, kończy się błędem FILE_QUOTA_EXCEEDED i niczego nie zapisuje. Aby zwolnić miejsce, usuń stare runy albo ustaw retencję runów w workflow, które tworzą pliki.

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

Konwersje CSV dopuszczają najwyżej 10 000 wierszy, 1000 kolumn i 1 000 000 komórek. Oba kierunki ograniczają zakodowany wynik przez max_bytes kroku przed zapisaniem pliku. Ekstrakcja tekstu PDF odrzuca ponad 2000 stron i przestaje czytać strony po przekroczeniu max_chars. Pliki wskazane bezpośrednio są ponownie sprawdzane przy przyjęciu runa względem bieżącego dostępu jego osoby do źródłowego workflowu.

Wyniki sandboxa są ograniczane także podczas strumieniowego pobierania z hosta. Plik rosnący po pomiarze nie omija pozostałego wspólnego limitu bajtów; JSON wyniku też do niego wchodzi. Zakończone wywołane runy zostają do końca runa wywołującego, żeby retencja nie powodowała powtórzenia ich skutków przez oczekujący krok.

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

Dwa węzły uruchamiają Pythona, do dwóch rodzajów pracy.

code.python.simple uruchamia krótki skrypt w sandboxie Monty, który nie ma systemu plików, sieci i ma małą bibliotekę standardową. Skrypt czyta powiązane wartości jako args, a jego ostatnie wyrażenie jest result kroku, które musi być wartością JSON. Tylko liczy, więc jest pure i wymaga code:execute.

W edytorze skrypt kroku z kodem pisze się w edytorze kodu: Python albo JavaScript podświetlony w obu motywach, Tab i Shift+Tab do wcięć, Enter zachowujący wcięcie, nawiasy i cudzysłowy domykane podczas pisania, nawias przy kursorze obrysowany razem z pasującym oraz klucze powiązanego args podpowiadane po wpisaniu args[" albo, w JavaScript, args.. Esc, a potem Tab, opuszcza edytor. Test step uruchamia sam skrypt na tym, co przekazały kroki przed nim.

code.python.sandbox uruchamia pełnego Pythona z pakietami i plikami runa na połączeniu sandboxd organizacji i wymaga sandbox:execute. Skrypt znajduje pliki wejściowe w inputs, zapisuje pliki do outputs i ustawia result. To trwałe zadanie: pierwsze wysłanie uruchamia je w tle, a każde kolejne sprawdza je w tej samej sesji, więc zrestartowany worker łączy się ponownie, zamiast uruchamiać je od nowa. Do sandboxa nigdy nie trafia żadne poświadczenie platformy, a to, do czego skrypt sięga poza swoimi plikami, zależy od konfiguracji runtime'u hosta - do niezaufanej pracy wybierz runtime bez sieci.

Co się stało Wynik
Wynik nie jest JSON-em PYTHON_OUTPUT_NOT_JSON
Skrypt rzucił wyjątek albo przekroczył limit PYTHON_ERROR
Zadanie przekroczyło timeout_seconds PYTHON_SANDBOX_TIMEOUT, sesja wyczyszczona
Brak używalnego połączenia sandboxd SANDBOX_UNAVAILABLE
Zapisał więcej niż 20 plików albo 100 MB albo wypisał więcej niż 10 MB PYTHON_OUTPUT_TOO_LARGE, zmierzone w sandboksie, zanim cokolwiek zostanie pobrane, a sesja wyczyszczona
Nie udało się połączyć z hostem SANDBOX_UNREACHABLE, ponawiane

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 uruchamia JavaScript na Node jako to samo trwałe zadanie, na tym samym połączeniu sandboxd, i wymaga sandbox:execute. Skrypt jest ciałem funkcji asynchronicznej: czyta powiązane wartości jako args, swoje pliki wejściowe w inputs, zapisuje pliki do outputs, może używać await, a to, co zwróci przez return, jest result kroku - null, gdy nie zwraca niczego. require ładuje moduły samego Node i to, co runtime ma zainstalowane. Wybierz runtime z Node.

Jego błędy są błędami sandboxa Pythona, nazwanymi dla JavaScriptu: rzucony błąd to JAVASCRIPT_ERROR, wynik, który nie jest wartością JSON - funkcja, BigInt - to JAVASCRIPT_OUTPUT_NOT_JSON, a limit czasu i limity wyjścia to JAVASCRIPT_SANDBOX_TIMEOUT i 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. Uruchamia opublikowaną wersję innego workflow, zaczynającą się od Called by a workflow, z powiązanym input, tak jak działa ten przebieg. Krok czeka, aż wywołany przebieg się skończy, i przekazuje jego output albo kończy się błędem CALLED_WORKFLOW_FAILED z błędem wywołanego przebiegu; przy wyłączonym Wait for it to finish od razu przekazuje uruchomiony przebieg. Wywołany przebieg jest powiązany z krokiem i z łańcuchem tego przebiegu. Wywołanie workflow, który już działa wyżej w łańcuchu, kończy się WORKFLOW_CALL_LOOP, a głębsze niż pięć wywołań - WORKFLOW_CALL_TOO_DEEP. Przebieg testowy wersji roboczej naprawdę wywołuje opublikowany workflow.

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.

Dodawanie węzła

Węzeł to paczka w backend/app/workflows/nodes/: __init__.py rejestruje NodeDefinition, _handler.py go implementuje, a README.md wyjaśnia, po co istnieje. load_builtins importuje paczkę. tests/test_workflow_node_layout.py pilnuje tego układu. Handler zwraca Completed, Waiting, Failed albo Uncertain i nigdy nie rzuca wyjątku. Run, dla którego działa, czyta z app.services.workflow_execution.context.current().

Typowany węzeł deklaruje trzy modele Pydantic: konfigurację, ustawianą w edytorze i zamrażaną przy publikacji; wejście, którego pola wypełniają bindingi; oraz wyjście, do którego bindują się późniejsze kroki. extra="forbid" na każdym z nich trzyma literówkę z dala od opublikowanego grafu. retry_guarantee wybierz według tego, co zrobiłoby powtórzenie wywołania: idempotent, gdy handler sam czyni powtórzenie nieszkodliwym - zapis do tabeli przekazuje operation_key() - at_least_once, gdy powtórzenie jest dopuszczalne, i none, gdy nie jest. Węzeł, który czyta albo zapisuje zasób, deklaruje check_resources, które publikacja uruchamia względem autora, a każdy run względem swojego principala.

Konsola rysuje węzeł z jego definicji, z ikoną i odcieniem w frontend/src/components/workflows/node-visuals.ts. Przetestuj handler bezpośrednio pod kątem odmów i przeprowadź go raz przez tests/integration/workflow_run_support.py, żeby jego konfiguracja, bindingi i wyjście zostały dowiedzione w prawdziwym runie.

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.