Skip to main content
Version: 2.1.0

Variables

A variable is a value you set when you run a workflow instead of hard-coding it in the file. You declare it on the workflow with a type and a default. An operator sets it at submit time, mid-run, or across the whole deployment.

Declare one on the workflow​

from orca.variables.variable_definition import VariableDefinition

@orca.workflow(name="smc_assay")
def smc_assay(wf: WorkflowContext):
wf.variable("plate_count", VariableDefinition(
type="int", default=1, min=1, max=96,
description="How many sample plates to spawn",
))
wf.start(sample_thread)
FieldEffect
typeOne of int, float, str, bool. Defaults to str.
defaultUsed when no override is set anywhere up the resolution chain.
min / maxRange bounds. Checked only for numeric values.
allowed_valuesWhitelist. A value not in the list fails validation.
descriptionHuman label for UI and tooling.
unitFree-form unit string ("mL", "degC") for UI and tooling.

Every write is checked against the definition, wherever it came from. A value that fails is refused and the set does not happen, so your code never reads an invalid one.

Read it in an action​

@orca.action(device=flex_1, inputs=[sample_plate])
async def transfer(ctx: ActionContext):
count = await ctx.param("plate_count", int)

ctx.param is async, so await it. The second argument narrows the type and raises TypeError on a mismatch. ctx.param also works inside an @orca.method and an @orca.thread, without the narrowing form. See Context API for the exact shape on each.

Set it when you run​

orca run smc_assay --vars plate_count=4

Mid-run, set it on a live execution and read it back:

orca var set plate_count 4 --execution <id>
orca var get plate_count --execution <id>

orca var get also reports which layer answered the read. CLI: control has the flags and the confirmations.

Advanced​

Below is what you need when a default and one override are not enough: values that outlive a single workflow, the order the layers are searched in, expressions computed at read time, and the full operator verb list.

Two scopes: workflow and global​

ScopeReference syntaxWhere declared
Workflowbare name: dilution_factorOn a @orca.workflow via wf.variable, local to that workflow template.
Globalglobal. prefix: global.reagent_batchOn the variable service, visible from every workflow.

The scopes are strict. A bare workflow lookup does not fall through to a global of the same name. To read a global from a workflow, write global.<name>.

Workflow definitions are registered on the variable store when the system is built, and replayed when a workflow is added after the build, so a workflow submitted later resolves defaults and validates values like a built-in one.

Declaring a global​

A global's value has two operator routes: orca var set-global <name> <value>, and a deployment profile whose key carries the global. prefix (see below). A global definition, meaning the type, default and bounds, has no decorator or CLI surface: it is registered by calling register_global_definitions on the variable service when the deployment is wired up. A global with no definition is still readable; it just gets no validation.

Validation in detail​

class VariableDefinition(BaseModel):
type: Literal["int", "float", "str", "bool"] = "str"
default: OptionValue | None = None
min: float | None = None
max: float | None = None
allowed_values: list[OptionValue] | None = None
description: str = ""
unit: str = ""

validate_value(name, value) runs on every set, whether the write targets a submission, an execution or the global layer, and whether it came from the CLI, a profile or the API. The rules:

  • bool is rejected where int or float is expected. Python's bool is a subclass of int, so the validator guards against it explicitly.
  • int is accepted where float is expected. The reverse is not.
  • min and max are checked only for int and float values.
  • allowed_values is checked whenever it is set.

A failure raises VariableValidationError and the set does not happen, so engine code reading the value never sees an invalid one. A name with no definition anywhere is written unvalidated.

Resolution layers​

Five layers, first match wins.

LayerSet byScope
1. Submission partitionorca var set <name> <value> --execution <id> --submission <sid>, or --vars at submitOne submission inside one execution
2. Execution partitionorca var set <name> <value> --execution <id>, or orca run --vars KEY=VALOne execution
3. Global value layerorca var set-global <name> <value>Deployment-wide. global. names only.
4. ComputedAn expression registered on the variable serviceBoth scopes. Evaluated at read time.
5. Definition defaultdefault= on the VariableDefinitionThe scope it was declared in

A global.name lookup walks all five. A bare name walks 1, 2, 4, 5: the global value layer is skipped because the scopes are strict.

Layer 1 only applies when the reader is inside a submission. ctx.param in an action, method or thread passes the thread's submission id automatically. An operator read with no --submission gets what a submission holding no override of its own would resolve.

orca var get reports which layer answered. The values are submission, submission-diverged, execution, global, computed, global-default and workflow-default.

Mid-flight sets​

A var set mid-flight takes effect on the next read. A thread that already read the value and stored it in a local keeps the old one. The variable service holds a single threading.Lock around resolution and mutation, because ctx.param is called from executor threads off the event loop, so concurrent set and resolve are safe.

Computed variables​

A computed variable is an expression string evaluated at read time. The supported route is a deployment profile:

{
"name": "high_throughput",
"variables": {"plate_count": 4, "global.reagent_batch": "L7"},
"computed": {"total_volume": "plate_count * 200"}
}
orca run smc_assay --profile ./profiles/high_throughput.json

Keys with the global. prefix write the global layer as well as the execution partition; bare names write the execution partition only. computed is name to expression. The same map can be registered directly with register_computed on the variable service.

The expression is evaluated with Python's eval against a namespace whose builtins are removed, so any name it does not resolve to a variable is an error rather than a callable. Nothing parses the expression into a syntax tree. What the namespace holds, in order:

  • workflow-definition defaults for the executing workflow
  • global-definition defaults
  • the global value layer
  • the execution partition

Submission overrides are not in it. A computed variable reads the same in every submission of an execution.

Two more constraints, both reported before or during evaluation with the offending name:

  • Every variable the expression references must be numeric. A str or bool operand raises ExpressionError naming the variable and its type.
  • The result must be int or float. Anything else raises ExpressionError.

Undefined names, syntax errors and division by zero all raise ExpressionError too.

Computed values are not cached. Every read re-evaluates, so a layer that changes mid-execution shows up on the next read.

Reserved names​

A workflow variable name cannot be the bare word global or start with global., case-insensitive. The store rejects it at registration. This keeps the two-scope addressing unambiguous.

The full operator verb list​

ActionCLI
List what an execution seesorca var list --execution <id>
List across every executionorca var list --all
Read one value and the layer that answeredorca var get <name> --execution <id>
Read as one submission's threads doorca var get <name> --execution <id> --submission <sid>
Show what each submission resolvesorca var explain <name> --execution <id>
Override for an executionorca var set <name> <value> --execution <id>
Override for one submissionorca var set <name> <value> --execution <id> --submission <sid>
Set a globalorca var set-global <name> <value>
Remove an overrideorca var unset <name> --execution <id>
Remove a submission's overrideorca var unset <name> --execution <id> --submission <sid>
Pass overrides at submit timeorca run <workflow> --vars KEY=VAL,KEY2=VAL2
Pass overrides for one submissionorca submission submit <workflow> --vars KEY=VAL

The name and value are positional; the execution is a --execution flag taking an id or a prefix. set, set-global and unset prompt for confirmation unless --force is passed. CLI: control covers setting and unsetting, CLI: monitor covers reading.

var set and var get warn when submission layers outrank what you just read or wrote, and name the submissions, so a write that no running thread will see does not pass silently.

Pitfalls​

  • A bare workflow name does not see globals. To read a global from an action, write ctx.param("global.batch_id"). A bare ctx.param("batch_id") raises UndefinedVariableError even when a global of that name exists.
  • int widens to float; float does not narrow to int. A VariableDefinition(type="int") rejects a float, including 1.0. The CLI parses --vars KEY=1.0 to a float, which then fails against an int definition.
  • bool validation is strict. type="int" rejects True and False even though Python's bool is a numeric subclass.
  • A submission override outranks your execution-level set. orca var set ... --execution <id> will not change what an already-submitted thread resolves if that submission holds its own value. Write the submission layer, or clear it with var unset --submission.
  • Computed variables cannot see submission overrides. If two submissions need different values, the variable has to be a plain one, not computed.
  • unset does not remove the definition. It drops the value from one partition. The next read falls through to global, computed, then the definition default. Removing a definition is a workflow-template change.
  • Mid-flight var set does not change a value already cached. A thread that read shake_speed=500 into a local will not see your 600. Pause it first, or write the action to read each time.

See also​

  • Context API: ctx.param on the action, method and thread contexts.
  • Workflows: where wf.variable sits among the other builder calls.
  • Submissions: how --vars at submit time fits the larger submission shape.
  • CLI: control: setting and unsetting values.
  • CLI: monitor: reading variables from the operator side.