The flow file
A flow file is YAML. One key is required — tasks: — and everything else exists to feed it.
name: build-and-shiplist: truevars: registry: registry.internaltasks: - name: build shell: docker build -t {{registry}}/app:latest . - name: push shell: docker push {{registry}}/app:latestNote list: true on the second line. It is not decoration — read the next section before writing
anything with more than one node.
Graphs and sequences
Section titled “Graphs and sequences”A flow runs in one of two modes, and the difference decides whether your second node runs at all.
By default a flow is a graph. Nodes are connected by their next: keys and by nothing else. A
node without next: is terminal:
name: three-stepstasks: - name: one shell: echo one - name: two shell: echo two - name: three shell: echo threeoneexecution engine instance Status: completed Completed Nodes: [one]The run stops after one and reports completed. Writing nodes underneath one another looks
like a sequence; in graph mode it is three unconnected terminal nodes, and the engine runs the
first.
Two ways to get all three:
# Explicit edges — required as soon as there is any branchingtasks: - name: one shell: echo one next: go: two - name: two shell: echo two next: go: three - name: three shell: echo three# A declared sequence — no routing to writelist: truetasks: - name: one shell: echo one - name: two shell: echo two - name: three shell: echo threeUse list: true when the flow is a straight line. It says what you mean, it cannot be got
wrong by omission, and it removes the routing boilerplate. Use graph mode the moment you need to
branch, fan out, retry to a different node, or handle an error somewhere other than the end.
The two modes also fail differently:
| Graph (default) | list: true | |
|---|---|---|
| Order | Whatever next: says | Top to bottom |
A node with no next: | Ends the run | Continues to the next |
| On failure | Follows error:, or stops | Stops |
continueonerror default | true | false |
In either mode a run that hit a failure is reported as errored even if a recovery branch
succeeded afterwards. See Execution.
Top-level keys
Section titled “Top-level keys”| Key | Type | Purpose |
|---|---|---|
name | string | The flow’s name. Used to select it with -n when a file holds several |
tasks | list | The nodes. See Node types |
vars | map | Variables available to every node as {{name}} |
tables | map | Named row sets, for data-driven runs |
table | string | Which table to iterate — runs the whole flow once per row |
imports | map | Short aliases for file URIs |
flows | list | Several flows in one file |
states | list | Nodes of a state machine, instead of tasks: |
starttask | string | Which node runs first. Defaults to the first in the list |
affinity | map | Pin the run to one worker. See Execution |
continueonerror | bool | Whether a failed node stops the run |
list | bool | Run the nodes top to bottom instead of following next:. See above |
pipeline | bool | Run as a streaming data pipeline |
Variables
Section titled “Variables”vars: is the flow’s starting state. Values can be scalars, lists or nested maps:
vars: environment: staging replicas: 3 registry: host: registry.internal namespace: platformRead them with {{ }}, using dots for nesting:
tasks: - name: deploy shell: | helm upgrade app ./chart \ --set image.repository={{registry.host}}/{{registry.namespace}}/app \ --set replicaCount={{replicas}} \ --namespace {{environment}}Setting them at run time
Section titled “Setting them at run time”kis flow -f deploy.yaml -v environment=production -v replicas=6kis flow -f deploy.yaml -e production.yaml-v sets one variable and repeats. -e loads a YAML file of them, which is the usual way to keep
per-environment values out of the flow.
Setting them from a node
Section titled “Setting them from a node”Two mechanisms, and the difference matters.
setvar: captures what an atom produced:
list: truetasks: - name: version shell: script: git describe --tags setvar: release_tag
- name: tag-image shell: docker tag app:latest app:{{release_tag}}assign: sets values directly, without running an atom:
tasks: - name: defaults assign: set: region: us-west-2 retries: 3 next: go: deployUse setvar: for the result of work, assign: for constants and reshaping. An assign: node
never reaches a worker, so it costs nothing.
Variables the engine sets
Section titled “Variables the engine sets”Some names are populated for you. They all begin with an underscore.
| Variable | Set by |
|---|---|
_item | The current item, inside foreach: or map: |
_index | The current item’s position, from zero |
_results | Every result, once a foreach: or map: finishes |
_result | The last node’s result |
_error, _cause | A fail: node’s error type and message |
_signal | The data a wait: node received |
_wait_timeout | true when a wait: node timed out |
Tables
Section titled “Tables”A table is a named set of rows. Declare one inline as pipe-separated text, with the first line as the header:
tables: servers: | host|role web-1|frontend db-1|databaseOr load one from a file:
tables: servers: type: csv file: ./data/servers.csv separator: "," inventory: type: excel file: ./data/inventory.xlsx sheet: Sheet1Running once per row
Section titled “Running once per row”Naming a table with the top-level table: key turns the whole flow into a data-driven run: it
executes once for every row, with that row’s columns available as variables.
name: check-serversvars: greeting: checkingtables: servers: | host|role web-1|frontend db-1|databasetable: serverstasks: - name: show shell: "echo {{greeting}} {{host}} is {{role}}"checking web-1 is frontendchecking db-1 is databaseEach row gets a complete, independent run — its own variables, its own record of which nodes
completed. That is the difference between this and a map: node: map: fans one node out over a
collection, table: fans the whole flow out over rows.
Variables from vars: are merged into every row, so {{greeting}} above is available alongside
the row’s own columns. A column with the same name as a variable wins for that row.
Imports
Section titled “Imports”imports: binds short names to file URIs, so a path used in several places is written once:
imports: selector: ./scripts/select-candidates.js hosts: ./data/production-hosts.csv
tables: servers: type: csv file: hosts
tasks: - name: pick script: language: javascript file: selector setvar: candidatesAliases work anywhere a file URI is accepted — a script’s file:, a table’s file:, a subflow
reference. Paths are resolved against the product root when the flow sits inside one, and against
the flow file’s own directory otherwise.
Several flows in one file
Section titled “Several flows in one file”flows: holds a list, each with its own name: and tasks:. Select one with -n:
flows: - name: deploy-service tasks: - name: run shell: "./deploy.sh {{service_name}}" - name: done succeed: true
- name: deploy-all tasks: - name: each-service foreach: items_path: services task: run-one next: go: finished - name: run-one subworkflow: definition_id: deploy-service input_mapping: _item: service_name - name: finished succeed: truekis flow -f deploy.yaml -n deploy-allThis is how shared logic is reused: one flow becomes a subflow of another, with input_mapping:
deciding what it receives. See Node types.
Templating
Section titled “Templating”{{ }} is Liquid, so it does more than substitution:
tasks: - name: report shell: | echo "Environment: {{environment | upcase}}" echo "Services: {{services | size}}" {% for svc in services %} echo " - {{svc}}" {% endfor %}Templating is applied to parameter values before a node runs, so any atom’s parameters can
carry it — not just shell:.
Two things to watch:
- Quote a value that starts with
{{. YAML reads a leading{as the start of a flow mapping, soshell: {{cmd}}is a parse error whileshell: "{{cmd}}"is what you meant. - Parameter keys are matched case-sensitively, and flows spell them lowercase.
workingdir:is the parameter;workingDir:is an unrecognised key and the atom quietly uses its default.
See also
Section titled “See also”- Node types — every kind of node
- Execution — planning, workers, retries, failure
- Atom reference — what a task node can run