Skip to content
Talk to our solutions team

Tasks and functions

An atom is written once and reached two ways. This page is the translation table between them — read it once and every reference entry in this section becomes predictable.

Each atom is a self-contained unit: it takes parameters, does something, returns a result. It knows nothing about flows or scripts.

Two thin adapters sit on top of it:

  • The task adapter makes it a node a flow can schedule. It reads parameters out of the flow’s YAML, runs the atom, and hands the result back to the run so later steps can use it.
  • The function adapter makes it a callable inside a script. It reads parameters out of the argument you pass, runs the atom, and returns the result to your code.
shell atom
(parameters, work, result)
▲ ▲
task adapter function adapter
│ │
shell: in a flow ─┘ └─ shell.execute() in a script

The practical consequence: you cannot get a behaviour difference between the two surfaces, because there is only one behaviour. If archive handles a .tar.zst in a flow, it handles it in a script. If a database query returns rows in one shape here, it returns that shape there. When the reference documents a default, that default is the atom’s, and it holds on both sides.

What does differ is the calling convention — the spelling, the shape of the arguments, and how you get the result back out. That is the rest of this page.

FlowScript
Reached asa task keya namespace
Spelledshell:shell.execute()
Granularityone key, mode chosen by parametersone function per verb

A flow task is a single key, and where an atom does several things the mode is a parameter. A namespace splits the same atom into named functions:

tasks:
- name: fetch-release
s3:
command: download
bucketname: releases
s3url: "{{minio.url}}"
credentials:
accesskey: "{{minio.username}}"
secretkey: "{{minio.password}}"
secure: false
commandparams:
object: build.tar.gz
path: ./build.tar.gz

The verb moves from a parameter (command: download) into the function name (s3.download). That is the single most common difference you will meet, and once you expect it, most translations are mechanical.

Look at those two tabs again. They call the same atom with the same values, and almost none of the keys match:

FlowScript
Bucketbucketnamebucket
Servers3urlendpoint
Credentialsnested under credentials:flat, alongside the rest
Operandsnested under commandparams:flat, alongside the rest

Do not assume a parameter name carries across. The two adapters were written for two very different calling conventions — a flow’s parameters are a YAML tree an operator reads, a script’s are one object you build in code — and they are named for their own surface.

Two patterns are worth keeping in your head, because they cover most of it:

  • Case. Where a name is shared, a flow spells it lowercase and a script spells it camelCase: workingdir / workingDir, inheritenv / inheritEnv. YAML keys are matched case-sensitively, so workingDir: in a flow is not the parameter you meant — it is an unrecognised key, and the operation quietly uses its default. If a flow parameter appears to do nothing, check its case first.
  • Nesting. Flows group credentials and operands into sub-blocks; scripts pass one flat object.

Every reference entry lists both surfaces in full, so you never have to translate from memory.

This is where the two surfaces genuinely diverge, because a flow and a script hold state differently.

A flow has a shared variable space for the whole run. An atom writes into it with setvar, and later steps read it back with {{ }} templating:

name: release
list: true
tasks:
- name: get-version
shell:
script: git describe --tags
setvar: version
- name: announce
print:
message: "Releasing {{version}}"

The value outlives the step. Anything after it in the run can read {{version}}.

So: setvar in a flow, a return value in a script. A flow’s variables are the run’s memory; a script’s variables are the function’s.

Namespace functions return a map with a consistent envelope:

FieldMeaning
successWhether the atom completed
errorThe message when it did not; empty when it did
(operation fields)Whatever this atom produces — output, rows, path, …

Check success rather than assuming; a failed atom returns a value, it does not throw:

function main() {
const r = shell.execute({ script: './deploy.sh', capture: true });
if (!r.success) {
return { ok: false, reason: r.error };
}
return { ok: true, log: r.output };
}

In a flow the equivalent check is structural rather than written by hand — a failed task fails the node, and where the run goes next is decided by the flow’s error routing. See Error handling.

Every entry in the atom reference follows one shape:

  1. What it does — one paragraph.
  2. Parameters — a table with both spellings, types, defaults and whether each is required.
  3. Both surfaces — a Flow tab and a Script tab, doing the same thing.
  4. Result — the fields you get back.
  5. Notes — anything that will bite you.

Pick the tab that matches what you are writing. Your choice is remembered across the whole site, so if you work in scripts you see script examples everywhere until you switch.

Most operations have both forms, but not all, and a few have a narrower function form than task form.

  • Some atoms exist only as tasks, because they mean nothing outside a run — suspend pauses a flow until it is resumed, throw fails a node deliberately, print writes into the run log. There is nothing for a script to call; a script already has return, throw and log.info.
  • Some exist as both, but the namespace covers less than the task. wait is the clearest case: as a task it polls several kinds of condition, as a namespace it offers wait.http only.
  • Some capabilities exist only as namespaces, because they are language services rather than units of work — log, json, string, math, array, time, and the authorization namespaces a script uses to ask who the caller is.

The reference marks all three cases. Where a tab would be empty, it says so and says what to use instead.