Data flow: params, vars & context | Diaphora Docs | Diaphora
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 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 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.
The golden rule for
context: if you readcontext.*anywhere in a session — in acontextdirective,expect, oriterate— that session must declareafteron 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 (withafterset).
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. |
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.
- Populate
varsdeterministically with the PreCalls deep-dive. - Sequence and gate sessions in Control the flow of a blueprint.
How data moves through a blueprint across the three namespaces — caller inputs, working memory, and session outputs — and the rule for reading context safely.