An algorithm on its own does nothing until a workflow runs it. A workflow is a graph: an Input node, your steps and an Output node, connected port to port. Connections are the only edges, so the run order follows from what feeds what, and you never manage it by hand.
This page covers building a workflow in the editor, the file behind it, and the rules that decide how each step runs. Behind the canvas, every workflow is a YAML file in the embodiflow-workflow/v1 format, and you can edit either view.
Build it in the editor
Open Workflows, then the project workflow, or start with New curate workflow or New export workflow. The Input node sits on the left and the Output node on the right. The palette lists built-in and team algorithms together, grouped by the seven categories, and you can search it.
There are several ways to add and connect a step, so use whichever suits the moment:
| Way to connect | How |
|---|---|
| Drop a card | Drop an algorithm on the canvas. It connects its main input to the nearest compatible output upstream. The editor tells you what it connected, and you can undo it. |
| Drag a wire | Drag from a port into empty space. A search lists the algorithms that fit that port. |
| Click the plus on a port | Choose Add a step after this output or Add a step before this input. |
| Use the side panel | Select a step, open Connections, choose Add a connection, then Choose an output. Outputs that don't fit aren't offered. |
| Drop on a connection | Drop an algorithm on a connection, or choose Insert a step here, to put it between the two ends. |
Ports are coloured by data type. While you drag, only fitting ports light up, and a refusal says why, for example that it would make a loop. Hover a connection to see what it carries. The inspector edits parameters, the when conditions and onFail. The YAML view edits the same file, Visual switches back, and Tidy now arranges the canvas.
Problems, saving and publishing
The problems panel lists every problem with its code. A draft that still has problems is saved anyway, so you never lose work, but it can't be published, bound to a project or run until they're fixed; the editor says so next to Publish. A file that can't be read at all is refused.
Test on a sample
Test on sample runs the draft on up to three recordings of the project. Window in seconds is optional and runs only the start of each recording, up to 600 seconds. A test run never saves a result: the steps that save the recording and evaluate its quality are skipped, shown grey as "Skipped: a test run on a window saves no result.", and never green. You can still read every step's output in the run.
The standardizer and raw checks always read the whole upload, even with a window, because raw files have no time axis.
A complete workflow
Here is the built-in human-ego-standard workflow exactly as stored. It's a good model to read before you write your own.
apiVersion: embodiflow-workflow/v1
name: Human egocentric standard
description: Pre-check, standardize, calibrate and anonymize; then head pose and depth from the calibration, MANO hands made metric by stereo hand keypoints, the SMPL-H body fitted to the hands, a ROS 2 3D view, visual quality, episode proposals and a success label per episode.
type: processing
params:
task: { type: string, default: "", title: Task text }
steps:
- id: pre_check
uses: embodiflow/raw-metrics@1
inputs: { raw: input.raw }
onFail: stop
- id: standardize
uses: auto
inputs: { raw: input.raw }
with: { task: "${{ params.task }}" }
- id: calibrate
uses: embodiflow/sensor-calibrate@1
inputs: { recording: standardize.recording }
- id: anonymize
uses: embodiflow/anonymize@1
inputs: { recording: standardize.recording }
- id: hand_pose
uses: embodiflow/hand-pose@1
inputs: { recording: anonymize.recording, calibration: calibrate.features }
with: { frame_stride: 6 }
- id: head_pose
uses: embodiflow/camera-pose-vio@1
inputs: { recording: anonymize.recording, calibration: calibrate.features }
- id: depth
uses: embodiflow/stereo-depth@1
inputs: { recording: anonymize.recording, calibration: calibrate.features }
- id: hands
uses: embodiflow/hand-mano@1
inputs: { recording: anonymize.recording, head_pose: head_pose.features, stereo_hands: hand_pose.features, depth: depth.features }
- id: body
uses: embodiflow/body-pose@1
inputs: { recording: anonymize.recording, head_pose: head_pose.features, hands: hands.features, depth: depth.features }
- id: view3d
uses: embodiflow/ego-vis@1
inputs: { recording: anonymize.recording, head_pose: head_pose.features, depth: depth.features, hands: hands.features, body: body.features }
- id: visual
uses: embodiflow/visual-metrics@1
inputs: { recording: anonymize.recording }
- id: propose
uses: embodiflow/propose-episodes@1
inputs: { recording: anonymize.recording }
- id: success
uses: embodiflow/episode-success@1
inputs: { recording: anonymize.recording, episodes: propose.episodes }
output:
recording: anonymize.recording
features: [calibrate.features, head_pose.features, depth.features, hands.features, body.features, view3d.features, success.features]
checks: [pre_check.checks, anonymize.checks, calibrate.checks, hand_pose.checks, head_pose.checks, depth.checks, hands.checks, body.checks, view3d.checks, visual.checks]
episodes: propose.episodes| Field | Rule |
|---|---|
type | processing, curate or export. It fixes what the Input node gives and what the Output node takes. |
params | Run parameters: { name: { type: string, number or boolean, default, title } }. A step uses one as a whole with value. |
steps[].id | Lowercase letters, digits and _, up to 32 long, unique, and not input, output or params. |
steps[].uses | embodiflow/<name>@<major>, team/<name>@<major>, or auto for a standardize step. |
steps[].inputs | Every required input connected: <port>: <stepId>.<port> or input.<port>. |
steps[].with | Parameter values, checked against the algorithm's parameters. |
steps[].when | Optional conditions. |
steps[].onFail | Check steps only: continue (default) or stop. |
steps[].enabled | Default true. A disabled step is kept and ignored. |
output | What the Output node receives. |
There is no needs, if, fallback list or locked step. Node positions live in a separate layout document, so moving boxes around never changes the workflow.
Built-in algorithms you can use
A step refers to a built-in algorithm by its reference. The name is its title in the console.
| Reference | Name | Category | Level |
|---|---|---|---|
embodiflow/anonymize@1 | Anonymize | transform | recording |
embodiflow/body-pose@1 | Body pose (SMPL-H) | enrich | recording |
embodiflow/camera-pose-vio@1 | Head pose (visual-inertial) | enrich | recording |
embodiflow/canonicalize@1 | Canonicalize MCAP | standardize | recording |
embodiflow/canonicalize-video@1 | Canonicalize video | standardize | recording |
embodiflow/ego-vis@1 | Ego 3D visualization | enrich | recording |
embodiflow/episode-success@1 | Episode success | enrich | episode |
embodiflow/filter-episodes@1 | Filter episodes | curate | dataset version |
embodiflow/hand-mano@1 | Hand pose (MANO) | enrich | recording |
embodiflow/hand-pose@1 | Hand pose | enrich | recording |
embodiflow/lerobot-export@1 | LeRobot v3 export | export | dataset version |
embodiflow/lerobot-preview@1 | Video preview | enrich | recording |
embodiflow/propose-episodes@1 | Propose episodes | segment | recording |
embodiflow/raw-metrics@1 | Raw-file metrics | check | recording |
embodiflow/recording-episodes@1 | Recording episodes | segment | recording |
embodiflow/sensor-calibrate@1 | Sensor calibration | enrich | recording |
embodiflow/split-episodes@1 | Split episodes | curate | dataset version |
embodiflow/stereo-depth@1 | Stereo depth | enrich | recording |
embodiflow/visual-metrics@1 | Visual metrics | check | recording |
Input and Output nodes
What the two ends do depends on the workflow type:
| Workflow type | Input gives | Output takes | What Output does |
|---|---|---|---|
processing | raw | recording (required), features[], checks[], episodes | Merges features into the recording, stores checks and evaluates the quality rules, creates episodes from episodes (otherwise one per recording) without touching episodes that people made, and writes privacy evidence after an anonymizing transform. |
curate | version | selection[], at least one | Combines the selections and creates the new dataset version, linked to the source version and the run. |
export | version | artifact (required) | Stores the files and offers the download. |
The Output node runs as steps you don't place but can see in the run view: Merge features (added only when features are connected), then saving the result, evaluating quality per recording, and reporting once per run. A recording is saved exactly when the saving step succeeded. Reading the upload and its facts happens before the Input node. Two steps that write a feature with the same name are refused with COMPOSE_FEATURE_CONFLICT.
Connecting steps
The editor, saving and running all apply the same rules, so a workflow that looks fine in the editor won't surprise you at run time. Every refusal says why.
| Rule | Error |
|---|---|
| The data type is equal. | WORKFLOW_PORT_INCOMPATIBLE |
The content fits: every requires entry of the input is declared by the producer. | WORKFLOW_CONTENT_MISSING |
| There are no cycles. | WORKFLOW_CYCLE |
| Every required input is connected. An output may feed any number of inputs. | WORKFLOW_INPUT_UNWIRED |
Only the Output node's features, checks and selection take several connections. Every step input takes exactly one. | Refused, with the reason |
A processing workflow's output.recording traces back to exactly one standardize step. | WORKFLOW_NO_STANDARDIZE |
An episode-level step's episodes input and output.episodes come from the same producer. | WORKFLOW_EPISODES_MISMATCH |
Content is checked before any run, not just the type. A features output carries the features its algorithm declares; a recording output carries its producer's content plus every transform's declared change; an episodes output carries the declared labels. In the example above, hand_pose needs calibration.stereo and calibrate declares it, so that connection is allowed.
Conditions
when makes a step conditional. It's a list of conditions, and all of them must hold:
| Condition | Example |
|---|---|
| A fact | { fact: duration_s, op: ">", value: 60 } |
| A run parameter | { param: run_depth, op: "==", value: true } |
| A check result | { check: pre_check.frame_gap_ratio, status: [pass, warn] }. The step waits for that check. |
op is one of ==, !=, >, >=, <, <= and in. When a condition is false, the step ends as skipped (condition false), which counts like not applicable further down. That's different from an algorithm's own requires, which ends the step as not applicable when the data lacks what it needs.
When a step doesn't succeed
There is one rule behind all of this: a step runs when every required input has a value. Everything else follows from it.
- A step that fails or is not applicable produces nothing.
- If a required input is missing because its producer was not applicable, the step is not applicable too.
- If a required input is missing because its producer failed, the step is skipped with "upstream failed", naming the producer.
- An optional input whose producer didn't succeed is simply absent.
- In a processing workflow,
recordingis required at the Output node, whilefeatures,checksandepisodesare optional. So a failed hand-pose step still leaves the recording saved, just without hand pose: the recording succeeds, the run is partially succeeded and the failed step stays red.
Stopping checks
Set onFail: stop on a check step to make it a gate. When any result of that check is fail, the recording stops: steps that haven't started end as skipped (stopped by check), and nothing is saved (WORKFLOW_STOPPED_BY_CHECK). warn, error, not_evaluated and not_applicable never stop a recording, and they never count as a pass either.
- id: pre_check
uses: embodiflow/raw-metrics@1
inputs: { raw: input.raw }
onFail: stopEpisode-level steps
A check or enrich step with level: episode runs once per episode of its episodes input, in parallel: at most 4 at a time and at most 100 episodes in all (WORKFLOW_FAN_OUT_TOO_LARGE above that).
One failed episode doesn't fail the others. The step succeeds when at least one episode succeeded, fails only when every episode failed, and keeps the count "N of M episodes failed"; the run is partially succeeded and lists the failed episodes. A step with failed episodes isn't cached, so a rerun tries those episodes again. With no episodes, the step is skipped, which counts like not applicable, so the Output node still saves.
An episode-level stopping check never stops the whole recording; it only drops the failing episode from what is saved. The checks of an episode-level step can't be connected to the Output node's checks, but its features still reach the saved recording through the merge. In the example, success takes propose.episodes, and the same episodes are the workflow's output.
Privacy
In a processing workflow, every path from a standardize step to anything that reads a recording, and to output.recording, passes through the built-in anonymize step. Checks and built-in algorithms whose outputs are numbers only are the exceptions. A path that skips it is refused with WORKFLOW_PRIVACY_BARRIER. When a recording doesn't require privacy protection, anonymize hands it through unchanged, so one workflow serves both cases.
Team code that reads raw files, or a recording that isn't anonymized yet, only runs for people who may read raw recordings; anyone else is refused with WORKFLOW_RAW_CODE_AUTHORITY_DENIED.
Curate and export workflows
A curate workflow starts from a dataset version and ends with a new one. The selection ports of its steps all connect to the Output node, and an episode is kept only if every selection keeps it.
apiVersion: embodiflow-workflow/v1
name: Curate standard
description: Drop episodes by quality and duration, then split the rest into train and validation by session.
type: curate
steps:
- id: filter
uses: embodiflow/filter-episodes@1
inputs: { version: input.version }
- id: split
uses: embodiflow/split-episodes@1
inputs: { version: input.version }
output:
selection: [filter.selection, split.selection]An export workflow starts from a dataset version and ends with files.
apiVersion: embodiflow-workflow/v1
name: Export LeRobot v3
description: Export the dataset version as a LeRobot v3 dataset.
type: export
steps:
- id: export
uses: embodiflow/lerobot-export@1
inputs: { version: input.version }
output:
artifact: export.artifactRun them on a version
On a dataset version page, Curate and export lists the project's curate and export workflows. Choose Run to start the curate workflow on that version. The run reports how many episodes it kept and dropped and links to the new version, or tells you no new version was needed. Export workflows aren't started there; use the version's export action instead.
- A curate run creates a new, immutable dataset version. The source version is never changed, and the new version records its source and the run.
- Starting or rerunning a curate run by hand needs permission to run workflows and to write data in the project. An export workflow needs permission to run workflows and to write exports.
- A project binds one curate workflow. Turn on Run when a version is created and every new version starts the curate workflow, then the export workflow. A version that a curate run created doesn't start the same curate workflow again, so there's no loop, and a workflow that can't start never blocks the other.
- A workflow with problems, or an unpublished revision, can't be bound or run.
Built-in templates
Templates are read-only. To change one, copy it with Duplicate to edit.
| Template | Type | Steps |
|---|---|---|
robot-standard (Robot standard) | processing | Raw-file metrics that can stop the run, standardize, Anonymize, Visual metrics, and Recording episodes, which saves the recording's own episodes by a rule (no model): none for a continuous recording, a delivery's bound episodes, else the whole recording. |
human-ego-standard (Human egocentric standard) | processing | Raw-file metrics that can stop the run, standardize, Sensor calibration and Anonymize, then Hand pose, Head pose (visual-inertial) and Stereo depth, Visual metrics, Propose episodes and Episode success per episode. |
curate-standard (Curate standard) | curate | Filter episodes and Split episodes. |
export-lerobot-v3 (Export LeRobot v3) | export | LeRobot v3 export. |
Robot standard
apiVersion: embodiflow-workflow/v1
name: Robot standard
description: Pre-check the upload, standardize it, anonymize when the recording requires privacy, measure visual quality, and save the recording's own episodes.
type: processing
params:
task: { type: string, default: "", title: Task text }
steps:
- id: pre_check
uses: embodiflow/raw-metrics@1
inputs: { raw: input.raw }
onFail: stop
- id: standardize
uses: auto
inputs: { raw: input.raw }
with: { task: "${{ params.task }}" }
- id: anonymize
uses: embodiflow/anonymize@1
inputs: { recording: standardize.recording }
- id: visual
uses: embodiflow/visual-metrics@1
inputs: { recording: anonymize.recording }
- id: recording_episodes
uses: embodiflow/recording-episodes@1
inputs: { recording: anonymize.recording }
with: { task: "${{ params.task }}" }
output:
recording: anonymize.recording
checks: [pre_check.checks, anonymize.checks, visual.checks]
episodes: recording_episodes.episodes