# 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](/content/docs/guides/data-flow-in-a-plan/index.html) 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 }}` | Session output serialized to a JSON **string** |
| `{{ .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 `context` string) → `{{ ... }}`. If it's a structure, add `| json`.
- **A `call` argument that needs a real array/object/number** → `$( ... )`.
- **An `expect`/`iterate` condition** → 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 `{{ ... | json }}` in text; `$()` is for `call` args only |
| 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](/content/docs/guides/precalls-deep-dive/index.html), where argument typing matters most.
- Revisit the namespaces in [Data flow: params, vars & context](/content/docs/guides/data-flow-in-a-plan/index.html).

Go templates always make strings; $(...) preserves real types. Learn the three expression positions and never hand a tool a stringified array again.
