Pattern: Variables Manager¶
Motivation¶
In multi-step agent workflows, intermediate results often need to be referenced across different execution stages. When an agent performs data analysis, API calls, or code execution, it generates values that subsequent steps depend on. The challenge is: how do you track these variables efficiently without overwhelming the LLM's context window with full data dumps?
Consider a data analysis agent that:
- Fetches user data from an API (returns 10,000 records)
- Filters the data based on criteria (produces 500 records)
- Computes statistics on the filtered data
- Generates a visualization
- Creates a summary report
At each step, the agent needs to know what variables exist and their characteristics, but including the full 10,000-record dataset in every prompt would be wasteful and expensive. The agent needs metadata about variables without the full values consuming context tokens.
Pattern Overview¶
Problem¶
In multi-step agent workflows, intermediate results often need to be referenced across different execution stages. When an agent performs data analysis, API calls, or code execution, it generates values that subsequent steps depend on. LLMs have finite context windows, and including large intermediate values in every prompt is expensive and wasteful. At each step, the agent needs to know what variables exist and their characteristics, but including full data dumps (like 10,000-record datasets) in every prompt would be wasteful and expensive. Agents need metadata about variables without the full values consuming context tokens.
Solution¶
The Variables Manager Pattern maintains a centralized registry of execution variables with rich metadata while providing context-efficient summaries. Instead of passing full values through context, agents work with variable references and retrieve full values only when needed. This pattern enables agents to maintain awareness of execution state through lightweight metadata summaries, retrieving full values only when explicitly needed.
This separation of metadata from values dramatically improves context efficiency while maintaining full observability. The pattern is particularly valuable when agents create intermediate values that need to be tracked across multiple execution steps, especially when those values are large and would consume significant context tokens if included directly. By providing summaries that show what exists without exposing full values, using minimal tokens, the pattern enables efficient state management across complex multi-step workflows.
Key Concepts¶
- Metadata-Rich Tracking: Store variables with type, description, creation time, and item counts without requiring full values in context
- Context Efficiency: Provide summaries that show what exists without exposing full values, using minimal tokens
- Singleton Architecture: Single source of truth across the entire agent execution, ensuring consistent state
- Observability: Comprehensive logging for debugging and auditing execution flows
- Lifecycle Management: Smart cleanup strategies to manage memory efficiently over long executions
How It Works¶
The Variables Manager Pattern operates through a structured process:
- Variable Registration: When an agent creates a value (from code execution, API calls, or data processing), it registers the variable with the manager, providing an optional name and description.
- Metadata Extraction: The manager automatically extracts metadata: type detection, item counts for collections, creation timestamps, and generates value previews.
- Summary Generation: When agents need to understand available data, they receive lightweight summaries showing variable names, types, descriptions, and counts—not full values.
- On-Demand Retrieval: Only when an agent explicitly needs to operate on data does it retrieve the full value through dedicated tool calls.
- Lifecycle Management: The manager provides reset strategies (full reset, keep-last-n) to manage memory over long executions.
When to Use This Pattern¶
✅ Use this pattern when:¶
- Large intermediate values: Values are large (>1KB) and would consume significant context tokens
- Multi-step workflows: Values are created in one step and used several steps later
- Code execution agents: Agents that write and execute code, creating variables in a programming environment
- Multi-agent coordination: Multiple agents need access to shared state
- Observability required: You need visibility into state evolution for debugging
- Repeated value access: Values might be reused multiple times across different prompts
❌ Avoid this pattern when:¶
- Small, immediate values: Values are small (<100 characters) and used immediately in the next step
- Ephemeral data: Values are temporary and don't need tracking
- Simple workflows: Overhead of variable management outweighs benefits
- Single-step operations: Values are created and consumed in the same operation
Decision Guidelines¶
Use Variable Manager when the benefits of context efficiency and observability justify the added complexity. Consider value size: large values (>1KB) benefit from metadata-only summaries. Consider workflow length: multi-step workflows where values persist across steps benefit from tracking. Consider reuse: values accessed multiple times benefit from centralized management. However, if values are small, immediate, and ephemeral, direct state passing is simpler.
Practical Applications & Use Cases¶
The Variable Manager Pattern excels in scenarios requiring multi-step data transformations and state tracking:
Data Analysis Workflows¶
Scenario: An agent analyzes sales data through multiple transformation steps.
Challenge: Each step produces intermediate datasets. Without variable management, either all datasets stay in context (expensive, hits limits) or datasets are lost between steps (requires re-computation).
Solution: Variable Manager tracks each transformation result with metadata. The agent sees:
raw_sales: 50,000 recordsfiltered_sales: 12,000 recordsmonthly_aggregates: 36 recordstrend_analysis: dict with 5 keys
The agent can plan next steps knowing what data exists and its scale, without loading any full datasets into context.
Code Execution Agents¶
Scenario: An agent writes and executes Python code to solve data problems.
Challenge: Executed code produces variables in the Python namespace. The agent needs to know what variables exist to write subsequent code that references them.
Solution: After each code execution, the Variable Manager captures all created variables. The agent sees what's available in the namespace and can write code like:
API Integration Chains¶
Scenario: An agent orchestrates multiple API calls where responses feed into subsequent requests.
Challenge: API responses can be large JSON structures. Passing them through context for later use consumes tokens and obscures the execution flow.
Solution: Store each API response as a variable with a description of its purpose. The agent's context contains metadata summaries, and full values are retrieved only when needed for the next API call.
Multi-Agent Systems¶
Scenario: Multiple specialized agents collaborate on a complex task, each producing intermediate results others need.
Challenge: Agents must coordinate without tightly coupling their implementations or maintaining complex message passing.
Solution: A shared Variable Manager acts as a communication bus. Agent A stores its results as variables with descriptive names. Agent B queries available variables and retrieves what it needs. The system maintains loose coupling while enabling coordination.
Implementation¶
Basic Example
The Variable Manager consists of two main components: VariableMetadata and VariablesManager. Here's the complete, self-contained implementation:
from datetime import datetime
from typing import Any, Dict, List, Optional
class VariableMetadata:
"""Encapsulates all metadata for a single variable."""
def __init__(self, value: Any, description: Optional[str] = None,
created_at: Optional[datetime] = None):
self.value = value
self.description = description or ""
self.type = type(value).__name__
self.created_at = created_at if created_at is not None else datetime.now()
self.count_items = self._calculate_count(value)
def _calculate_count(self, value: Any) -> int:
"""Calculate the count of items in the value based on its type."""
if isinstance(value, (list, tuple, set)):
return len(value)
elif isinstance(value, dict):
return len(value)
elif isinstance(value, str):
return len(value)
elif hasattr(value, '__len__'):
try:
return len(value)
except Exception:
return 1
else:
return 1
class VariablesManager(object):
"""Singleton manager for tracking execution variables with metadata."""
_instance = None
variables: Dict[str, VariableMetadata] = {}
variable_counter: int = 0
_creation_order: list = []
def __new__(cls, *args, **kwargs):
"""Singleton pattern: ensure only one instance exists."""
if not cls._instance:
cls._instance = super(VariablesManager, cls).__new__(cls)
return cls._instance
def add_variable(self, value: Any, name: Optional[str] = None,
description: Optional[str] = None) -> str:
"""Add a new variable with an optional name or auto-generated name."""
if name is None:
self.variable_counter += 1
name = f"variable_{self.variable_counter}"
self.variables[name] = VariableMetadata(value, description)
# Track creation order
if name not in self._creation_order:
self._creation_order.append(name)
return name
def get_variable(self, name: str) -> Any:
"""Get a variable value by name."""
metadata = self.variables.get(name)
return metadata.value if metadata else None
def get_variable_metadata(self, name: str) -> Optional[VariableMetadata]:
"""Get complete metadata for a variable by name."""
return self.variables.get(name)
def get_variable_names(self) -> List[str]:
"""Get all variable names."""
return list(self.variables.keys())
def get_last_n_variable_names(self, n: int) -> List[str]:
"""Get the names of the last n created variables."""
if n <= 0:
return []
return self._creation_order[-n:] if len(self._creation_order) >= n else self._creation_order[:]
def get_variables_summary(self, variable_names: Optional[List[str]] = None,
last_n: Optional[int] = None) -> str:
"""Generate a markdown summary of variables."""
if last_n:
variable_names = self.get_last_n_variable_names(last_n)
elif variable_names is None:
variable_names = list(self.variables.keys())
if last_n:
summary_lines = [f"# Last {last_n} Variables Summary\n"]
else:
summary_lines = [f"# Variables Summary\n"]
for name in variable_names:
if name not in self.variables:
continue
metadata = self.variables[name]
preview = self._get_value_preview(metadata.value, max_length=5000)
summary_lines.append(f"## {name}")
summary_lines.append(f"- Type: {metadata.type}")
summary_lines.append(f"- Items: {metadata.count_items}")
summary_lines.append(f"- Description: {metadata.description}")
summary_lines.append(f"- Created: {metadata.created_at.strftime('%Y-%m-%d %H:%M:%S')}")
summary_lines.append(f"- Value Preview: {preview}\n")
return "\n".join(summary_lines)
def _get_value_preview(self, value: Any, max_length: int = 5000) -> str:
"""Get a structured preview of the value, truncating when large."""
try:
full_repr = repr(value)
if len(full_repr) <= max_length:
return full_repr
except Exception:
pass
# Smart truncation for large values
if isinstance(value, (list, tuple)):
if len(value) <= 10:
return repr(value)
preview = repr(value[:10])
return preview[:-1] + f", ... (+{len(value) - 10} more)]"
if isinstance(value, dict):
if len(value) <= 5:
return repr(value)
items = list(value.items())[:5]
preview = "{" + ", ".join(f"{k!r}: {v!r}" for k, v in items) + ", ...}"
return preview
# For strings and other types
str_repr = str(value)
if len(str_repr) <= max_length:
return str_repr
return str_repr[:max_length] + "..."
def reset(self) -> None:
"""Reset the variables manager, clearing all variables."""
self.variables = {}
self.variable_counter = 0
self._creation_order = []
def reset_keep_last_n(self, n: int) -> None:
"""Reset the variables manager, keeping only the last 'n' added variables."""
if n <= 0:
self.reset()
return
variables_to_keep = {}
original_creation_order = []
max_variable_counter = 0
# Identify the last 'n' variables
names_to_keep = self._creation_order[-n:]
for name in names_to_keep:
if name in self.variables:
variables_to_keep[name] = self.variables[name]
original_creation_order.append(name)
# Update counter to avoid collisions
if name.startswith("variable_") and name[9:].isdigit():
max_variable_counter = max(max_variable_counter, int(name[9:]))
# Perform the reset
self.variables = {}
self.variable_counter = 0
self._creation_order = []
# Re-add the kept variables
for name in original_creation_order:
metadata = variables_to_keep[name]
self.variables[name] = VariableMetadata(
metadata.value,
description=metadata.description,
created_at=metadata.created_at
)
self._creation_order.append(name)
# Set counter to prevent collisions
self.variable_counter = max_variable_counter
Key Design Decisions: - Automatic Type Detection: No manual type specification needed - Flexible Counting: Works with any iterable type, falls back gracefully - Timestamp Tracking: Automatic creation time capture - Singleton Architecture: Single source of truth across the entire agent execution - Smart Counter Management: Prevents collisions between auto-generated and explicit names - Update Semantics: Re-adding with the same name updates the variable - Creation Order Tracking: Maintains chronological order for last-n queries
Usage Examples¶
Adding Variables
from variables_manager import VariablesManager
vm = VariablesManager()
# Automatic naming
var_name = vm.add_variable([1, 2, 3, 4, 5],
description="Sample data for analysis")
print(var_name) # Returns: "variable_1"
# Explicit naming
var_name = vm.add_variable({"key": "value"},
name="config_data",
description="Configuration loaded from file")
print(var_name) # Returns: "config_data"
# Update existing variable
vm.add_variable([1, 2, 3, 4, 5, 6],
name="variable_1",
description="Updated sample data")
# Overwrites variable_1
Retrieving Variables
from variables_manager import VariablesManager
vm = VariablesManager()
# Get the full value
data = vm.get_variable("variable_1")
print(data) # Returns: [1, 2, 3, 4, 5, 6]
# Get metadata
metadata = vm.get_variable_metadata("variable_1")
print(f"Type: {metadata.type}, Items: {metadata.count_items}")
# Get all variable names
names = vm.get_variable_names()
print(names) # Returns: ['variable_1', 'config_data', 'variable_2', ...]
# Get last N variable names (most recent)
recent = vm.get_last_n_variable_names(3)
print(recent) # Returns: ['variable_7', 'variable_8', 'variable_9']
Summary Generation
The most important feature for context efficiency is summary generation:
from variables_manager import VariablesManager
vm = VariablesManager()
# Add some example variables
vm.add_variable(
[{'id': i, 'name': f'User{i}', 'state': 'CA'} for i in range(500)],
name="data_filtered",
description="Customer records meeting filter criteria"
)
vm.add_variable(
{'mean': 156.4, 'median': 150.0, 'std': 12.3, 'min': 100, 'max': 200, 'count': 500},
name="statistics",
description="Summary statistics computed from filtered data"
)
# Get summary of all variables
summary = vm.get_variables_summary()
print(summary)
# Get summary of specific variables
summary = vm.get_variables_summary(
variable_names=["data_filtered", "statistics"]
)
print(summary)
# Get summary of last N variables (most useful for agents)
summary = vm.get_variables_summary(last_n=5)
print(summary)
Example Output:
# Last 5 Variables Summary
## data_filtered
- Type: list
- Items: 500
- Description: Customer records meeting filter criteria
- Created: 2024-12-02 10:23:42
- Value Preview: [{'id': 0, 'name': 'User0', 'state': 'CA'}, ... (+490 more)]
## statistics
- Type: dict
- Items: 6
- Description: Summary statistics computed from filtered data
- Created: 2024-12-02 10:24:15
- Value Preview: {'mean': 156.4, 'median': 150.0, ...}
This allows agents to see the "shape" of data without consuming excessive tokens.
Lifecycle Management
Managing the variable landscape over long executions is crucial:
from variables_manager import VariablesManager
vm = VariablesManager()
# Complete reset - useful when starting a completely new task
vm.reset() # Clear all variables, reset counter
# Selective retention - keep only the most recent variables
# Agent has created 20 variables during analysis
# Keep only the 5 most recent (likely most relevant)
vm.reset_keep_last_n(5)
# Now only the last 5 variables remain
Use Case: In long-running analyses, the agent might accumulate many intermediate variables. Periodically calling reset_keep_last_n(10) maintains a sliding window of recent variables, preventing unlimited memory growth while keeping relevant state.
Integration with Agent Tool Systems¶
The Variable Manager works best when integrated with agent tools. Here are complete, self-contained examples:
LangChain Integration
from typing import Optional, Any
from langchain.tools import tool
from variables_manager import VariablesManager
@tool
def list_available_variables(last_n: Optional[int] = None) -> str:
"""
Get a summary of available variables with metadata.
Args:
last_n: If provided, only show the last N variables created
Returns:
Markdown-formatted summary of variables
"""
vm = VariablesManager()
return vm.get_variables_summary(last_n=last_n)
@tool
def get_variable_value(variable_name: str) -> Any:
"""
Retrieve the full value of a variable by name.
Args:
variable_name: The name of the variable to retrieve
Returns:
The variable's value, or None if not found
"""
vm = VariablesManager()
return vm.get_variable(variable_name)
@tool
def execute_python_code(code: str) -> dict:
"""
Execute Python code and capture created variables.
Args:
code: Python code to execute
Returns:
Dictionary with status, created variables, and summary
"""
vm = VariablesManager()
# Create execution namespace
namespace = {}
# Execute the code
try:
exec(code, namespace)
status = "success"
except Exception as e:
status = f"error: {str(e)}"
# Capture all created variables
variables_created = []
for name, value in namespace.items():
if not name.startswith('_') and name not in ['__builtins__', '__name__', '__doc__']:
vm.add_variable(
value,
name=name,
description=f"Created by code execution"
)
variables_created.append(name)
# Return summary
return {
"status": status,
"variables_created": variables_created,
"summary": vm.get_variables_summary(last_n=5)
}
# Example usage with an agent
from langchain.agents import initialize_agent, AgentType
from langchain.llms import OpenAI
# tools = [list_available_variables, get_variable_value, execute_python_code]
# agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
Custom Tool Framework Integration
from typing import Optional, Any, Dict
from variables_manager import VariablesManager
class VariableManagerTools:
"""Wrapper class for variable manager tools in custom frameworks."""
def __init__(self):
self.vm = VariablesManager()
def list_variables(self, last_n: Optional[int] = None) -> str:
"""Get a summary of available variables."""
return self.vm.get_variables_summary(last_n=last_n)
def get_variable(self, name: str) -> Any:
"""Get a variable value by name."""
return self.vm.get_variable(name)
def execute_code(self, code: str) -> Dict[str, Any]:
"""Execute Python code and capture variables."""
namespace = {}
try:
exec(code, namespace)
status = "success"
except Exception as e:
status = f"error: {str(e)}"
variables_created = []
for name, value in namespace.items():
if not name.startswith('_') and name not in ['__builtins__', '__name__', '__doc__']:
self.vm.add_variable(value, name=name, description="Created by code execution")
variables_created.append(name)
return {
"status": status,
"variables_created": variables_created,
"summary": self.vm.get_variables_summary(last_n=5)
}
# Usage example
tools = VariableManagerTools()
# Agent can call:
summary = tools.list_variables(last_n=5)
value = tools.get_variable("data_filtered")
result = tools.execute_code("x = [1, 2, 3, 4, 5]\ny = sum(x)")
This integration allows agents to discover, inspect, and use variables naturally through their tool-calling mechanisms.
Key Takeaways¶
- Context Efficiency: The Variable Manager Pattern separates metadata from values, enabling agents to maintain awareness of execution state through lightweight summaries rather than expensive full-value inclusion.
- Singleton Architecture: A single instance ensures consistent state across the entire agent execution, preventing synchronization issues.
- On-Demand Retrieval: Full values are retrieved only when explicitly needed, not included in every prompt.
- Lifecycle Management: Smart reset strategies (full reset, keep-last-n) manage memory efficiently over long executions.
- Tool Integration: Variable Manager works best when integrated with agent tools, allowing natural discovery and retrieval through tool calls.
- When to Use: Apply this pattern for multi-step workflows with large intermediate values, code execution agents, and multi-agent systems requiring shared state.
Related Patterns¶
This pattern works well with:
- Code Execution Agents - Variable Manager is essential for tracking variables created during code execution
- Multi-Agent Systems - Shared Variable Manager enables loose coupling and coordination between agents
- Memory Management - Variable Manager complements session state and external memory by managing execution-level state
- Tool Use - Variable Manager integrates naturally with agent tools for discovery and retrieval
This pattern differs from:
- Session State (Memory Management) - Session state manages conversation-level context; Variable Manager tracks execution-level computation results
- Filesystem as Context / RAG - Filesystem as Context provides semantic search over documents; Variable Manager provides exact-name lookup for execution variables
- Persistent Task Lists (Recitation) - Recitation tracks tasks; Variable Manager tracks data values
References
- LangChain State Management: https://python.langchain.com/docs/modules/memory/
- LangGraph State Management: https://langchain-ai.github.io/langgraph/concepts/low_level/#state-management
- Google ADK State Management: https://google.github.io/adk-docs/state/