Private Beta
.md

BountyHub Docs

Workflow Syntax Reference

Complete reference for the BountyHub workflow language: workflows, activities, triggers, conditions, inputs, and durations.

BountyHub workflows are written in Starlark — a small, deterministic, Python-like configuration language. A workflow file is a Starlark program whose result is a workflow(...) definition.

This page is the complete reference for the structural parts of the language. Step primitives are covered in Steps and the pages that follow it.

#Anatomy of a Workflow File

BHW

Key rules:

  • Every workflow file must produce a workflow(...) value.
  • Activities are referenced by the variables they are bound to, not by their name strings.
  • step.module(...) returns the invocation; its declared outputs are only reachable through it as invocation.outputs.<name> (e.g. probe_run.outputs.domains), never as <activity>.artifacts.<name>. A load() symbol is the module definition and has no outputs.
  • The language is a strict subset of Starlark plus BountyHub builtins — no arbitrary I/O, no imports beyond load() of published modules.

#workflow(...)

Defines a workflow from a list of activities:

BHW

For simple single-activity workflows you can use the shorthand form, which builds one synthetic activity:

BHW

#activity(...)

An activity is a named unit of work with its own triggers, condition, environment, steps, and optional timeout:

BHW
  • triggers decide when the activity starts (see Triggers). An activity with no trigger never starts on its own; it can only be started by an event reference from another activity's trigger.
  • env sets environment entries for the activity. Values are plain strings; use config.* references inside individual steps when values must be resolved at execution time.
  • timeout bounds the whole activity; individual steps accept their own timeout.

#Activity values

Binding an activity to a variable gives you an activity value used for wiring:

ExpressionMeaning
discover.succeeded()Event: activity finished successfully
discover.failed()Event: activity failed
discover.completed()Event: activity reached any terminal state
discover.artifacts.<name>Reference to a named artifact produced by the activity
discover.when(cond)Copy of the activity that only runs when the runtime predicate cond holds (lambda run: ...)

#Triggers

Triggers decide when an activity starts.

#Schedule: trigger.cron(expr)

Standard cron expression:

BHW

#Events: trigger.on(event_expr)

Start when another activity reaches a terminal state:

BHW

Events are declarative subscriptions — they describe what to react to, not when to evaluate. Compose them with event.all(...) and event.any(...):

BHW

Compositions nest freely (event.any(a, event.all(b, c))).

#Manual dispatch: trigger.dispatch(inputs = {...})

Start the activity manually (from the UI or CLI), optionally declaring inputs the dispatcher must provide:

BHW

Inputs are available at runtime through the run context as run.inputs. For example, build the command line from the dispatched domain:

BHW

Dispatch input declarations:

DeclarationParameters
input.string(...)default="", required=False, description
input.bool(...)default=False, required=False, description

Dispatch inputs are input.string / input.bool only; there is no input.file dispatch input.

Module files use a separate namespace with explicit public names (input.string(name = ...), input.bool(...), input.file(...), input.directory(...)); see Modules.

#Conditions

Conditions gate whether an activity or step runs.

The default condition is run.ok: run only if everything before it succeeded. You can override it with a lambda that receives the run context:

FieldTypeMeaning
run.okboolWhether all previous steps succeeded
run.inputsdictDispatch inputs of the current run
run.stepsdict of step_resultResults of previous steps, keyed by step id
run.triggertrigger contextWhat started this run (dispatch, cron, or on)

A step_result exposes status and conclusion, compared against the steps.Status and steps.Conclusion enums (SUCCEEDED, FAILED, SKIPPED).

Steps are addressable by the id you give them (step.exec(..., id = "scan") is read as run.steps.scan or run.steps["scan"]). Without an id, a step has no stable key in run.steps. name is the optional human-facing label shown in the UI; id is the machine key used by conditions.

A step predicate is the concise form of the same check:

PredicateMeaning
scan.succeeded()run -> bool: true when the referenced step succeeded
scan.failed()run -> bool: true when the referenced step failed

Both require the referenced step to have an id. They are deferred: the runner evaluates them right before the dependent step runs.

Examples:

BHW

.when(F) is the fluent spelling of cond = F and works on both steps and activities:

BHW

A condition must be a runtime predicate. Static booleans (cond = True, .when(True)) are rejected so a condition can never be evaluated while the workflow is being defined. Step predicates only see earlier steps in the same activity; cross-activity wiring uses trigger.on(scan.succeeded()).

Activities also accept conditions, either directly (cond = ...) or fluently via .when(...), which returns a copy of the activity that only runs when the condition holds:

BHW

#Durations

Timeouts are expressed with the duration namespace. Constructors compose fluently:

BHW

Use them on activities and steps:

BHW

Durations must be strictly positive.

#Limits

Workflows enforce platform limits on structure (number of activities per workflow, steps per activity, triggers per activity, environment entries per activity). Keep activities focused — split large phases into separate activities wired by events rather than building monolithic step lists.

#Language Notes

  • Comments start with #.
  • Keyword arguments use = (as in Python): name = "scan".
  • Strings support "..." and '...'; .format(...) works on strings and is commonly used with module inputs (see Modules).
  • Everything is validated when the workflow is saved: unknown names, type mismatches, and unbound required inputs are compile-time errors, not runtime surprises.