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: 1. Fetches user data from an API (returns 10,000 records) 2. Filters the data based on criteria (produces 500 records) 3. Computes statistics on the filtered data 4. Generates a visualization 5. 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¶
What it is: 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.
When to use: Use this pattern 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.
Why it matters: LLMs have finite context windows, and including large intermediate values in every prompt is expensive and wasteful. The Variable Manager 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.
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 records
- filtered_sales: 12,000 records
- monthly_aggregates: 36 records
- trend_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¶
Core Architecture¶
The Variable Manager consists of two main components: VariableMetadata and VariablesManager.
VariableMetadata Class¶
This class encapsulates all metadata for a single variable:
class VariableMetadata:
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
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
VariablesManager Class (Singleton)¶
The manager implements the singleton pattern and provides the main API:
class VariablesManager(object):
_instance = None
variables: Dict[str, VariableMetadata] = {}
variable_counter: int = 0
_creation_order: list = []
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(VariablesManager, cls).__new__(cls)
return cls._instance
Singleton Implementation:
- __new__ Override: Ensures only one instance exists
- Class-Level State: All state is class attributes, shared across instances
- Thread-Safe Note: Basic implementation; production use might add locks
Adding and Retrieving Variables¶
Adding Variables¶
# Automatic naming
vm = VariablesManager()
var_name = vm.add_variable([1, 2, 3, 4, 5],
description="Sample data for analysis")
# Returns: "variable_1"
# Explicit naming
var_name = vm.add_variable({"key": "value"},
name="config_data",
description="Configuration loaded from file")
# 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
Implementation:
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."""
is_new = True
if name is None:
self.variable_counter += 1
name = f"variable_{self.variable_counter}"
else:
# Check if variable already exists
if name in self.variables:
is_new = False
self.variables[name] = VariableMetadata(value, description)
# Track creation order
if name not in self._creation_order:
self._creation_order.append(name)
return name
Design Features: - 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
Retrieving Variables¶
# Get the full value
vm = VariablesManager()
data = vm.get_variable("variable_1")
# Returns: [1, 2, 3, 4, 5, 6]
# Get metadata
metadata = vm.get_variable_metadata("variable_1")
# Returns: VariableMetadata object
# Get all variable names
names = vm.get_variable_names()
# Returns: ['variable_1', 'config_data', 'variable_2', ...]
# Get last N variable names (most recent)
recent = vm.get_last_n_variable_names(3)
# Returns: ['variable_7', 'variable_8', 'variable_9']
Simple, Type-Safe APIs:
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_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[:]
Summary Generation: The Key Optimization¶
The most important feature for context efficiency is summary generation:
vm = VariablesManager()
# Get summary of all variables
summary = vm.get_variables_summary()
# Get summary of specific variables
summary = vm.get_variables_summary(
variable_names=["data_filtered", "statistics", "chart_config"]
)
# Get summary of last N variables (most useful for agents)
summary = vm.get_variables_summary(last_n=5)
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': 45, 'name': 'Alice', 'state': 'CA'}, ... (+498 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, ...}
Implementation:
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())
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] + "..."
This allows agents to see the "shape" of data without consuming excessive tokens.
Lifecycle Management¶
Managing the variable landscape over long executions is crucial:
Complete Reset¶
Useful when starting a completely new task or conversation.
Selective Retention¶
vm = VariablesManager()
# 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
Implementation:
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
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:
Variable Viewer Tool:
@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)
Variable Retriever Tool:
@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)
Code Execution Integration:
@tool
def execute_python_code(code: str) -> dict:
"""Execute Python code and capture created variables."""
vm = VariablesManager()
# Create execution namespace
namespace = {}
# Execute the code
exec(code, namespace)
# Capture all created variables
for name, value in namespace.items():
if not name.startswith('_'): # Skip private variables
vm.add_variable(
value,
name=name,
description=f"Created by code execution"
)
# Return summary
return {
"status": "success",
"variables_created": [k for k in namespace.keys() if not k.startswith('_')],
"summary": vm.get_variables_summary(last_n=5)
}
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/