Variables
E0-style or descriptive names like fetch_project) and input; item, index, and accumulator join them inside iteration bodies, and length is a path operator — step ids may not shadow any of those five. Root-anchored paths are strict — a path that doesn’t resolve is a typed error, never a silent empty string (see below).
Two scoping rules come from branching: a decide step’s result is referenced through its own id ({{E1.result.…}}, {{E1.branch}}) — branch-internal step ids are visible only to later steps inside the same branch — and the non-taken branch’s templates are never rendered at all.
Typed splice
A string that is exactly one variable tag resolves to the raw JSON value — numbers stay numbers, arrays stay arrays:Sections
Sections iterate arrays and gate on truthiness — used mostly in solver templates:- Inside a section, bare keys (
{{title}}) read from the current item, falling back to enclosing scopes and roots. - Loop variables:
{{@index}}(0-based),{{@first}},{{@last}}— booleans usable as sections, e.g. the comma-separation idiom above. - Inverted sections render when the value is missing, false, or empty:
- A bare key missing on a list item renders empty (items legitimately omit optional fields). A root-anchored section path that goes nowhere is falsy rather than an error — sections are conditionals.
- Static validation applies the same scoping rule: a bare key inside a section body is an item read, never a root reference, so it is exempt from the “
inputor an earlier step” check. Dotted paths are root-anchored everywhere — a typo’d or forward root inside a section ({{E9.summary}}beforeE9runs) is still caught at load time.
Error taxonomy
Variable resolution failures are typed, because different failures deserve different handling:
How each is handled depends on who authored the plan — see Errors & replanning. The short version:
EmptyData means “nothing found”, not “broken plan”.