Skip to content

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:

  1. 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.

  2. Metadata Extraction: The manager automatically extracts metadata: type detection, item counts for collections, creation timestamps, and generates value previews.

  3. Summary Generation: When agents need to understand available data, they receive lightweight summaries showing variable names, types, descriptions, and counts—not full values.

  4. On-Demand Retrieval: Only when an agent explicitly needs to operate on data does it retrieve the full value through dedicated tool calls.

  5. 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:

# Agent knows filtered_data exists from previous execution
summary_stats = filtered_data.describe()

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

vm = VariablesManager()
vm.reset()  # Clear all variables, reset counter

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.

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/