Templates and expressions | Diaphora Docs | Diaphora
Templates and expressions
FML uses two expression systems, and mixing them up is the single most common source of bugs in a blueprint. The rule behind all of them is one sentence:
Go text templates always produce a string. Wrapped Expr
$(...)preserves the real type.
This guide explains the three positions where expressions appear, why the string-vs-type distinction matters, and how to always pick the right one.
Prerequisites
- The Data flow: params, vars & context guide — you should know the three namespaces.
Why there are two systems
A prompt is text, so interpolating a value into it should give you text, that's what Go templates do. But a tool argument might need a real array, object, or number, not a string that looks like one. Handing "[1, 2, 3]" (a string) to an argument that expects [1, 2, 3] (an array) is a type error waiting to happen. $(...) exists to pass the native typed value through untouched.
The three positions
| Position | Syntax | Type | Where |
|---|---|---|---|
| 1. Text | {{ ... }} |
Always string | prePrompt/prompt text, context strings, call string args, set values, parameter defaults |
| 2. Typed tool args | $( ... ) |
Any (native) | Only inside call argument values |
| 3. Session conditions | "..." (bare Expr) |
Bool / value | expect="...", iterate="..." |
1. Go text templates — {{ ... }}
Used anywhere the destination is text:
+ Research {{ .params.topic }} in the {{ .vars.region }} region.
- Summarize the {{ .params.max_results }} findings above.
Common patterns:
| Pattern | Meaning |
|---|---|
{{ .params.x }} |
Parameter, as a string |
{{ .vars.x }} |
Variable, as a string |
{{ .context.session }} |
Full session output (Go struct notation) |
| `{{ .context.session | json }}` |
{{ .it }} |
Current iterator element, as a string |
The | json filter is how you drop a whole structured object into prompt text — it serializes to a JSON string, which is exactly what you want inside text.
2. Wrapped Expr — $( ... )
Used exclusively inside call argument values, when the argument must receive a non-string value from scope:
call("process_items") {
label = "Items for {{ .params.topic }}" # string arg → template is correct
items = $(context.gather.sources) # array arg → Expr preserves the type
limit = $(params.max_results) # int arg → Expr preserves the type
}
| Pattern | Returns | Use case |
|---|---|---|
$(context.session.field) |
Any | Pass a nested object/array from a session |
$(params.count) |
int | Pass a number as a number |
$(vars.myList) |
array | Pass a variable holding an array |
$(len(context.items)) |
int | Pass a computed integer |
3. Bare Expr in session arguments
expect and iterate take a plain quoted Expr string — no$(...) wrapper:
session("elaborate",
after="gather",
expect="len(context.gather.keyPoints) > 0",
iterate="context.gather.keyPoints") {
...
}
| Pattern | Meaning |
|---|---|
len(context.first) > 0 |
Array length check |
context.overview.keyPoints != null |
Nil check |
params.max_results |
Parameter value |
The decision in one question
When you're about to interpolate a value, ask: is the destination text, or a tool argument?
- Text (a prompt, a label, a
contextstring) →{{ ... }}. If it's a structure, add| json. - A
callargument that needs a real array/object/number →$( ... ). - An
expect/iteratecondition → bare Expr in quotes, no wrapper.
The classic bug
You want to pass a session's array of sources to a tool:
# WRONG — the tool receives the string "[{...},{...}]", not an array
call("rank") {
items = "{{ .context.gather.sources }}"
}
# RIGHT — the tool receives the actual array
call("rank") {
items = $(context.gather.sources)
}
And the inverse, trying to use $(...) in prompt text, where it doesn't belong:
# WRONG — $(...) is only for call arguments
- Summarize these: $(context.gather.sources)
# RIGHT — serialize the structure into the text with | json
- Summarize these: {{ .context.gather.sources | json }}
Common mistakes
| Mistake | Fix |
|---|---|
"{{ .context.items }}" for an array/object tool arg |
Use $(context.items) — templates only make strings |
$( ... ) inside prompt or prePrompt text |
Use `{{ ... |
Wrapping expect/iterate in $(...) |
They take bare Expr strings: expect="len(context.x) > 0" |
Passing a number as "{{ .params.n }}" to a numeric arg |
Use $(params.n) to keep it an int |
Next steps
- Apply this in PreCalls deep-dive, where argument typing matters most.
- Revisit the namespaces in Data flow: params, vars & context.
Go templates always make strings; $(...) preserves real types. Learn the three expression positions and never hand a tool a stringified array again.