BountyHub Docs
Step Outputs
Declare what a step produces and pass it to later steps by reference instead of remembering file names.
Step Outputs
Every step declares what it produces. Consumers reference the step's result, not the producer's file name:
hosts.output is a file reference. The second step never needs to know that
the file is called hosts.txt.
#output and outputs
A step result has:
| Field | Meaning |
|---|---|
run.output | the single declared output (file or directory) |
run.outputs.<name> | a named declared output |
run.output exists only when the step declares exactly one output. With
zero or several, use run.outputs.<name>; using .output there is an error.
#Where outputs come from
The runner writes the file from stdout. stdout = path.file(...) exposes
that file as run.output:
The tool writes the file itself. The runner only sees argv, so declare the file:
The step type owns the destination. step.transform publishes its
destination; step.agent publishes its declared agent.output file:
#Multiple files
Use a name → path map. Each name becomes a field:
When a tool writes an unpredictable tree, publish a directory or a glob instead of listing every file:
#Passing outputs on
An output is a path-like resource. Use it anywhere a file reference is accepted:
#Modules
A module declares its own inputs and outputs. The caller sees only the declared outputs:
A module invocation is isolated: its declared file inputs are copied in, and only its declared outputs are copied back. It never reads the caller's other files, and it never writes into the caller's workspace.
#Loops
Inside step.for_each, each iteration runs the body as its own step sequence.
Give each iteration a distinct output path, then consume the published files
from a later step:
Per-iteration result collections (scans[i].outputs) are not available yet;
the published files are the interface.
#Isolated steps
By default, script/exec/agent steps in an activity share one workspace.
To give every data step its own private filesystem, opt in at the workflow
level:
With isolation:
- each
script/exec/transform/agent/module/dataset/decision step gets a fresh directory; - declared outputs are the only data that leaves it;
- a later step receives a referenced output by copy, and cannot read anything else the earlier step wrote;
- files a step writes without declaring them are discarded.
Module steps are always isolated, regardless of the flag. for_each control
steps run in the parent scope, but their body steps are isolated and their
items (including glob and batch files) are materialized into each iteration.
Artifact downloads are isolated too, and must declare outputs so the
downloaded files leave the step.
References are the interface either way: run.output / run.outputs.<name>.
Passing a bare path string that another step happened to write is not data
flow; it only works while the shared workspace is enabled.
#Rules
- A step's file names appear once: where the step is defined.
- Consumers use
run.output/run.outputs.<name>, never a producer's path. .outputonly exists when exactly one output is declared.outputs = {...}is the complete contract; it replaces the implicitstdoutoutput.- Directories and globs are valid outputs for tools that write a tree.
- Module steps are isolated; by default
script/exec/agentshare the activity workspace, andworkflow(isolated_steps = True)gives each data step its own private scope.
#Migration
Old workflows shared a directory and passed file names between steps. Replace the path with the producer's result: