Skip to content

Names, Scope, and Single Assignment

A step is a map with exactly one key, and that key is the step’s id. There is no separate id field, and no step is anonymous.

- classify: # the id is `classify`
llm.categorize: { ... }

Ids MUST be unique within their enclosing scope and MUST match ^[a-z][a-z0-9_]*$.

The names run, inputs, trigger, current, and error are reserved for the bindings that this specification defines. A file MUST NOT use a reserved name as a step id, a trigger as binding, an into binding, an outputs name, or a state’s accepts name.

Note: run is also the key of a step body. A step body key is not an expression path, so the two uses do not conflict. {{ run.id }} always names the run metadata.

An id names the binding that holds the step’s result. Bindings are created once and are never reassigned. No syntax mutates a binding, and one id resolves to one value for the life of a run.

A step binds either a value or a scope, according to its body kind. The table of step bindings states which body kinds bind which.

  • A binding that holds a value is read directly: {{ classify.category }} reads a field of an action result.
  • A binding that holds a scope is read by the ids inside it: {{ triage.lead.id }} reads the lead step of the branch that a route executed.

Scopes nest. A step MAY reference a binding in its own scope and in any enclosing scope. The following constructs create a scope: route cases, choose arms, for_each iterations, parallel branches, repeat iterations, and states entries. A binding created inside such a construct is visible within it, and from outside through the construct’s own id.

steps:
- classify: { ... } # binds `classify` in the root scope
- triage:
route: "{{ classify.category }}"
cases:
sales:
- lead: { ... } # binds `lead` inside the `sales` case
# visible outside as `triage.lead`

An inner binding does not shadow an enclosing one. Ids are unique within a scope, and a construct’s bindings are reached through its own id.

A trigger binds its payload in the root scope. That binding follows the rules of this section. A root-scope step id that repeats the binding’s name is a collision, and as MUST resolve it.