Overview
In Reliant workflows, all string values in YAML are treated as potential CEL expressions. The template syntax{{expression}} is used to embed CEL expressions within strings.
Key Concepts
- Template Interpolation: Use
{{expression}}to insert dynamic values into strings - Pure Expressions: When an entire value is
{{expression}}, the native type is preserved (not converted to string) - Literal Strings: Text without
{{}}is treated as a literal value - Explicit Namespaces: All data access uses explicit namespaces (
inputs.*,nodes.*, etc.)
Quick Examples
Template Syntax
Basic Interpolation
Use double curly braces to embed expressions within strings:Type Preservation
When an entire YAML value is a single{{expression}}, the expression’s native type is preserved:
Literal Values
Text without{{}} is passed through as-is:
Available Namespaces
Reliant uses an explicit namespace model—all data access must use a namespace prefix. There is no implicit variable injection.inputs.*
Accesses workflow input parameters defined in the workflow schema.nodes.*
Accesses outputs from completed workflow nodes. Only available after the referenced node has completed.Optional Chaining (?.) for Conditional Nodes
When accessing nodes that may have been skipped (due to a condition: on the node), use optional chaining to safely handle the case where the node output doesn’t exist:
| Field | Type | Description |
|---|---|---|
message.role | string | Always "assistant" |
message.text | string | Response text content |
response_text | string | Same as message.text |
tool_calls | array | Tool calls requested by model |
input_tokens | int | Input tokens consumed |
output_tokens | int | Output tokens generated |
stop_reason | string | Why the LLM stopped generating |
| Value | Description |
|---|---|
end_turn | LLM finished its response naturally |
tool_use | LLM wants to use tools |
max_tokens | Hit the token limit |
stop_sequence | Hit a stop sequence |
| Field | Type | Description |
|---|---|---|
exit_code | int | Command exit code (0 = success) |
stdout | string | Standard output |
stderr | string | Standard error |
nodes.<loop_id>.<output_name> where <output_name> matches outputs declared in the loop’s inline workflow.
workflow.*
Provides workflow metadata and execution context.| Field | Type | Description |
|---|---|---|
workflow.id | string | Unique workflow execution ID |
workflow.name | string | Workflow definition name |
workflow.path | string | Working directory path |
workflow.branch | string | Current git branch |
iter.*
Provides loop iteration context. Only available inside loop constructs.| Field | Type | Description |
|---|---|---|
iter.iteration | int | Current iteration (0-indexed in loop body; increments before while check) |
outputs.* in while conditions. For inject templates, use thread: mode: inherit to preserve context across iterations.
output.*
Used insave_message blocks to reference the current activity’s output.
input_tokens, output_tokens, etc.) and thinking are automatically extracted from activity output - no explicit configuration needed.
outputs.*
Used in loopwhile conditions to reference the sub-workflow’s declared outputs.
Built-in Functions
Standard CEL Functions
| Function | Description | Example |
|---|---|---|
size(x) | Length of list, map, or string | size(nodes.call_llm.tool_calls) > 0 |
has(x.field) | Check if field exists | has(nodes.verify) ? nodes.verify.exit_code == 0 : true |
type(x) | Get type of value | type(inputs.data) == "string" |
String Functions
| Function | Description | Example |
|---|---|---|
contains(s, substr) | Check if string contains substring | contains(output.stderr, "error") |
startsWith(s, prefix) | Check if string starts with prefix | startsWith(inputs.model, "claude") |
endsWith(s, suffix) | Check if string ends with suffix | endsWith(file, ".go") |
matches(s, pattern) | Regex match | matches(output.stdout, "^OK") |
Reliant Custom Functions
first(list)
Returns the first element of a list as an optional. Returns optional.none() if the list is empty.
Use .orValue(default) to unwrap with a default value, or .value() to unwrap (errors on empty).
last(list)
Returns the last element of a list as an optional. Returns optional.none() if the list is empty.
Use .orValue(default) to unwrap with a default value, or .value() to unwrap (errors on empty).
join(list, delimiter)
Joins list elements into a string with a delimiter.
parseJson(string)
Parses a JSON string into a CEL value (map or list).
toJson(value)
Converts a value to its JSON string representation.
coalesce(value1, value2, ...)
Returns the first non-null argument. Supports 2-4 arguments.
getOrDefault(map, key, default)
Safely accesses a map key with a fallback default value.
Response Tool Data Access
Response tool data is available directly on ExecuteTools output via theresponse_data field. This field contains parsed response data keyed by tool name.
Response tools use JSON Schema to define structured outputs. A common pattern is choice/value:
response_data:
parseDuration(string)
Parses a Go duration string and returns seconds as a number.
Common Patterns
Conditional Logic
Use the ternary operator for conditional values:Default Values
Several patterns for handling missing or empty values:Checking Optional Fields
Usehas() to safely check for optional fields:
Working with Tool Calls
Common patterns for handling tool calls from LLM responses:Loop Iteration Patterns
Using iteration context in loops:String Interpolation
Embed multiple values in strings:Type Handling
CEL Type System
CEL is strongly typed. Values have specific types that affect how operators and functions work.| Type | Description | Examples |
|---|---|---|
int | Integer numbers | 0, 42, -1 |
double | Floating point | 1.5, 0.0, -3.14 |
string | Text | "hello", "" |
bool | Boolean | true, false |
list | Ordered collection | [1, 2, 3], [] |
map | Key-value pairs | {"key": "value"} |
null | Null value | null |
Null Checks
Always check for null before accessing nested fields:Type Coercion
CEL performs limited automatic type coercion:Boolean Evaluation
Values are not implicitly converted to booleans. Use explicit comparisons:Edge Conditions
Edge conditions use CEL expressions without the{{}} wrapper:
Loop While Conditions
Loops use do-while semantics: the first iteration always executes, theniter.iteration increments before the while condition is checked to determine whether to continue.
condition field:
while:
outputs.*- Sub-workflow outputs defined ininline.outputsiter.*- Loop iteration context (iter.iterationis 0-indexed)inputs.*- Workflow inputs (useful for configurable iteration limits likeinputs.max_turns)
iter.iteration increments, then the while condition is evaluated.
Iteration counting: In the loop body, iter.iteration is 0-indexed (0, 1, 2…). In the while check, it reflects completed iterations (1 after first, 2 after second). Use iter.iteration < N to run exactly N iterations.
Common Mistakes
Missing Null Checks
Wrong Namespace
Using {{}} in Conditions
Empty String vs Null
Accessing Undefined Nodes
Debugging Tips
- Check node IDs: Ensure referenced node IDs match exactly (case-sensitive)
- Verify node completion:
nodes.*only works after the node completes - Use has() liberally: Wrap optional field access in
has()checks - Check types: Use
type(x)to debug unexpected type errors - Start simple: Build complex expressions incrementally
See Also
- Custom Workflows - Complete workflow YAML schema
- Types Reference - Node inputs and outputs
- CEL Language Specification - Official CEL documentation