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)
| Field | Effect |
|---|---|
type | One of int, float, str, bool. Defaults to str. |
default | Used when no override is set anywhere up the resolution chain. |
min / max | Range bounds. Checked only for numeric values. |
allowed_values | Whitelist. A value not in the list fails validation. |
description | Human label for UI and tooling. |
unit | Free-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
| Scope | Reference syntax | Where declared |
|---|---|---|
| Workflow | bare name: dilution_factor | On a @orca.workflow via wf.variable, local to that workflow template. |
| Global | global. prefix: global.reagent_batch | On 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:
boolis rejected whereintorfloatis expected. Python'sboolis a subclass ofint, so the validator guards against it explicitly.intis accepted wherefloatis expected. The reverse is not.minandmaxare checked only forintandfloatvalues.allowed_valuesis 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.
| Layer | Set by | Scope |
|---|---|---|
| 1. Submission partition | orca var set <name> <value> --execution <id> --submission <sid>, or --vars at submit | One submission inside one execution |
| 2. Execution partition | orca var set <name> <value> --execution <id>, or orca run --vars KEY=VAL | One execution |
| 3. Global value layer | orca var set-global <name> <value> | Deployment-wide. global. names only. |
| 4. Computed | An expression registered on the variable service | Both scopes. Evaluated at read time. |
| 5. Definition default | default= on the VariableDefinition | The 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
strorbooloperand raisesExpressionErrornaming the variable and its type. - The result must be
intorfloat. Anything else raisesExpressionError.
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
| Action | CLI |
|---|---|
| List what an execution sees | orca var list --execution <id> |
| List across every execution | orca var list --all |
| Read one value and the layer that answered | orca var get <name> --execution <id> |
| Read as one submission's threads do | orca var get <name> --execution <id> --submission <sid> |
| Show what each submission resolves | orca var explain <name> --execution <id> |
| Override for an execution | orca var set <name> <value> --execution <id> |
| Override for one submission | orca var set <name> <value> --execution <id> --submission <sid> |
| Set a global | orca var set-global <name> <value> |
| Remove an override | orca var unset <name> --execution <id> |
| Remove a submission's override | orca var unset <name> --execution <id> --submission <sid> |
| Pass overrides at submit time | orca run <workflow> --vars KEY=VAL,KEY2=VAL2 |
| Pass overrides for one submission | orca 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 barectx.param("batch_id")raisesUndefinedVariableErroreven when a global of that name exists. intwidens tofloat;floatdoes not narrow toint. AVariableDefinition(type="int")rejects a float, including1.0. The CLI parses--vars KEY=1.0to a float, which then fails against an int definition.boolvalidation is strict.type="int"rejectsTrueandFalseeven though Python'sboolis 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 withvar unset --submission. - Computed variables cannot see submission overrides. If two submissions need different values, the variable has to be a plain one, not computed.
unsetdoes 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 setdoes not change a value already cached. A thread that readshake_speed=500into a local will not see your600. Pause it first, or write the action to read each time.
See also
- Context API:
ctx.paramon the action, method and thread contexts. - Workflows: where
wf.variablesits among the other builder calls. - Submissions: how
--varsat submit time fits the larger submission shape. - CLI: control: setting and unsetting values.
- CLI: monitor: reading variables from the operator side.