# 2038 Workflow Variables Ecdd2861

> A Technical Guide to WilmerAI Workflow Variables

- Skill: `tools-only/2038-workflow-variables-ecdd2861` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add tools-only/2038-workflow-variables-ecdd2861`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tools-only/2038-workflow-variables-ecdd2861/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: tools-only (https://skillmd.com/u/tools-only)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/tools-only/2038-workflow-variables-ecdd2861

---

### A Technical Guide to WilmerAI Workflow Variables

This guide provides a comprehensive and validated reference to all dynamic variables available within the WilmerAI
workflow system. It has been corrected against the system's source code to ensure accuracy and prevent the generation of
invalid workflows.

#### Core Principle: Dynamic Substitution

The **`WorkflowVariableManager`** service is responsible for replacing placeholders in your workflow's string properties
with real-time data. This happens automatically before a node is executed.

-----

### Part 1: How to Use Variables

#### Standard Formatting (`{...}`)

By default, simply place the variable name in curly braces within any valid string property. The system uses Python's
`str.format()` method for substitution.

```json
{
  "title": "Greet User with Time",
  "type": "Standard",
  "systemPrompt": "Today is {todays_date_pretty}. The current time is {current_time_12h}.",
  "prompt": "Please respond to the user's message: {chat_user_prompt_last_one}"
}
```

#### Jinja2 Templating (Advanced)

For advanced logic like loops or conditionals, add **`"jinja2": true`** to the node's configuration. This allows you to
use the full Jinja2 syntax.

```json
{
  "title": "Render Conversation History",
  "type": "Standard",
  "jinja2": true,
  "prompt": "Here is the conversation so far:\n\n{% for message in messages %}{{ message.role | capitalize }}: {{ message.content }}\n{% endfor %}\n\nHow can I help you now?"
}
```

#### ⚠️ Critical Limitation: Configuration vs. Content

Variable substitution is performed by node handlers on specific fields, not by the core workflow engine. This creates a
critical distinction between what can and cannot be a variable.

**The principle is: Configuration keys are static, content keys can be dynamic.**

##### ✅ Fields that SUPPORT variables (Content)

You **CAN** use variables in fields that are treated as content for the node to process.

* `prompt`
* `systemPrompt`
* `title`
* `promptToSearch` (and similar input fields on specialized nodes)
* `filepath` (in `GetCustomFile` and `SaveCustomFile`, the handlers process variables for this field)

##### ⚠️ Fields with SPECIAL variable support (Early Substitution)

**`endpointName`** and **`preset`** now support a LIMITED form of variable substitution. These fields use **early variable substitution**, which means they are processed BEFORE nodes execute. This creates important limitations:

**ONLY these variables work in `endpointName` and `preset`:**
* `{agent#Input}` - Values passed from parent workflows via `scoped_variables`
* `{custom_variable}` - Static variables defined at the top level of your workflow JSON
* Date/time variables like `{todays_date_pretty}`, `{current_time_12h}`, etc.
* `{chat_user_prompt_last_one}` and other conversation history variables
* `{time_context_summary}` (if a discussionId is present)

**These variables DO NOT work in `endpointName` and `preset`:**
* ❌ `{agent#Output}` - These don't exist yet since nodes haven't executed!
* ❌ Any variable that depends on the output of another node

**Example of CORRECT usage:**
```json
{
  "coding_endpoint": "Creative-Fast-Endpoint",
  "nodes": [{
    "type": "Standard",
    "endpointName": "{coding_endpoint}",
    "preset": "{agent1Input}"
  }]
}
```

**Example of INCORRECT usage:**
```json
{
  "nodes": [{
    "type": "Standard",
    "endpointName": "{agent1Output}",  // ❌ WILL FAIL - agent1Output doesn't exist yet!
    "prompt": "..."
  }]
}
```

##### ❌ Fields that DO NOT SUPPORT variables (Configuration)

You **CANNOT** use variables in fields that define a node's configuration. These fields are read by the workflow engine
*before* any processing occurs. **Always use hardcoded, static string values for them.**

* `type`
* `returnToUser` (This is a boolean `true`/`false`, not a string).
* `workflowName`
* Keys within a `conditionalWorkflows` object.
* `jinja2` (This is a boolean `true`/`false`).

#### Adding Custom Variables (The Correct Way)

You can add your own reusable variables by adding a new key-value pair to the top level of your workflow JSON file. Any
key that is not `"nodes"` will automatically become an available variable for use in **content fields**.

**Correct Example `my_workflow.json`:**

```json
{
  "shared_persona": "You are a witty AI assistant who loves puns.",
  "nodes": [
    {
      "title": "Respond to User",
      "type": "Standard",
      "endpointName": "Creative-Fast-Endpoint",
      "systemPrompt": "{shared_persona}",
      "prompt": "{chat_user_prompt_last_one}",
      "returnToUser": true
    }
  ]
}
```

-----

### Part 2: Complete Variable & Placeholder Reference

This is an exhaustive list of all available variables, validated against `workflow_variable_manager.py`.

#### Custom Workflow Variables

* **`{custom_variable}`**: The value of any top-level key in the workflow's JSON file (except for `"nodes"`).

#### Data Flow Variables

* **`{agent#Output}`**: The string result from a previous node in the **same** workflow. The `#` corresponds to the
  node's position (1-indexed). For example, `{agent1Output}` is the result of the first node.
* **`{agent#Input}`**: A value passed from a parent workflow into a child workflow via the `scoped_variables` property
  of a `CustomWorkflow` node. The `#` corresponds to the value's position in the `scoped_variables` list (1-indexed).

#### Conversation History Variables

* **`{chat_user_prompt_last_one}`**: The raw text of the last message in the conversation.
* **`{chat_user_prompt_last_two}`**: Raw text of the last 2 turns.
* **`{chat_user_prompt_last_three}`**: Raw text of the last 3 turns.
* **`{chat_user_prompt_last_four}`**: Raw text of the last 4 turns.
* **`{chat_user_prompt_last_five}`**: Raw text of the last 5 turns.
* **`{chat_user_prompt_last_ten}`**: Raw text of the last 10 turns.
* **`{chat_user_prompt_last_twenty}`**: Raw text of the last 20 turns.
* **`{chat_user_prompt_n_messages}`**: Raw text of the last N messages, where N is set by the node property
  `nMessagesToIncludeInVariable` (defaults to 5 if not specified). This is the preferred approach for custom message
  counts, as it allows any value of N without requiring a new hardcoded variable.
* **`{templated_user_prompt_n_messages}`**: Same as `{chat_user_prompt_n_messages}`, but formatted with the LLM's
  chat template. Controlled by the same `nMessagesToIncludeInVariable` property.
* **`{chat_user_prompt_estimated_token_limit}`**: Recent messages as a raw string, selected by estimated token budget
  instead of message count. The budget is set by the node property `estimatedTokensToIncludeInVariable` (defaults to
  2048). Starting from the most recent message and working backwards, messages are accumulated until the estimated token
  count would exceed the budget. At least one message is always included, even if it alone exceeds the limit. This is
  useful when you want to fill a prompt with as much conversation context as will fit, without knowing how many messages
  that corresponds to. Note: token counts are estimated (not exact) using a heuristic that intentionally overestimates.
* **`{templated_user_prompt_estimated_token_limit}`**: Same as `{chat_user_prompt_estimated_token_limit}`, but formatted
  with the LLM's chat template. Controlled by the same `estimatedTokensToIncludeInVariable` property.
* **`{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 by the node property `minMessagesInVariable`, defaults to 5), then
  continues adding older messages as long as the accumulated estimated token count stays within the budget (set by the
  node property `maxEstimatedTokensInVariable`, defaults to 2048). The minimum message count always takes precedence:
  even if the minimum messages exceed the token budget, they are all 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 messages are short enough to fit within a token budget. For example,
  with `"minMessagesInVariable": 5` and `"maxEstimatedTokensInVariable": 5000`, you always get at least 5 messages. If
  those 5 messages only total 2000 estimated tokens, additional older messages are included until the 5000-token budget
  is reached (without exceeding it).
* **`{templated_user_prompt_min_n_max_tokens}`**: Same as `{chat_user_prompt_min_n_max_tokens}`, but formatted with
  the LLM's chat template. Controlled by the same `minMessagesInVariable` and `maxEstimatedTokensInVariable` properties.
* **`{templated_user_prompt_last_one}`**: The last message, formatted with the LLM's chat template (e.g.,
  `[INST]...[/INST]`).
* **`{templated_user_prompt_last_two}`**: Last 2 turns, templated.
* **`{templated_user_prompt_last_three}`**: Last 3 turns, templated.
* **`{templated_user_prompt_last_four}`**: Last 4 turns, templated.
* **`{templated_user_prompt_last_five}`**: Last 5 turns, templated.
* **`{templated_user_prompt_last_ten}`**: Last 10 turns, templated.
* **`{templated_user_prompt_last_twenty}`**: Last 20 turns, templated.
* **`{chat_system_prompt}`**: The system prompt sent from the front-end client.
* **`{system_prompts_as_string}`**: All system messages from the conversation history concatenated into a single string.
* **`{messages}`**: The entire conversation history as a raw list of dictionaries (e.g.,
  `[{'role': 'user', 'content': '...'}]`). **Note**: This is available for both standard formatting and Jinja2, but it
  is most useful with Jinja2 for iterating over the conversation history.

#### Date & Time Variables

* **`{todays_date_pretty}`**: e.g., "August 30, 2025"
* **`{todays_date_iso}`**: e.g., "2025-08-30"
* **`{YYYY_MM_DD}`**: e.g., "2025_08_30" (underscore-separated format, useful for filenames)
* **`{current_time_12h}`**: e.g., "08:00 PM"
* **`{current_time_24h}`**: e.g., "20:00"
* **`{current_month_full}`**: e.g., "August"
* **`{current_day_of_week}`**: e.g., "Saturday"
* **`{current_day_of_month}`**: e.g., "30"

#### Context & Memory Variables

* **`{Discussion_Id}`**: The unique identifier for the current conversation/discussion. This is 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 natural language summary of the conversation's timeline (e.g., "The user started this
  conversation a few minutes ago").

##### Using `{Discussion_Id}` and `{YYYY_MM_DD}` for Dynamic File Paths

These variables are particularly useful with the `GetCustomFile` and `SaveCustomFile` nodes, which support variable
substitution in their `filepath` fields. This allows you to create per-conversation or date-based file storage.

**Example: Per-Conversation Notes**

```json
{
  "type": "GetCustomFile",
  "filepath": "/data/sessions/{Discussion_Id}_notes.txt"
}
```

**Example: Daily Logs**

```json
{
  "type": "SaveCustomFile",
  "filepath": "/data/logs/{YYYY_MM_DD}_report.txt",
  "content": "{agent1Output}"
}
```

**Example: Combined Session and Date**

```json
{
  "type": "SaveCustomFile",
  "filepath": "/data/{YYYY_MM_DD}/{Discussion_Id}_output.txt",
  "content": "Generated at {current_time_12h}:\n\n{agent1Output}"
}
```

See the `GetCustomFile` and `SaveCustomFile` node documentation for more details.
* **`{current_chat_summary}`**: **⚠️ UNAVAILABLE VARIABLE:** The helper function `generate_chat_summary_variables` that
  populates this is **not called** by the main variable generation logic. **Do not use `{current_chat_summary}`** as it
  will not be substituted. To get the summary, you must use a dedicated node like `GetCurrentSummaryFromFile`.

#### Special Placeholders (Context-Specific)

These are not standard variables but are special keywords replaced within specific node types or sub-workflows. They do
**not** use curly braces. Their processing logic is not present in the core `WorkflowVariableManager` and is handled by
specialized workflows (e.g., those called by the `QualityMemory` node).

* **`[TextChunk]`**: Represents a block of text to be processed into a memory.
* **`[IMAGE_BLOCK]`**: Represents the AI-generated description of an image within an `ImageProcessor` context.
* **`[Memory_file]`**, **`[Full_Memory_file]`**, **`[Chat_Summary]`**: Represent various memory files for file-based
  memory generation.
* **`[LATEST_MEMORIES]`**, **`[CHAT_SUMMARY]`**: Used specifically by the `chatSummarySummarizer` node type.
