# Data flow: params, vars & context

Because each session runs in its own isolated LLM context, the interesting part of writing a blueprint is **moving data around** — getting a user's input into a session, carrying a fetch result forward, and handing one session's output to the next.

FML gives you exactly three namespaces to do this, available everywhere in templates and expressions: `params`, `vars`, and `context`. This guide explains what each holds, when to reach for it, and how data flows from the blueprint's inputs to its final output.

## Prerequisites

- The [Anatomy of a session](/content/docs/guides/anatomy-of-a-session/index.html) guide — you should understand that each session has its own context.

## The three namespaces

| Namespace | Holds | Written by | Read as (template / Expr) |
| --- | --- | --- | --- |
| `params` | User-supplied inputs | `parameter(...)` declarations | `{{ .params.x }}` / `params.x` |
| `vars` | Globals and PreCall results | `set` and `call(...) -> var` | `{{ .vars.x }}` / `vars.x` |
| `context` | Completed session outputs | sessions finishing (keyed by name or `target`) | `{{ .context.s }}` / `context.s` |

Think of it as **inputs → working memory → results**. `params` come in at the start, `vars` are the scratch space you populate as the blueprint runs, and `context` accumulates the finished output of each session.

## `params` — inputs from the caller

Parameters are declared at the root of the blueprint and supplied by whoever runs it (via the API, SDK, or CLI):

```
parameter("topic",       type=string)
parameter("max_results", type=int, default=5)   # default makes it optional
```

| Argument | Required | Value | Notes |
| --- | --- | --- | --- |
| `type` | yes | `string` / `int` / `bool` | Bare keyword, no quotes |
| `title` | no | quoted string | Human-readable label |
| `default` | no | literal value | Makes the parameter optional at runtime |

Once declared, a parameter is readable in every session:

```
+ Search for recent information about {{ .params.topic }}.
- Summarize the top {{ .params.max_results }} findings.
```

Parameters are read-only. They're the stable inputs the whole blueprint is written against.

## `vars` — globals and fetched data

`vars` is your working memory. Two things land here:

**Globals set with `set`** — blueprint-wide or session-scoped constants:

```
set defaultRegion = "eu-west"     # root level: available everywhere

session("lookup") {
    set threshold = 10            # session level: merged with globals
    - Return records above the {{ .vars.threshold }} threshold in {{ .vars.defaultRegion }}.
    schema { records: string[] }
}
```

**PreCall results routed to a var** — a deterministic fetch, saved for later:

```
call("list_tickets") -> openTickets {
    status = "open"
}
```

After that call, `vars.openTickets` holds the fetched data. You can inject it into a prompt (`{{ json .vars.openTickets }}`), pass it to another tool as a typed value (`$(vars.openTickets)`), or gate a session on it (`expect="vars.openTickets != null"`). See [PreCalls deep-dive](/content/docs/guides/precalls-deep-dive/index.html) for routing details.

## `context` — outputs from other sessions

When a session finishes, its typed output is stored under its name in `context`. A session named `overview` with this schema:

```
session("overview") {
    - Summarize the topic.
    schema {
        summary:   string
        keyPoints: string[]
    }
}
```

publishes `context.overview.summary` and `context.overview.keyPoints`. Any later session can read them.

But **reading `context` does not happen automatically** — a fresh session doesn't see prior output until you either transfer it in or reference it in a directive. There are two ways to bring it into a session:

**1. The `context` directive** injects prior output into the session's LLM context so the model can read it:

```
session("report", after="overview") {
    context "Overview so far:\n{{ .context.overview | json }}"
    - Write a one-paragraph executive summary from the overview.
    schema { summary: string }
}
```

Use `context true` to dump the entire accumulated context as JSON, or a template string (as above) to include just what's relevant. Either form **requires `after`** so the source session has already run.

**2. Expressions in session arguments** reference `context` to sequence or gate work — `expect="len(context.overview.keyPoints) > 0"`, `iterate="context.overview.keyPoints"`. These also require `after`. See [Control the flow of a blueprint](/content/docs/guides/control-plan-flow/index.html).

> **The golden rule for `context`:** if you read `context.*` anywhere in a session — in a `context` directive, `expect`, or `iterate` — that session **must** declare `after` on the session that produced it. Otherwise the value may not exist yet.

## How it flows end to end

Here's the full loop — a parameter comes in, a PreCall stores data in a var, one session produces output into context, and a second session reads all three:

```
parameter("topic", type=string)
set tone = "concise"

session("gather") {
    use search

call("searchDocuments") -> rawDocs {
        query = "{{ .params.topic }}"
    }

+ Search for {{ .params.topic }} using the results already fetched.
    - Extract the key findings.
    schema { findings: string[] }
}

session("write", after="gather") {
    context "Findings:\n{{ json .context.gather.findings }}"
    - Write a {{ .vars.tone }} brief on {{ .params.topic }} from the findings above.
    schema { brief: string }
}
```

- `params.topic` — the caller's input, read in both sessions.
- `vars.rawDocs` / `vars.tone` — fetched data and a global constant.
- `context.gather.findings` — the first session's output, transferred into the second (with `after` set).

## Common mistakes

| Mistake | Fix |
| --- | --- |
| Reading `context.other` without `after="other"` | Always pair a `context` read with `after`. |
| Expecting a session to "just know" earlier output | Nothing crosses sessions implicitly, transfer it with a `context` directive. |
| Passing an array to a tool as "{{ .vars.list }}" | Templates render to strings; use `$(vars.list)` to preserve the type — see [Templates and expressions](/content/docs/guides/templates-vs-expressions/index.html). |
| Trying to reassign a `param` | Parameters are read-only inputs; use `vars` for working state. |

## Next steps

- Understand string-vs-typed values in [Templates and expressions](/content/docs/guides/templates-vs-expressions/index.html).
- Populate `vars` deterministically with the [PreCalls deep-dive](/content/docs/guides/precalls-deep-dive/index.html).
- Sequence and gate sessions in [Control the flow of a blueprint](/content/docs/guides/control-plan-flow/index.html).

How data moves through a blueprint across the three namespaces — caller inputs, working memory, and session outputs — and the rule for reading context safely.
