Node Types
Every workflow is a directed graph of nodes. Each node has a type that determines what it does at runtime, and a config object that controls its behavior. Nodes are connected by edges; the runtime follows edges to determine execution order.
Palette categories
| Category | Node types |
|---|---|
| Core | agent, classify, end, note |
| Tools | file_search, guardrails, mcp |
| Logic | if_else, while_loop, user_approval, sub_workflow |
| Data | transform, set_state |
There is also a built-in start node that every workflow begins from. It is added automatically and cannot be deleted.
on_errorAny node's config accepts on_error: skip to let the rest of the graph continue past that node's failure instead of halting the run — see Run Infrastructure → Resilience & fault handling.
Core
agent
Runs an AI agent for one turn. The node sends a prompt to an LLM and stores the response in workflow state.
Modes:
| Mode | How to configure |
|---|---|
| Prompt mode | Fill in system_prompt directly in the properties panel |
| Skill mode | Set skill_id to reference a saved Skill. The skill's system prompt, tools, and knowledge are merged in |
Config fields:
| Field | Description |
|---|---|
system_prompt | Inline instructions for the agent (Prompt mode) |
skill_id | ID of a Skill to load (Skill mode — overrides inline prompt) |
model | Model override. Leave blank to use the instance default |
tools | List of tool names to enable for this node |
knowledge_ids | Knowledge bases to search before the agent responds |
execution_mode | auto (default) or review — see Run Infrastructure |
input_key | State key to read as the user message. Defaults to input |
output_key | State key to write the agent response into. Defaults to output |
classify
Routes workflow execution along one of several labelled edges based on AI classification.
The node sends the current state to an LLM with a list of categories. The model returns one category label, and the runtime follows the matching outgoing edge.
Config fields:
| Field | Description |
|---|---|
categories | Comma-separated or JSON list of label strings (e.g. positive, negative, neutral) |
system_prompt | Optional instruction to guide the classification model |
model | Model override |
input_key | State key with the text to classify |
Edges: Draw one outgoing edge per category label. Label the edge with the exact category string.
Label matching: The runtime matches the model's output against your category list using these rules in order:
- Exact match (case-insensitive) — preferred.
- Partial match — if the LLM adds punctuation or a trailing explanation, the runtime checks whether any class label appears as a substring of the output.
- Fallback — if neither matches, the first category in the list is used, and a warning is logged. The workflow does not stop.
Always list the most likely or safest category first so the fallback is a sensible default.
The workflow stops with an error only if the categories list is empty — validate your classify nodes before publishing (the graph validator rejects empty class lists at stage time).
end
Terminates workflow execution and marks the run as complete. Every workflow must have at least one end node.
Config fields: none — this node requires no configuration.
note
A sticky-note annotation on the canvas. Notes are not executed; they exist purely for documentation and team communication within the editor.
Config fields:
| Field | Description |
|---|---|
content | The text to display on the canvas |
Tools
file_search
Performs a vector search over one or more Knowledge bases and injects the retrieved chunks into workflow state.
Config fields:
| Field | Description |
|---|---|
knowledge_ids | List of Knowledge base IDs to search |
query_key | State key containing the search query. Defaults to input |
output_key | State key to write retrieved context into. Defaults to context |
top_k | Number of chunks to retrieve (default: 5) |
guardrails
Evaluates content against a list of rules using an LLM call. If the content fails any rule, the workflow halts immediately with an error — execution does not continue to downstream nodes.
Config fields:
| Field | Description |
|---|---|
rules | List of rule strings to enforce (one per line in the editor), e.g. "No PII in output", "Must cite a source" |
input_key | State key with the content to check |
The model is asked to reply PASS or FAIL: <reason>. The runtime handles all outcomes:
| LLM output | What happens |
|---|---|
Begins with PASS | Workflow continues to the next node |
Begins with FAIL | Workflow halts immediately; the reason is surfaced as the run's error message |
| Empty output | Treated as an error (not a silent pass) — run halts with [guardrails: LLM returned empty output] |
A node_complete SSE event is emitted before the node_error event, so the run timeline always has a complete record of every node that executed.
mcp
Calls an external tool via the Model Context Protocol. Connects to a remote MCP server (HTTP or SSE transport), invokes a specific tool, and writes the result into workflow state.
Config fields:
| Field | Description |
|---|---|
server_url | HTTP(S) URL of the MCP server (e.g. https://my-mcp.example.com/mcp) |
tool_name | Name of the tool to call on the server |
arguments | JSON object of arguments to pass to the tool |
output_key | State key to write the tool response into (default: output) |
auth_token | Bearer token for the MCP server. Stored encrypted; displayed as •••••••• in the UI and API responses. Leave blank if the server requires no auth. |
Security:
server_urlis validated against an SSRF allowlist before the request is sent. Private IP ranges (10.x,192.168.x,169.254.x,127.x,::1, etc.) and link-local addresses are blocked to prevent server-side request forgery.auth_tokenis stored encrypted at rest and is never returned in API responses — editing the node shows the sentinel value••••••••. To update the token, type a new value; to keep the existing one, leave it as-is.
If you are using hrida-mcpo as the MCP server, the server name in config.json must match the agent or Digital Employee slug (e.g. hr-agent). The tool endpoint will be at {mcpo_base_url}/{server_name}/tools/call.
Logic
if_else
Evaluates a Python expression against the current workflow state and branches execution.
Config fields:
| Field | Description |
|---|---|
condition | Python expression that evaluates to True or False. State variables are available via state and input (e.g. state.get('score', 0) > 0.8) |
true_label | Label of the outgoing edge to follow when the condition is True (default: true) |
false_label | Label of the outgoing edge to follow when the condition is False (default: false) |
Edges: Draw two outgoing edges labelled to match true_label and false_label.
Expression context:
| Variable | Value |
|---|---|
state | Full workflow state dictionary — state['key'] or state.get('key', default) |
input | The current workflow input string |
Allowed built-ins: abs, all, any, bool, dict, enumerate, filter, float, int, len, list, map, max, min, reversed, round, set, sorted, str, sum, tuple, zip.
isinstance and repr are not availableUnlike every other built-in listed above, these two can't be added to the sandbox — the underlying expression library (simpleeval) hardcodes both in its own safety blocklist for defense-in-depth, rejected at evaluator construction time no matter how the sandbox is configured. If you need a type check, compare against a literal instead (e.g. type(state['x']) == type('') won't work either — restructure the workflow to avoid needing runtime type introspection, or do the check in an upstream transform/agent node using plain Python string/value comparisons).
Validation: The expression is syntax-checked when you stage or publish the workflow. Errors surface as validation warnings before the workflow can go live.
If the expression raises an error at runtime, the condition defaults to False (takes the false branch) and a warning is attached to that node's node_complete event — visible in the run stream/timeline, not just the server log.
while_loop
A conditional router that repeats a subgraph path until a condition becomes False or a maximum iteration count is reached. The node emits one of two output values on each execution — "continue" or "done" — and the runtime follows the matching outgoing edge.
Config fields:
| Field | Description |
|---|---|
condition | Python expression evaluated on each pass. Same context as if_else — state and input are available. When True the loop continues; when False execution exits. |
max_iterations | Hard limit on iterations (1–100, default: 10). When reached, the loop exits regardless of the condition to prevent runaway workflows. |
Edges: Draw two outgoing edges from this node:
| Edge label | When followed |
|---|---|
continue | Condition is True and max_iterations not yet reached — loop body executes |
done | Condition is False or max_iterations reached — loop exits |
Iteration counter: The current iteration count is stored in workflow state under the key _while_{node_id}_iter. Downstream nodes can read this key (e.g. in a transform or if_else) if they need to know how many times the loop has run.
Example — retry until quality passes:
start
→ set_state [state.quality = 0]
→ while_loop [condition: state.get('quality', 0) < 0.8, max_iterations: 5]
→ (continue) → agent → set_state [state.quality = ...] → while_loop
→ (done) → end
The runtime uses BFS (breadth-first) topological execution. True back-edges (a node downstream of while_loop looping back to it) are placed in a special "last level" and run once per workflow execution. For genuine multi-iteration loops, the while_loop node itself re-evaluates its condition each time it is reached in the BFS traversal — effective when the loop body updates the condition variable and the graph topology forms a cycle.
If the condition expression fails at runtime, it defaults to False (exits the loop) and a warning is attached to that node's node_complete event — visible in the run stream/timeline, not just the server log.
user_approval
Pauses workflow execution and creates an inbox task for a human reviewer. Unlike agent nodes in review mode (which run the LLM first and pause on its output), user_approval pauses immediately — the configured prompt text itself is what the reviewer sees and decides on.
Config fields:
| Field | Description |
|---|---|
prompt | Text shown to the reviewer as the task description |
review_group | Who can see and decide the task — a space role (e.g. lifecycle_manager) or an admin-console group (group:{id}), selectable in the properties panel |
review_timeout_minutes | Minutes before the task expires (minimum 15, server-enforced) |
timeout_action | What happens if no one decides in time: auto_proceed, reject, or escalate |
timeout_escalate_group | Only used when timeout_action is escalate — the role or group the task is reassigned to |
When this node is reached, the run pauses and an inbox task is created. The reviewer sees it in Dashboard > Inbox, decides (approve / reject / delegate), and the run resumes from the next node in sequence — user_approval does not branch by edge label. See Inbox for the full decision flow.
Data
transform
Applies a Python expression to reshape or compute values in workflow state.
Config fields:
| Field | Description |
|---|---|
expression | Python expression. The result is assigned to output_key. State variables are available as locals |
input_key | State key to read (exposed as value in the expression) |
output_key | State key to write the result into |
Expression context:
| Variable | Value |
|---|---|
state | Full workflow state dictionary |
input | Current workflow input string |
value | Shorthand for the value at input_key in state |
Allowed built-ins: Same whitelist as if_else — abs, all, any, bool, dict, enumerate, filter, float, int, len, list, map, max, min, reversed, round, set, sorted, str, sum, tuple, zip. isinstance/repr are not available (see the note above if_else's expression context).
Example: Strip whitespace from a string:
value.strip()
Compute a word count:
len(value.split())
Build a summary string using state keys:
f"Score: {state.get('score', 0):.2f} — {state.get('label', 'unknown')}"
Validation: Syntax is checked at stage/publish time. If an expression fails at runtime, the error is written as the node's output (prefixed [transform error: ...]) rather than crashing the run, giving you a visible signal without losing the full run timeline.
set_state
Sets a key in workflow state. The value supports the same sandboxed Python expressions as transform and if_else — useful for counters, computed flags, or dynamic values, not just static literals.
Config fields:
| Field | Description |
|---|---|
state_key | The state key to write |
state_value | A literal string (e.g. pending), or an expression referencing state/input (e.g. state['count'] + 1). If the value isn't a valid expression, it's stored as the literal text — existing static-value workflows are unaffected |
sub_workflow
Invokes another published workflow inline and uses its final output as this node's output. Wired by the workflow author at build time — the target is fixed in the graph, not decided dynamically.
Config fields:
| Field | Description |
|---|---|
workflow_id | The published workflow to invoke (selected from a dropdown of published workflows) |
input_expression | Optional Python expression for the child's input (e.g. state['summary']). Leave blank to pass the previous node's output |
Unlike the trigger_workflow tool, a sub_workflow node does not guard against calling itself or forming a cycle (A → B → A). Avoid wiring a workflow's sub_workflow node back to itself or to an ancestor in its own call chain — this can hang until the workflow's wall-clock timeout is reached.
If you need an AI agent to decide at runtime whether and which workflow to delegate to, use the trigger_workflow tool on an agent node instead — it includes a built-in recursion guard.
The run this node spawns records parent_run_id/parent_node_id pointing back to the calling run and this node — so the full call chain (however many levels deep) can be viewed as a tree instead of disconnected runs. See Traceability & Audit Log → chain correlation.
Edges and conditions
Edges connect an output connector on one node to an input connector on another. Most edges are unconditional — the runtime follows them automatically after the source node finishes.
For classify and if_else, edges carry a label matching the node's output (a category name, or true/false). The runtime follows only the edge(s) whose label matches — every node reachable solely through a non-matching branch is skipped, not just left unexecuted in isolation.
user_approval does not branch by edge label — the run always continues to the next node in sequence after a decision. while_loop emits "continue" or "done" and the runtime skips all nodes reachable only through the non-matching branch (same blocking logic as if_else and classify).
To label an edge: click the edge after drawing it and type the label in the properties panel.
Canvas tips
- Add a node: Drag from the palette onto the canvas.
- Connect nodes: Hover over a node until connectors appear; drag from a connector to another node.
- Delete: Select a node or edge and press
Delete/Backspace. - Multi-select: Hold
Shiftand click multiple nodes; drag to box-select. - Zoom: Scroll wheel, or use the zoom controls in the bottom-right.
- Auto-arrange: The AI Assist generate feature places new nodes with automatic layout. Manual positioning is always available by dragging.