The workflow file
A workflow's JSON definition: nodes, edges, inputs, objects, and the optional canvas block.
A workflow is a JSON file at workflows/<id>.json. The id matches the file name without .json. You'll usually edit workflows through the visual Workflow Editor; this page documents the file it writes.
Minimal example
A fresh workflow from zeon new workflow my_workflow:
{
"workflow_id": "my_workflow",
"name": "my_workflow",
"description": "",
"version": "1.0.0",
"author": "",
"created_at": "2026-05-24T12:00:00.000Z",
"updated_at": "2026-05-24T12:00:00.000Z",
"simulation_validated": false,
"objects": [],
"inputs": [],
"nodes": [
{ "node_id": "start", "type": "start", "label": "Start" },
{ "node_id": "end", "type": "end", "label": "End" }
],
"edges": [
{
"edge_id": "e0",
"from_node": "start",
"to_node": "end",
"condition": { "type": "default" }
}
]
}The two timestamps are stamped by the CLI on your machine when the file is scaffolded, not by a server.
Top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
workflow_id | string | yes | Matches the file name. Pattern ^[a-z0-9_]+$. |
name | string | yes | Display name. May differ from workflow_id, and usually reads as prose, like "Plate Prep v2" rather than plate_prep_v2. |
description | string | no | Free text shown in the workflow picker. |
version | string | yes | Semantic version, X.Y.Z. |
author | string | yes | Free-text author. The scaffold writes an empty string; the gateway substitutes user when the value is missing or empty. |
created_at | string (RFC 3339 ms) | yes | Stamped by the scaffold at creation. |
updated_at | string (RFC 3339 ms) | yes | Rewritten on every save. |
simulation_validated | boolean | yes | Set to true by the executor after a simulation run succeeds. false on new workflows. |
simulation_result | object | no | Result of the last simulation validation. Absent until one has run. |
last_simulation_timestamp | string | no | When that validation ran. Absent until then. |
objects | list | yes | Object declarations (see below). |
inputs | list | no | Per-run parameters (see below). |
nodes | list | yes | The graph's nodes. |
edges | list | yes | The connections between them. |
canvas_ui | object | no | Custom React canvas configuration. Present only when a canvas is attached. |
inputs
inputsEach input is a value supplied before the run starts. A single-input example, from a real workflow:
"inputs": [
{
"name": "reaction_plate",
"type": "object",
"is_array": false,
"description": "PCR Wellplate"
}
]| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Identifier. Referenced from node parameters as {"$input": "<name>"}. |
type | string | yes | string, int, float, object, or structured. |
is_array | boolean | no | true if the input is a list. Omitted entirely on many real inputs; treat absent as false. |
description | string | no | Shown next to the field when the operator sets up a run. |
defaultValue | any | no | Pre-filled value. Note the camelCase. This key really is spelled differently from is_array. |
itemSchema | object | no | Per-item field schema, for arrays of structured values. |
The five types appear in the editor's dropdown as String, Integer, Float, World Object, and Structured Data.
There is no
boolinput type. Some existing workflow files carry"type": "boolean", which is outside the declared set. The editor will not offer it, and a boolean-ish input is normally modelled as astringorint.
objects
objectsA list of object declarations the workflow's nodes can reference by alias:
"objects": [
{
"alias": "reaction_plate",
"mesh_type": "wellplate_pcr",
"display_name": "PCR Wellplate"
}
]| Field | Type | Description |
|---|---|---|
alias | string | The name node parameters use to refer to this object. |
mesh_type | string | The object type from the mesh database. |
display_name | string | Human-readable label used when mapping the alias to a real object. |
At execution time each alias is bound to a concrete object in the loaded world. This mechanism predates workflow inputs and is kept for compatibility. New workflows generally declare an object-typed input instead, and leave objects empty. Both work.
nodes
nodesFive node types exist: start, end, skill, loop, and conditional. Every workflow has one start and at least one end.
In practice, most workflows use only start, end, and skill, a linear chain of skill nodes.
The file format and the executor use different names for the same edge conditions. The editor translates between them in both directions as it loads and saves: default becomes unconditional, loop_continue becomes loop_body, and loop_complete becomes loop_exit. (The gateway applies the same mapping one way, when importing older files.) You write the file-format names documented below; the translation is not something you manage.
A skill node:
{
"node_id": "pick_pipette_1",
"type": "skill",
"label": "Pick Epipette",
"skill_id": "epipette_grey_pick",
"parameters": {}
}| Field | Type | Required | Description |
|---|---|---|---|
node_id | string | yes | Unique within the workflow. Pattern ^[a-z0-9_]+$. |
type | string | yes | The node type. Note the key is type, not node_type. |
label | string | yes | Shown on the canvas. |
skill_id | string | for skill | Which skill to run. Must name a skill in skills/. |
parameters | map | for skill | Values passed to the skill. Required on skill nodes; write {} when there are none. |
description | string | no | Per-node note. |
retry | integer | no | Retry attempts for this node on failure. |
edges
edges{
"edge_id": "e0",
"from_node": "start_0",
"to_node": "pick_pipette_1",
"condition": { "type": "default" }
}| Field | Type | Required | Description |
|---|---|---|---|
edge_id | string | yes | Unique within the workflow. Real files use e0, e1, e2. |
from_node | string | yes | A node_id in the workflow. |
to_node | string | yes | A node_id in the workflow. |
condition | object | yes | An object, not a bare string: { "type": "<condition-type>" }. |
Condition types:
| Type | Meaning |
|---|---|
default | Unconditional. What the scaffold puts on the start edge. |
on_success | Follow when the source node succeeded. What real workflows use between skill nodes. |
on_failure | Follow when the source node failed. |
if_true / if_false | The two branches out of a conditional node. |
loop_continue / loop_complete | The two exits from a loop node. |
The graph is structurally validated before it runs. A workflow with a node connected to no edges, an edge naming a node that doesn't exist, or a conditional node without exactly two outgoing edges is rejected.
Parameter references
A node's parameter can be:
- A literal:
"volume": 5,"well": "A1". Arrays and nested objects are also fine. - A workflow input reference:
{ "$input": "reaction_plate" }, resolved at run time to whatever was supplied for that input. For object-typed inputs it may also carry anoffset:{ "$input": "plate", "offset": { "xyz": [0, 0, 0.01], "wxyz": [1, 0, 0, 0] } }. - An object reference:
{ "alias": "reaction_plate", "offset": { "xyz": [0, 0, 0.01], "wxyz": [1, 0, 0, 0] } }, naming an entry in the workflow'sobjectslist.
In both reference forms the offset lets a skill target a pose relative to the object rather than its origin (10 mm above a plate, say). xyz is in metres; wxyz is a quaternion, scalar first.
canvas_ui
canvas_uiPresent when a canvas is attached to the workflow:
"canvas_ui": {
"kind": "react",
"source_ref": "canvas/my_workflow_screen.tsx",
"enabled": true,
"version": 1,
"updated_at": "2026-05-24T12:00:00.000Z"
}| Field | Type | Description |
|---|---|---|
kind | string | "react", the only kind today. |
source_ref | string | Path to the TSX file. Must match canvas/<name>.tsx, where <name> is lowercase letters, digits, and underscores; the gateway rejects anything else. Because it is source_ref and not the filename that binds the canvas, <name> need not match the workflow_id; the tools default it to <workflow_id>_screen. |
enabled | boolean | false falls back to the standard input panel even though a canvas exists. |
version | integer | A save counter, not a format version. Starts at 1 and increments on every canvas save; it cache-busts the compiled component. |
updated_at | string (ISO 8601) | When the canvas source was last edited. The tools write millisecond-precision UTC (…Z), but the format is not enforced, and one real workflow carries 2026-05-16T02:56:07+00:00. |
See Creating a Canvas for how to add one.
See also
Updated 2 days ago