Developer Guide: Workflow Variables
This document provides a comprehensive guide to using dynamic variables within the WilmerAI workflow system. It details the available built-in variables, the powerful Jinja2 templating engine, and the simple process for adding custom variables directly from a workflow's JSON configuration.
The system is powered by the $WorkflowVariableManager$, a service that leverages the central *
$ExecutionContext$* to make a wide range of data available for substitution in prompts and tool arguments.
1. Available Variables (Reference Guide)
The following variables are automatically available within any prompt in a workflow.
Custom Workflow Variables
Any top-level key (except for "nodes") in your workflow JSON file is automatically available as a variable.
{my_custom_variable}: The value of the"my_custom_variable"key in the JSON file.
Inter-Node Variables
These variables allow you to pass data between nodes and between parent and child workflows.
{agent<N>Output}: The string result from the Nth node (1-indexed) in the current workflow. For example,{agent1Output}is the result of the first node.{agent<N>Input}: A value passed into a sub-workflow from a parent workflow viascoped_variables. For example,{agent1Input}is the first value passed from the parent.
Conversation History Variables
Multiple formats of the conversation history are provided.
{chat_user_prompt_last_<N>}: The last N turns of the conversation, formatted as a raw string. Supported values for N are1,2,3,4,5,10,20. Example:{chat_user_prompt_last_one}.{templated_user_prompt_last_<N>}: The last N turns of the conversation, formatted with the LLM's specific chat template (e.g., adding[INST]tokens). Supported values for N are the same as above.{chat_user_prompt_n_messages}: The last N turns as a raw string, where N is configured by the node-level propertynMessagesToIncludeInVariable(defaults to 5). This is the preferred approach when the hardcoded counts above do not meet your needs, since any integer value is supported.{templated_user_prompt_n_messages}: Same as above, but formatted with the LLM's chat template. Controlled by the samenMessagesToIncludeInVariableproperty.{chat_user_prompt_estimated_token_limit}: Recent turns as a raw string, selected by estimated token budget rather than message count. The budget is configured by the node-level propertyestimatedTokensToIncludeInVariable(defaults to 2048). Starting from the most recent message and working backwards, messages are included as long as the accumulated estimated token count stays within the budget. At least one message is always included, even if it alone exceeds the budget. Token estimation usesrough_estimate_token_length, which intentionally overestimates by using the higher of a word-based estimate (1.35 tokens/word) and a character-based estimate (3.5 chars/token), then applying a configurablesafety_marginmultiplier (default 1.10).{templated_user_prompt_estimated_token_limit}: Same as above, but formatted with the LLM's chat template. Controlled by the sameestimatedTokensToIncludeInVariableproperty.{chat_user_prompt_min_n_max_tokens}: A combination of the N-messages and token-limit approaches. This variable pulls a minimum number of messages (set byminMessagesInVariable, defaults to 5), then continues adding older messages as long as the accumulated estimated token count stays within the budget (set bymaxEstimatedTokensInVariable, defaults to 2048). The minimum message count takes precedence: if the minimum messages alone exceed the token budget, they are all still included. Beyond the minimum, expansion stops when the next message would push the total past the token limit. This is useful when you want at least N messages of context, but are willing to include more if the conversation messages are short enough to fit within a token budget.{templated_user_prompt_min_n_max_tokens}: Same as above, but formatted with the LLM's chat template. Controlled by the sameminMessagesInVariableandmaxEstimatedTokensInVariableproperties.{system_prompts_as_string}: All system messages from the conversation history, concatenated into a single string.{messages}: The entire conversation history as a list of dictionaries ([{'role': 'user', 'content': '...'}]). This is primarily for use with Jinja2 templating.
Date & Time Variables
A variety of pre-formatted date and time strings are available.
{todays_date_pretty}: Example:August 17, 2025{todays_date_iso}: Example:2025-08-17{YYYY_MM_DD}: Example:2025_08_17(underscore-separated format, useful for filenames){current_time_12h}: Example:7:09 PM{current_time_24h}: Example:19:09{current_month_full}: Example:August{current_day_of_week}: Example:Sunday{current_day_of_month}: Example:17
Contextual Variables
{Discussion_Id}: The unique identifier for the current conversation/discussion. Useful for creating per-conversation files or organizing data by session. If no discussion ID is present, this will be an empty string.{time_context_summary}: A human-readable summary of when the conversation started (e.g., "The user started this conversation a few minutes ago").
Dynamic File Path Variables
The {Discussion_Id} and {YYYY_MM_DD} variables are particularly useful with the GetCustomFile and SaveCustomFile
nodes, which support variable substitution in their filepath fields. This enables per-conversation or date-based file
storage patterns.
Implementation Details:
- The
filepathfield in both nodes is processed throughWorkflowVariableManager.apply_variables()before the file operation is performed. - See
specialized_node_handler.py(handle_get_custom_fileandhandle_save_custom_filemethods) for the implementation.
Example Usage:
{
"type": "SaveCustomFile",
"filepath": "/data/{YYYY_MM_DD}/{Discussion_Id}_output.txt",
"content": "{agent1Output}"
}
2. How to Use Variables
There are two ways to use variables in your prompts: standard formatting and the more powerful Jinja2 templating.
Standard Formatting
By default, you can insert any variable into a prompt using curly braces. The system uses Python's str.format() method
for substitution.
{
"type": "Standard",
"prompt": "The current time is {current_time_12h}. Please respond to this message: {chat_user_prompt_last_one}"
}
Jinja2 Templating (Advanced)
For more complex logic, like loops or conditionals, you can enable the Jinja2 templating engine by adding
"jinja2": true to your node's configuration. This gives you access to the full power of Jinja2 syntax.
This is especially useful with the {messages} variable, which provides the entire conversation history as a list.
Example: A node that summarizes a conversation using a Jinja2 loop.
{
"type": "Standard",
"endpointName": "Ollama-Llama3",
"jinja2": true,
"prompt": "Please summarize the following conversation:\n{% for message in messages %}\n{{ message.role }}: {{ message.content }}\n{% endfor %}"
}
3. How to Add a Custom Variable
Adding a new, reusable variable to a workflow is simple and requires no code changes.
Step 1: Add the Variable to the Workflow Config
Add your new key-value pair to the top level of your workflow's JSON file (e.g., in
Public/Configs/Workflows/my_workflow.json).
{
"persona_details": "You are a witty pirate cartographer from the 17th century.",
"creative_guideline": "Your answers should be imaginative and slightly dramatic.",
"nodes": [
{
"type": "Standard",
"returnToUser": true,
"endpointName": "Ollama-Llama3",
"systemPrompt": "{persona_details} You must follow this rule: {creative_guideline}",
"prompt": "Help the user with their geography question: {chat_user_prompt_last_one}"
}
]
}
Step 2: Use the Variable in Your Prompts
You can now immediately use {persona_details} and {creative_guideline} in any prompt within that workflow. The
system will automatically find and substitute them. No further steps are needed.
4. How It Works (Under the Hood)
The system is designed to make adding custom variables trivial by isolating the change to the JSON configuration file.
$WorkflowManager$Loads the Config: The manager loads the entire workflow JSON file into aworkflow_file_configdictionary.$WorkflowProcessor$Populates the Context: This entire dictionary is passed to the processor, which then places it into theworkflow_configfield of the$ExecutionContext$for each node.$WorkflowVariableManager$Reads All Keys: The variable manager receives the context and has a generic loop that iterates over theworkflow_configdictionary, making each top-level key (except"nodes") available for substitution.
Early Variable Substitution for endpointName and preset
As of recent updates, the endpointName and preset fields support a special form of early variable substitution. This occurs in WorkflowProcessor._process_section() BEFORE the LLM handler is loaded and BEFORE nodes execute.
Technical Implementation:
- The processor creates a minimal
ExecutionContextwith only pre-execution variables - This context includes
agent_inputs(from parent workflows) andworkflow_config(static variables) - It explicitly excludes
agent_outputssince no nodes have executed yet - Variables are applied to
endpointNameandpresetusing this limited context - The substituted values are then used to load the LLM handler
Available Variables for Early Substitution:
{agent#Input}- Passed from parent workflows- Custom static variables from workflow JSON top-level
- Date/time variables
- Conversation history variables
{time_context_summary}
NOT Available:
{agent#Output}- These don't exist until nodes execute- Any variable dependent on node execution results
This design allows nested workflows to pass endpoint configurations while maintaining the architecture where the LLM handler is loaded before node execution.
File: /Middleware/workflows/managers/workflow_variable_manager.py
# In WorkflowVariableManager.generate_variables(...)
# --- Custom top-level variables from workflow JSON ---
if context.workflow_config:
for key, value in context.workflow_config.items():
if key != "nodes": # Exclude the nodes list itself
variables[key] = value