Extend Flow / Task container contract
Verified against Flow 5.0.0-beta.39
This page sets out what Flow hands a task's container and what it takes back. It's for task developers who need the exact rules, and for developers whose own dispatcher runs task images. For a walkthrough that builds a task and adds it to a workspace, see Develop a task.
Seven task types go to a dispatcher. The engine runs every other type itself, with no container.
| Where it runs | Task types |
|---|---|
| A dispatcher | template, custom, script, generic, ai, uploadartifact, downloadartifact |
| The engine | start, end, decision, acquirelock, releaselock, runworkflow, runscheduledworkflow, setwfstatus, setwfproperty, approval, manual, eventwait, sleep |
A dispatcher receives only the types it registered. Flow's Kubernetes dispatcher registers every dispatched type except generic, so a generic task waits in the queue until a dispatcher that registers it is running.
template and script run the task's image with its command, arguments or script. A task with no image takes Admin › Settings › Tasks › Default Image Path when its task run is created.custom takes its runtime from its own parameters: image, command and arguments (each split on newlines), and shellScript. A secret command or arguments arrives as one element.uploadartifact and downloadartifact: Flow fills in the transfer links when it hands the task out, and verifies an upload when the task ends. An optional download of a missing artifact ends succeeded with the result found=false, without reaching a dispatcher.ai runs the image its revision declares. See How AI tasks run.Flow fills in every reference before a dispatcher sees the task, at two moments:
| What | When | Stored with the values |
|---|---|---|
| The task run's parameter values | When the task is admitted | Yes. They're frozen, with secret values encrypted. |
The task's script, command, arguments and envs |
When a dispatcher claims the task | No. Only the copy handed to the dispatcher carries values, so no secret is written into a stored spec. |
So $(params.x) in a script, command, argument or environment variable reads the task's own parameter as frozen at admission, and a requeued task gets the same values. A $(workspace.params.x) or $(global.params.x) written straight into one of those four fields is read at the claim, so an edit made since admission shows.
Flow inserts each value byte for byte: quotes, newlines, backslashes and $ included. Shell syntax such as $(date) isn't a Flow reference and stays as written. A reference to something undeclared fails the task before any dispatcher can claim it, with the reason ParamUnresolved. A reference that can't be filled at the claim, because its workspace or global parameter was deleted, fails the task with SpecFillFailed.
Flow's Kubernetes dispatcher sets these environment variables. A later row wins on a name clash, so the task's own variables override the rest.
| Variable | Value |
|---|---|
HTTP_PROXY, HTTPS_PROXY, NO_PROXY, PROXY_HOST, PROXY_PORT |
The proxy, in upper and lower case, when the operator turns one on |
DEBUG |
The run's debug flag |
CI |
true |
FLOW_VERSION |
The dispatcher's flow.version |
RESULTS_PATH |
/tekton/results, a directory, on the tekton executor; /dev/termination-log, a file, on the kube-jobs executor |
RESULTS_MAX_BYTES |
The most bytes all results together may take: 4096 on kube-jobs; on tekton, 4096 unless the operator raised it to match the cluster |
PARAM_NAMES |
Every parameter's original name, comma-separated |
PARAM_<NAME> |
One per parameter |
TRACEPARENT |
The task run's W3C trace context |
| The task's own variables | As declared |
The container also gets:
/data: a scratch directory of its own;/workspace by default);kube-jobs it's mounted at /scripts/script and runs as a file, so it must start with a shebang such as #!/bin/sh. On tekton it runs as the step's script.There's no /params directory and no variable holding every parameter as JSON. Pass a large input on the run workspace, with a parameter carrying its path.
A-Z, a-z, 0-9 and _ with _. So api-url arrives as PARAM_API_URL, and privateKey as PARAM_PRIVATEKEY. PARAM_NAMES maps each variable back to its exact name.PARAM_NAME_COLLISION. The dispatcher repeats the check and fails a task that still has one.PARAM_ITEM, the element (JSON when it's an object or array), and PARAM_INDEX, its position from 0 as text. See Repeat a task for each item.A secret parameter reaches the container as an ordinary PARAM_<NAME>, in plain text. Flow masks secrets on every read and scrubs the run's secret values from the logs it serves, but there's no per-task Kubernetes Secret.
Warning
Anyone who can read the task's pod spec can read its secrets. On the tekton executor, every parameter is also a parameter of the TaskRun object. Don't print a secret: until Flow serves the log, it's unscrubbed. See Use secrets.
Exit 0 to succeed. Any other exit code fails the task. When a task fails, Flow's Kubernetes dispatcher sends a status message of up to 4096 bytes: the Job's or TaskRun's condition, the container's exit code and termination reason, and the last 20 lines of its log.
Write results to RESULTS_PATH:
| Executor | RESULTS_PATH |
Write |
|---|---|---|
tekton |
/tekton/results, a directory |
One file per result, named after the result, holding its value |
kube-jobs |
/dev/termination-log, a file |
One JSON object of result names and values, or Tekton's [{"key": "…", "value": "…"}] array |
Only declared results count. Tekton collects only the results the task declares, and on kube-jobs Flow keeps only the declared names when the task declares any. On kube-jobs, the termination message carries results and nothing else: a short message that isn't a results payload is logged and returns no results. Write errors to the log instead: the status message carries its last 20 lines. Develop a task has a shell snippet that works on both executors, and the Node library @boomerang-io/task-core.
| Cap | Limit | Over it |
|---|---|---|
| All parameters, resolved | 16384 bytes, set by flow.engine.task.params.max-bytes on core |
The task ends invalid with a PARAMS_TOO_LARGE message, before any dispatcher sees it. |
| All results | RESULTS_MAX_BYTES, 4096 bytes by default on Flow's Kubernetes dispatcher |
The task fails with ResultsTooLarge. Kubernetes cuts a termination message at 4096 bytes, so Flow fails the task rather than keep a truncated payload. |
| Results the engine stores | 1048576 bytes (1 MB), set by flow.engine.task.results.max-bytes |
The task fails with ResultsTooLarge and nothing is stored. This guards storage against a faulty dispatcher; a task meets the dispatcher's limit first. |
| A for-each parent's collected results | 16 MB, the database's document limit | The parent fails with ResultsTooLarge. |
A node's retry setting never retries ResultsTooLarge. Move larger data by reference: a path on the run workspace, or an artifact. See Keep files between runs.
Catalogue tasks are released from the boomerang-io/tasks repository on their own version lines, apart from Flow's.
| Image | Runs |
|---|---|
boomerangio/task-flow |
Every catalogue task that names no image of its own, through Default Image Path (boomerangio/task-flow:3.3.0 at this release). Flow's Kubernetes dispatcher also runs it for the Upload Artifact and Download Artifact tasks. |
boomerangio/task-ai:2.1.0 and 2.0.0 |
Revisions 2 and 1 of ai-prompt and ai-agent |
boomerangio/box-service:0.0.18 |
The Box tasks |
boomerangio/worker-python:0.0.3 |
Revision 2 of run-python-script |
rtsp/mongosh |
Revision 1 of mongodb-query-execution |
A workflow pins each node's task version when it's saved, so a new revision changes nothing until the workflow is saved again.
The task-flow tasks read their parameters from PARAM_NAMES and PARAM_<NAME>, and write results to RESULTS_PATH, through @boomerang-io/task-core. A dispatcher you write must set those variables to run catalogue images.
An ai task runs the image and command its revision declares, prompt or agent. It never runs a script, and a workflow author never builds its container. An in-process dispatcher ignores the image and does the work itself.
Flow's Kubernetes dispatcher runs an ai task only when its image starts with a prefix the operator allows, boomerangio/task-ai by default. Any other image fails the task with ImageNotAllowed before anything is created, and a node's retry setting never retries it. To run an AI worker of your own, your operator adds its prefix: see Flow's Kubernetes dispatcher.
An ai pod also receives, when the operator sets them on the dispatcher:
OPENAI_BASE_URL and OPENAI_API_KEY, a default endpoint and token. The task's own endpoint and token parameters still arrive as PARAM_ENDPOINT and PARAM_TOKEN, and win inside the image.OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS and OTEL_EXPORTER_OTLP_PROTOCOL, the OpenTelemetry exporter settings, with OTEL_SERVICE_NAME set to flow-task-<taskRef> and OTEL_RESOURCE_ATTRIBUTES carrying flow.workflow.run.id, flow.task.run.id and flow.task.name.A task's own variable of the same name wins. The worker's parameters and results are documented in tasks/ai/README.md in the boomerang-io/tasks repository. For using AI tasks in a workflow, see Use AI tasks.
TRACEPARENT is 00-, then 00000000 followed by the run's 24-character id, then -, the last 16 characters of the task run's id, and -01. For example, task run 6ad1bfa6cfefa66e88d5e2e0 of run 6ad1bfa3cfefa66e88d5e24a gets:
00-000000006ad1bfa3cfefa66e88d5e24a-cfefa66e88d5e2e0-01A task that exports OpenTelemetry spans and reads TRACEPARENT through the W3C propagator lands in its run's trace, beside the run's other tasks. A retry is a new run, so it's a new trace. Flow stores no traces and emits no run or task spans of its own.