Component Variable Propagation#
Variable propagation is the mechanism by which a canvas component's output values are stored in a shared state bag (CanvasState) and made available to downstream components via a {{cpn_id@variable}} reference syntax. It is the primary data-flow channel across an agent pipeline's DAG.
Reference Syntax#
Variable references take one of four forms :
| Form | Resolves to |
|---|---|
{{cpn_id@param}} | Outputs[cpn_id][param] |
{{cpn_id@param.path}} | Dot-path traversal into Outputs[cpn_id][param] |
{{sys.x}} | Sys["x"] (query, user_id, conversation_turns, files) |
{{env.x}} | Env["x"] (deployment-time constants) |
cpn_id is a DSL-assigned identifier such as begin_0, llm_0, or cpn_0. The iteration aliases {{item}} and {{index}} are also recognized and map to Globals["__item__"] / Globals["__index__"] .
The regex is defined in both the Go runtime and the Python base class — they must stay byte-for-byte identical:
- Go:
VarRefPatternininternal/agent/runtime/template.go - Python:
variable_ref_pattinagent/component/base.py
CanvasState — the central store#
CanvasState (in internal/agent/runtime/state.go) is the per-run shared bag:
Outputs map[string]map[string]any— the primary variable store:Outputs[cpn_id][param_name] = valueSys,Env,Globals— system variables, env constants, and loop/global aliasesHistory,Retrieval— conversation history and aggregated retrieval chunksCancelFlag,RunID,TaskID— run lifecycle metadata
All maps are guarded by a single sync.RWMutex . CanvasState is registered with eino's type registry so it round-trips through checkpoint/resume paths .
Key accessors:
| Method | Purpose |
|---|---|
SetVar(cpnID, param, v) | Write Outputs[cpnID][param] (supports nested dot-paths) |
GetVar(ref) | Resolve any cpn_id@param, sys.x, env.x, item, or index ref |
ReadVars(refs) | Batch-resolve a list of refs in a single locked pass |
RecordOutput(cpnID, bucket, payload) | Store a node result for downstream lookup |
Snapshot() | Return a shallow copy of all Outputs for statePre injection |
How outputs are written#
Python side: each component calls set_output(key, value), which stores {value, type} in self._param.outputs[key]. For example, UserFillUp calls self.set_output(k, resolved) for each form field it processes . After invocation, the canvas persists each component's output() dict back into the shared graph state.
Go side: the eino statePost handler in scheduler.go flattens every key in a node's output map directly into Outputs[cpnID][key] , mirroring the Python convention so {{cpnID@key}} references work identically. Metadata keys (__cpn_id__, state, __legacy_noop__) are skipped.
How references are resolved#
Template resolution (Go): ResolveTemplate(s, state) replaces every {{...}} in a string with its current CanvasState value. Unresolvable refs are loud errors to surface misconfigured canvases early. ResolveTemplateForDisplay is the soft-fail variant (unresolved → ""), mirroring canvas.py's display path.
Python side: get_variable_value(exp) splits cpn_id@var_nm, fetches the component's output(root_key), then calls get_variable_param_value for any dot-path traversal. Traversal handles dict, list/tuple (integer index), JSON strings (auto-parsed once), and object attributes. get_value_with_variable handles mixed strings containing multiple references.
For parameter binding, get_input() on ComponentBase detects whether an input value is a bare reference (is_reff) or a templated string, resolves accordingly, and stores the result via set_input_value.
Lifecycle in the scheduler#
The eino scheduler wires statePre and statePost onto every node as graph options :
statePre— before a node runs, injects a snapshot of all currentOutputsinto the input map under"state", and syncs eino's local state into the context-attachedCanvasState.statePost— after a node runs, writes each output key into both the eino-local state and the contextCanvasStateviaSetVar.
This double-write ensures that components reading state via runtime.GetStateFromContext (e.g. Begin, Message, LLM) always see upstream outputs regardless of which state object they hold.
Key files#
| File | Role |
|---|---|
internal/agent/runtime/state.go | CanvasState struct, GetVar/SetVar/RecordOutput, dotTraverse |
internal/agent/runtime/template.go | VarRefPattern, ResolveTemplate, ResolveTemplateForDisplay |
internal/agent/canvas/scheduler.go | statePre/statePost handlers, BuildWorkflow |
agent/component/base.py | set_output, get_input, variable_ref_patt, get_input_elements_from_text |
agent/canvas.py | get_variable_value, get_variable_param_value, get_value_with_variable, set_variable_value |
agent/component/fillup.py | UserFillUp — example of a component writing multiple named outputs |