Operators
The deterministic control-flow nodes — Choice, Map, Decision Table, Rules, Expression, and Sub-workflow — what each one does and when to reach for it.
Operators are the nodes in dynamiq.nodes.operators that shape a flow without calling a model: they branch, iterate, decide, compute, and delegate. They cost nothing per run, behave the same way on every input, and combine freely with LLM, agent, and tool nodes in the same Flow.
The operators at a glance
| Node | What it does | Guide |
|---|---|---|
Choice | Evaluates JSONPath conditions over its input and gates the nodes that depend on each option. | Choice node |
Map | Runs one inner node once per item of a list, optionally in parallel. | Map node |
DecisionTable | Matches typed inputs against rows of condition cells and returns the outputs of the matching rows. | Decision Table |
Rules | Checks a record against a list of rules and returns one finding per rule, with the values each check read. | Rules |
Expression | Computes new fields from its inputs with sandboxed Jinja2 expressions. | Expression |
SubWorkflow | Runs another Flow as one step and returns its Output node's result. | Sub-workflow |
All six import from one place:
from dynamiq.nodes.operators import Choice, DecisionTable, Expression, Map, Rules, SubWorkflowEvery operator is an ordinary Node. It takes depends (or .depends_on()), input_transformer (or .inputs()), error_handling, caching, and callbacks like any other node — see Workflows, Flows & Nodes — and it serializes to YAML and appears in traces with its input and output.
Choosing between them
- Route with
Choicewhen the next step depends on a condition and the branches are different nodes. - Decide with
DecisionTablewhen the logic is a matrix of rules — eligibility checks, pricing grids, scorecards, lookups — that a domain expert should be able to read and edit. One table replaces a chain ofChoicenodes or a Python function full ofifstatements, and the rules that fired come back with the result. - Review with
Ruleswhen every check must be reported, not only the first match — a claim adjudication, an invoice match, a compliance screen, a pre-funding review. Each rule reads the record by path and has a severity, and its finding says whether it passed, failed, warned, did not apply or could not be evaluated; a missing value never passes silently. - Compute with
Expressionwhen you need a derived value — a ratio, a rounded total, a label — and a Python node would be overkill. - Reuse with
SubWorkflowwhen a decision or a pipeline is shared by several workflows, or when a batch should run it once per item inside aMap. - Iterate with
Mapwhen the same node must run for every element of a list.
The decision operators are built for each other: a table's outputs feed an expression, a rule set's status drives a Choice and its findings feed an expression that collects reason codes, and the whole chain packages into a SubWorkflow that a Map runs over a batch. The loan pricing example in the repository's examples/components/core/dag directory, decision_workflow.yaml with run_decision_workflow.py, wires all of them together in YAML and runs offline.
Choice: first or all matching branches
By default a Choice follows the first option whose condition holds: every option after it is skipped, and an option without a condition is the else branch. Set hit_policy="all" to run every option whose condition holds. An option without a condition stays the fallback — under all it runs only when no conditioned option matched, wherever it sits in the list.
from dynamiq import Workflow
from dynamiq.flows import Flow
from dynamiq.nodes.node import NodeDependency
from dynamiq.nodes.operators import Choice, ChoiceOption, Expression
from dynamiq.nodes.types import ChoiceCondition, ConditionOperator, ExpressionItem
from dynamiq.runnables import RunnableConfig
route = Choice(
id="route",
name="route",
hit_policy="all",
options=[
ChoiceOption(
id="large",
name="large",
condition=ChoiceCondition(
operator=ConditionOperator.NUMERIC_GREATER_THAN, variable="$.amount", value=1000
),
),
ChoiceOption(
id="international",
name="international",
condition=ChoiceCondition(
operator=ConditionOperator.STRING_EQUALS, variable="$.region", value="EU"
),
),
ChoiceOption(id="default", name="default"), # no condition: the fallback
],
)
large_review = Expression(
id="large_review",
name="large_review",
expressions=[ExpressionItem(key="check", expression="'manual review above 1000'")],
depends=[NodeDependency(route, option="large")],
)
vat = Expression(
id="vat",
name="vat",
expressions=[ExpressionItem(key="vat", expression="(amount * 0.2) | round(2)")],
depends=[NodeDependency(route, option="international")],
)
plain = Expression(
id="plain",
name="plain",
expressions=[ExpressionItem(key="check", expression="'no checks'")],
depends=[NodeDependency(route, option="default")],
)
workflow = Workflow(flow=Flow(nodes=[route, large_review, vat, plain]))
result = workflow.run(
input_data={"amount": 2500, "region": "EU"},
config=RunnableConfig(callbacks=[]),
)
for node_id in ("large_review", "vat", "plain"):
print(node_id, result.output[node_id]["status"], result.output[node_id]["output"])
# large_review success {'check': 'manual review above 1000'}
# vat success {'vat': 500.0}
# plain skip NoneWith the default hit_policy="first" the same input runs large_review only: vat is skipped because an earlier option already matched. In YAML the field is hit_policy: all on the node. The condition operators, AND/OR groups, and the _PATH variants that compare two inputs are covered in the Choice node guide.
A node behind an option that did not match is recorded with status skip, not failure, and nodes downstream of it are skipped too. Read the statuses in result.output to tell the branches apart — see Running workflows & results.
Decision Table
Rules as rows: cell grammar, hit policies, aggregation, and coercion.
Rules
Every check of a review reported as a finding, with severities and the values read.
Expression
Derived fields from sandboxed Jinja2 expressions.
Sub-workflow
Run a flow as one step, alone or per item inside a Map.
Map node
Fan-out and fan-in over a list, with the SDK equivalent.
Streaming & Callbacks
Stream tokens and intermediate steps from LLM and agent nodes with StreamingConfig, and hook every lifecycle event with callback handlers.
Decision Table
Match typed inputs against rows of condition cells — the cell grammar, hit policies and aggregations, input coercion, build-time validation, and the YAML form.