Skip to content

Variables and Namespaces

theirish81 edited this page Sep 3, 2026 · 1 revision

Variables and Namespaces

FML enforces strict isolation across its variable system. Instead of maintaining a single global state dictionary, FML divides data into four isolated namespaces, each with distinct lifecycles, access rules, and scoping behaviors.


The Four Namespaces

Namespace Origin / Declaration Go Template Access ({{ ... }}) Antonmedv Expr Access ($( ... ) / Attrs) Lifecycle
params Defined via parameter(...) {{ .params.name }} params.name Immutable throughout plan execution
vars Defined via set or PreCall -> vars:name {{ .vars.name }} vars.name Mutable; supports global & session shadowing
context Generated by completed session outputs {{ .context.session_name }} context.session_name Appended as sessions complete
it Current item in an iterate session {{ .it }} or {{ .it.field }} it or it.field Scoped strictly to current loop iteration

1. The params Namespace

The params namespace contains all read-only values supplied to the plan at runtime.

Declaration

parameter("api_endpoint", type="string", default="https://api.example.com")
parameter("concurrency", type="int", default=5)
parameter("dry_run", type="bool", default=false)

Access Examples

  • Inside a Go Template (Prompt or String Call Arg):
    + Sending requests to {{ .params.api_endpoint }} with concurrency {{ .params.concurrency }}.
    
  • Inside an Antonmedv Expression (Wrapped Call Arg or Condition):
    call("batch_processor") {
        max_threads = $(params.concurrency)
    }
    
    session("execute", expect="!params.dry_run") {
        # Runs only if dry_run is false
    }
    

Note

Values in params cannot be mutated or shadowed. They remain constant for the entire duration of the plan.


2. The vars Namespace

The vars namespace stores intermediate values, computation results, and static variables created by the developer.

Populating vars

A. Global set Statements

set max_retries = 3
set user_agent = "FML-Bot/1.0"

B. PreCall Output Routing

# Explicit vars routing
call("query_user") -> vars:user_profile {
    user_id = "{{ .params.user_id }}"
}

# Implicit routing (omitting namespace prefix defaults to vars)
call("query_settings") -> app_settings {
    env = "production"
}

Accessing vars

session("send_notification") {
    # In template
    + Notify user {{ .vars.user_profile.email }} using agent {{ .vars.user_agent }}.

    # In wrapped expr
    call("deliver_email") {
        recipient = "{{ .vars.user_profile.email }}"
        retry_limit = $(vars.max_retries)
    }
}

3. The context Namespace

The context namespace acts as the primary bus for passing validated artifacts between sessions.

Publication to context

When a session completes successfully, its validated schema output is published under its session name:

session("gather_metrics") {
    - Format metrics.
    schema {
        cpu_load: float
        memory_mb: int
    }
}
# Result published to: context.gather_metrics = { cpu_load: 0.72, memory_mb: 4096 }

Custom Key via target

If a session declares target="custom_key", the output is saved under context.custom_key instead of its session name:

session("fetch_user_records_v3", target="users") {
    - Format user list.
    schema string[]
}
# Result published to: context.users = ["alice", "bob"]

Accessing context Downstream

session("analyze", after="gather_metrics") {
    # Full context JSON serialization
    context "System Metrics: {{ .context.gather_metrics | json }}"

    # Specific property interpolation
    + Analyzing CPU load: {{ .context.gather_metrics.cpu_load }}

    # In conditional expressions
    expect = "context.gather_metrics.cpu_load > 0.5"
}

4. The it Namespace (Iteration Scoping)

When a session defines iterate=array_expr, the runtime loops over the array, executing the session once per element. During each execution, the current element is placed into the it namespace.

session("process_items", after="fetch_items", iterate=context.fetch_items.records) {
    # In template
    + Processing item ID: {{ .it.id }} (Name: {{ .it.name }})
    - Generate summary for {{ .it.name }}.

    schema {
        item_id: string
        status: string
    }
}
  • If iterating over an array of primitives (["apple", "banana"]), access the value directly with {{ .it }}.
  • If iterating over an array of objects ([{ "id": 1 }, { "id": 2 }]), access properties using dot notation: {{ .it.id }} or it.id.

Variable Scoping & Shadowing Rules

FML implements lexical scoping with shadowing for the vars namespace:

graph TD
    subgraph Global["Global Scope"]
        GV["set retries = 3<br/>(vars.retries = 3)"]
    end

    subgraph S1["Session: download_file"]
        GV --> S1V["set retries = 5<br/>(vars.retries shadowed to 5)"]
    end

    subgraph S2["Session: notify_user"]
        GV --> S2V["vars.retries is 3<br/>(Global value unaffected)"]
    end
Loading

Shadowing Behavior

  1. Local Declarations: A set statement or PreCall output routing (-> vars:name) inside a session creates or modifies a variable exclusively within that session's scope.
  2. Non-Destructive Mutation: Modifying or shadowing a variable locally never mutates the global variable in other sessions.
  3. Session Isolation: Session A cannot read or modify the local vars of Session B. The only way to share data between sessions is through the validated context namespace.

Target Routing Syntax: Colons vs. Dots

When routing tool or PreCall outputs to namespaces, FML requires the colon (:) separator:

# CORRECT:
call("get_data") -> vars:my_var { ... }
call("get_data") -> context:my_key { ... }
call("get_data") -> my_var { ... } # Defaults to vars:my_var

# INCORRECT (Syntax Error):
call("get_data") -> vars.my_var { ... }   # INVALID: Do not use dots!
call("get_data") -> context.my_key { ... } # INVALID: Do not use dots!

Caution

Always use a colon (:) to separate the namespace prefix from the identifier name (-> vars:identifier). Using a period will cause a syntax parse failure.


Common Pitfalls & Checklist

Common Mistake Error Symptoms Solution
Missing dot in Go Template {{ params.id }} outputs empty or causes error Add leading dot: {{ .params.id }}
Adding dot in Antonmedv Expr $(.vars.id) causes parse failure Remove leading dot: $(vars.id)
Dot in call target routing -> vars.output causes syntax error Use colon: -> vars:output
Reading unpopulated local call in context context "Data: {{ .vars.local }}" is empty Move variable reference to + or - prompt
Accessing context.foo without after="foo" Execution failure or null reference Add after="foo" attribute to session

Clone this wiki locally