Graph Format
A workflow is stored as two JSON arrays: nodes and edges. This format is compatible with React Flow and is what you send to the API when creating or running a workflow.
Node Schema
{
"id": "fetchEntries",
"type": "get_entries",
"data": {
"label": "Fetch Entries",
"isExecuted": false,
"handles": ["outputs"],
"schema": {},
"params": {
"datasetId": {
"value": 42,
"isExpression": false,
"isAttachedToInputNode": false
},
"numberOfEntries": {
"value": "{{ $input.limit }}",
"isExpression": true,
"isAttachedToInputNode": false
}
},
"inputs": [],
"outputs": [],
"errors": []
},
"position": { "x": 0, "y": 0 },
"isSelected": false,
"isDragging": false
}
Node Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier for this node within the workflow. Used to reference the node's output in template expressions |
type | string | The node type — see Node Types |
data.label | string | Display name shown in the UI |
data.isInput | boolean? | Marks this as a group input boundary node |
data.isOutput | boolean? | Marks this as a group output boundary node |
data.isTool | boolean? | Marks this node as an agent tool |
data.isExecuted | boolean | Set to true by the worker after execution |
data.handles | string[] | Active connection handles — see handle types below |
data.schema.input | JSONSchema7? | Expected input schema for this node type |
data.schema.output | JSONSchema7? | Produced output schema for this node type |
data.params | object | Node parameters — each key maps to a param object |
data.params[key].value | any | The parameter value. May contain template expressions |
data.params[key].isExpression | boolean | Set to true if value contains a {{ ... }} expression |
data.params[key].isAttachedToInputNode | boolean | Set by the UI when a param is wired to an input handle |
data.inputs | array | Populated by the worker with runtime inputs after execution |
data.outputs | array | Populated by the worker with runtime outputs after execution |
data.errors | array | Populated by the worker if the node fails: [{ message, code? }] |
position | object | { x, y } canvas coordinates — used by the UI, ignored by the worker |
measured | object? | { width?, height? } canvas rendering hints — ignored by the worker |
isSelected | boolean | UI state — ignored by the worker |
isDragging | boolean | UI state — ignored by the worker |
Before a job is sent to the workers, the platform strips the NodeDataParam wrappers. Workers receive params as a plain Record<string, any> where each key maps directly to its value.
Edge Schema
{
"id": "edge_fetchEntries_to_search",
"source": "fetchEntries",
"sourceHandle": "outputs",
"target": "vectorSearch",
"targetHandle": "inputs"
}
Edge Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier for this edge |
source | string | Node id of the source node |
sourceHandle | string | The handle on the source node — one of inputs, outputs, tools, agents |
target | string | Node id of the target node |
targetHandle | string | The handle on the target node — one of inputs, outputs, tool, agent |
Handle Types
Handles determine both the visual connection points in the UI and the execution semantics in the worker.
| sourceHandle | targetHandle | Meaning |
|---|---|---|
outputs | inputs | Flow edge — data flows from source to target. The worker uses these edges to compute execution order via topological sort |
tools | tool | Tool edge — connects an agent node to a tool node. Does not affect execution order |
agents | agent | Agent edge — connects a deep agent to a sub-agent. Does not affect execution order |
Complete Example
A two-node workflow that fetches entries and runs a vector search:
{
"nodes": [
{
"id": "fetchEntries",
"type": "get_entries",
"data": {
"label": "Fetch Entries",
"isExecuted": false,
"handles": ["outputs"],
"schema": {},
"params": {
"datasetId": {
"value": 5,
"isExpression": false,
"isAttachedToInputNode": false
},
"numberOfEntries": {
"value": 20,
"isExpression": false,
"isAttachedToInputNode": false
}
},
"inputs": [],
"outputs": [],
"errors": []
},
"position": { "x": 0, "y": 0 },
"isSelected": false,
"isDragging": false
},
{
"id": "vectorSearch",
"type": "vector_search",
"data": {
"label": "Vector Search",
"isExecuted": false,
"handles": ["inputs", "outputs"],
"schema": {},
"params": {
"query": {
"value": "{{ $input.query }}",
"isExpression": true,
"isAttachedToInputNode": false
},
"collectionName": {
"value": "{{ @fetchEntries.collection }}",
"isExpression": true,
"isAttachedToInputNode": false
},
"topK": {
"value": 5,
"isExpression": false,
"isAttachedToInputNode": false
}
},
"inputs": [],
"outputs": [],
"errors": []
},
"position": { "x": 350, "y": 0 },
"isSelected": false,
"isDragging": false
}
],
"edges": [
{
"id": "e1",
"source": "fetchEntries",
"sourceHandle": "outputs",
"target": "vectorSearch",
"targetHandle": "inputs"
}
]
}
Group Nodes
A group node encapsulates a nested sub-workflow. Its data.workflow field contains a full nodes and edges structure. At execution time the worker dissolves the group in-place, replacing it with its inner nodes and rewiring all edges transparently. group_input and group_output nodes mark the boundaries of the nested graph.
{
"id": "myGroup",
"type": "group",
"data": {
"label": "My Sub-Workflow",
"isExecuted": false,
"handles": ["inputs", "outputs"],
"schema": {},
"params": {},
"inputs": [],
"outputs": [],
"errors": [],
"workflow": {
"nodes": [
{ "id": "groupIn", "type": "group_input", ... },
{ "id": "innerNode", "type": "chat_completion", ... },
{ "id": "groupOut", "type": "group_output", ... }
],
"edges": [
{ "id": "e1", "source": "groupIn", "sourceHandle": "outputs", "target": "innerNode", "targetHandle": "inputs" },
{ "id": "e2", "source": "innerNode", "sourceHandle": "outputs", "target": "groupOut", "targetHandle": "inputs" }
]
}
},
"position": { "x": 0, "y": 0 },
"isSelected": false,
"isDragging": false
}