Use the AI assistant
The assistant turns a plain-language instruction into a workflow graph: it
calls POST /api/workflows/assist (apps/api/src/server/routes/assist.ts,
apps/api/src/server/workflow-assist.ts), which asks an LLM for a spec,
validates it, and hands back a graph for you to review. It never writes to the
definition store. You reach it from two places: the Assistant starting
point of the creation dialog, and the Build with AI panel above the
editor's canvas. This page assumes you know the dataflow model and can read the
DSL — see Your first workflow and
Workflow DSL — and the rest of the editor; see
Use the visual editor.
Prerequisites
- The API-proxy process needs a configured LLM provider:
ASSIST_PROVIDER(defaultanthropic) andASSIST_MODEL(defaultclaude-sonnet-4-5), plus that provider's API key (e.g.ANTHROPIC_API_KEY). There's no startup check — an unconfigured provider only fails the first time you generate. - Three more variables bound a generation:
ASSIST_MAX_ATTEMPTS(default 4, 1 to 5) caps the number of proposals,ASSIST_BUDGET_MS(default 150 000) caps the time all attempts share, andASSIST_MAX_TOKENSsets the output limit of each call (Anthropic: 16 384 when unset; OpenAI-compatible providers: no limit is sent when unset). See Environment variables.
Create a workflow from an instruction
- From the workflow list, click New. In the creation dialog, pick the Assistant starting point.
- Describe the workflow in Instruction for the assistant — trigger, steps, conditions. The three example chips fill in an instruction you can edit.
- Leave Trigger on Chosen by the assistant, or pick one. With a routed trigger picked, the Filter section appears below it, prefilled with the default filter of the trigger's event when it has one (see Choose which events start a workflow).
- Set the name. The target file is derived from it once, when you click Create and generate (or press ⌘/Ctrl + Enter in the instruction).
- The editor opens and starts the generation as soon as the catalogue has loaded. Nothing is saved yet: review the graph, then Save the draft. The first save creates the workflow as a draft; it only receives events once published.
With Chosen by the assistant, the editor draws a provisional trigger
(the first one in the list) and sends the current spec without its trigger
key, so the model picks the type — and a selector if the instruction calls
for one. Picking a trigger type by hand in the editor ends that mode.
Revise the current graph with a follow-up instruction
- Open a workflow in edit mode, or stay in the editor after a first generation.
- In Build with AI, describe the change, not the whole workflow — e.g. "add a guard that only continues when the count is above 5".
- Click Generate. The request carries the graph currently shown in the editor, unsaved wiring included, and the target file (so the digest does not list this workflow among the other receivers of its event).
- Review carefully: a successful response replaces the whole graph (nodes, connections, name, exposed params, inline values). It's not a surgical merge.
The trigger follows these rules when a proposal is loaded:
- the type and the description are the proposal's;
- the filter is the proposal's when it carries one; otherwise the current filter is kept if the type did not change, and dropped if it did. The assistant therefore cannot remove a filter: use Remove the filter in the trigger inspector;
- the configuration is the proposal's, or the current one when the proposal has none and the type did not change;
- a webhook keeps its current URL identifier.
The model is told to keep the name and the trigger (type, selector, configuration) of the current workflow unless the instruction asks to change them.
Read the result
- On success, the flash reads "Workflow proposed by the AI — check the graph, adjust, then save." Nothing is written yet.
- On failure, the panel shows what the endpoint returned:
- empty instruction (
400) — type something. - a list of validation errors (
422, joined with·) — no attempt passed validation (see What gets validated). When the response carries the closest proposal, Load the proposal loads it into the editor with its errors, to fix by hand before publishing. A last line may give the cause of the stop: time budget reached, a model that did not answer within the call's timeout (at any attempt), or a model call that failed after the first attempt. Assistant indisponible : …(503) — the first LLM call failed: most commonlyProvider « <name> » non configuré (clé API en variable d'environnement manquante)., or a network error reaching the provider. Fix the environment and retry; the instruction is kept.réponse HTTP <code> sans détail— the response carried no JSON body, typically a gateway or load-balancer timeout (502,504). See Response time.
- empty instruction (
What gets validated before you see it
assistWorkflow (apps/api/src/server/workflow-assist.ts) never hands you
raw model output:
- It prompts the model with a digest of the catalogue: for every node type,
its label, description (1 000 characters), ports and configuration fields — a coded
field lists its allowed codes (
info|warning|critical), and a « Jeux de codes » section gives each code with its label. A trigger's entry depends on its family: a trigger with its own address lists the fields a filter can read (the example's keys with their values) and « routage épinglé » when a filter has no effect; a dedicated trigger gives its event, fixed (event=BioResultReceived (fixe)), and its outputs; the line of Événement reçu readsconfig.event : un type de la section Événementsand states that its outputs are the declared fields of the event (those with a type on itsevent :line, in order, thendone), never the context identifiers or the envelope (eventId,occurredAt). An « Événements » section then gives one entry per declared event: its description, its context kinds, the fields a filter can read — each contract field with its type, its required mark and its example value, then the context identifiers and the envelope, for BioResultReceived (shortened here):event : analyte (String, requis)="creatinine", value (Decimal, requis)=160, unit (String)="µmol/L", …, patientId (requis), encounterId (requis), eventId, occurredAt(at most 20 fields) —, its routing (the published workflows that already receive it, each with its filter — « sans filtre » without one, « filtre en erreur : expression vide » for an empty one — and its effects), its default filter when it has one, and its dedicated triggers (« aucun » when no plugin provides one). The model is told to prefer the dedicated trigger of an event when there is one, since its outputs are typed, and otherwise to use Événement reçu with the event intrigger.config.event. Two distinct types that share a name are written with their origin (Quantity (type du cœur),Quantity (@posos/common)), and a « Homonymes » line lists each such name. The rules that come with it cover, among others, templates in text configuration fields,context.read, the three memory scopes, type names written at the exact case, core kinds versus structured types of the same name (a core measure is compared on its fields in a condition, not broken), and the trigger referenced in connections astrigger.<port>, nevertrigger.<type>.<port>. The digest has a size budget (60 000 characters). Beyond it, it is rebuilt shorter, step by step: descriptions at 500 then 280 characters, then without the example values of the event fields, then descriptions at 200, 160 and 120 characters (configuration fields at 60, then 40, in the last two steps). A digest still beyond the budget is sent as is and logged (componentassist, « assistant : condensé du catalogue au-delà du budget, envoyé tel quel. … »). It adds your instruction and, if present, the current graph. - It parses the model's JSON reply against the
WorkflowSpecschema, then runs the same static validation (validateSpec) as publication — including the expression checks of the trigger filter, guard and switch conditions (syntax with column, decimal separator, unknown transforms, functions and variables), the template placeholders, the switch case rules, and the allowed codes of coded configuration fields — then the terminology check. - If a check fails, it asks again with your instruction, the current graph, its previous proposal and that proposal's errors, so the model corrects the proposal instead of starting over. A reply cut by the output limit is detected and the model is asked for a compact answer.
- It stops after
ASSIST_MAX_ATTEMPTSproposals or when the time budget leaves less than 20 s for another attempt. The endpoint then returns422with the errors of the closest proposal, and that proposal.
Every published workflow that receives an event starts on it, independently of
the others: a second workflow on an event is not an error. The model is told
not to reproduce an effect that a listed workflow already produces for that
event, and to say so. It only sets a selector when the instruction narrows
the events handled or when the event has a default filter, which it keeps,
combined with && with the instruction's conditions.
A generated workflow that loads into the editor passed the same checks as a publication — see Two-level validation and Validate a workflow.
Response time
A generation takes one model call per attempt. All attempts share a budget of
150 s (ASSIST_BUDGET_MS); each call is limited to 110 s or to the remaining
budget, whichever is shorter. A call without an answer in that time ends the
generation with a 422 naming the attempt, not a 503: a large workflow
written in one call can exceed it, and a shorter instruction, or a workflow
built in several instructions, fits. The console's relay to the API gives up
after 170 s with a readable error. Behind a load balancer, set its backend timeout
above 170 s: Google Cloud's default is 30 s, which cuts longer generations with
a bare 502.
Duration depends mostly on the model. A model that reasons before answering can take over a minute to revise an existing workflow; a model without reasoning answers in a few seconds, which leaves room for more attempts.
The proxy logs each generation under the assist component, without the
instruction: provider, model, duration in milliseconds, number of attempts,
cause of the stop, truncated replies, output limit, and the first validation
errors or the provider error.
Save what you generated
- Adjust anything by hand — wire ports, branch with guards and switch, loop with map, the trigger's filter.
- Optionally simulate it against a forged event.
- Click Save the draft, then Publish when it is ready. The confirmation names the other workflows that start on the same event and their effects; publication revalidates everything on the server.
Related
- Use the visual editor — the creation dialog and the editor this assistant lives in.
- Choose which events start a workflow — what the trigger filter decides.
- Workflow DSL — the schema the assistant's output must conform to.
- Simulate a workflow — try a generated graph before saving it.
- Use agents in a workflow —
agent.*nodes run their own LLM call at runtime; this assistant only shapes the graph at authoring time. - Environment variables
— the
ASSIST_*variables and the provider API keys.