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

FieldTypeRequiredDescription
workflow_idstringyesMatches the file name. Pattern ^[a-z0-9_]+$.
namestringyesDisplay name. May differ from workflow_id, and usually reads as prose, like "Plate Prep v2" rather than plate_prep_v2.
descriptionstringnoFree text shown in the workflow picker.
versionstringyesSemantic version, X.Y.Z.
authorstringyesFree-text author. The scaffold writes an empty string; the gateway substitutes user when the value is missing or empty.
created_atstring (RFC 3339 ms)yesStamped by the scaffold at creation.
updated_atstring (RFC 3339 ms)yesRewritten on every save.
simulation_validatedbooleanyesSet to true by the executor after a simulation run succeeds. false on new workflows.
simulation_resultobjectnoResult of the last simulation validation. Absent until one has run.
last_simulation_timestampstringnoWhen that validation ran. Absent until then.
objectslistyesObject declarations (see below).
inputslistnoPer-run parameters (see below).
nodeslistyesThe graph's nodes.
edgeslistyesThe connections between them.
canvas_uiobjectnoCustom React canvas configuration. Present only when a canvas is attached.

inputs

Each 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"
  }
]
FieldTypeRequiredDescription
namestringyesIdentifier. Referenced from node parameters as {"$input": "<name>"}.
typestringyesstring, int, float, object, or structured.
is_arraybooleannotrue if the input is a list. Omitted entirely on many real inputs; treat absent as false.
descriptionstringnoShown next to the field when the operator sets up a run.
defaultValueanynoPre-filled value. Note the camelCase. This key really is spelled differently from is_array.
itemSchemaobjectnoPer-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 bool input 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 a string or int.

objects

A list of object declarations the workflow's nodes can reference by alias:

"objects": [
  {
    "alias": "reaction_plate",
    "mesh_type": "wellplate_pcr",
    "display_name": "PCR Wellplate"
  }
]
FieldTypeDescription
aliasstringThe name node parameters use to refer to this object.
mesh_typestringThe object type from the mesh database.
display_namestringHuman-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

Five 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": {}
}
FieldTypeRequiredDescription
node_idstringyesUnique within the workflow. Pattern ^[a-z0-9_]+$.
typestringyesThe node type. Note the key is type, not node_type.
labelstringyesShown on the canvas.
skill_idstringfor skillWhich skill to run. Must name a skill in skills/.
parametersmapfor skillValues passed to the skill. Required on skill nodes; write {} when there are none.
descriptionstringnoPer-node note.
retryintegernoRetry attempts for this node on failure.

edges

{
  "edge_id": "e0",
  "from_node": "start_0",
  "to_node": "pick_pipette_1",
  "condition": { "type": "default" }
}
FieldTypeRequiredDescription
edge_idstringyesUnique within the workflow. Real files use e0, e1, e2.
from_nodestringyesA node_id in the workflow.
to_nodestringyesA node_id in the workflow.
conditionobjectyesAn object, not a bare string: { "type": "<condition-type>" }.

Condition types:

TypeMeaning
defaultUnconditional. What the scaffold puts on the start edge.
on_successFollow when the source node succeeded. What real workflows use between skill nodes.
on_failureFollow when the source node failed.
if_true / if_falseThe two branches out of a conditional node.
loop_continue / loop_completeThe 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 an offset: { "$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's objects list.

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

Present 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"
}
FieldTypeDescription
kindstring"react", the only kind today.
source_refstringPath 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.
enabledbooleanfalse falls back to the standard input panel even though a canvas exists.
versionintegerA save counter, not a format version. Starts at 1 and increments on every canvas save; it cache-busts the compiled component.
updated_atstring (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


Did this page help you?