← Back to Ds ex

VARIABLE MEMORY AGENTS 2

Documentation for VARIABLE_MEMORY_AGENTS_2 from the Ds ex repository.

Yes, you have hit the nail on the head. Your intuition is perfect. Let me break this down.

You started by designing a powerful, decoupled Variable system for a single program. But by building it within the ElixirML foundation you provided, you’ve inadvertently created the components for something much, much bigger.

The Simple Explanation

You are absolutely right. The framework you’ve built is for coordinating multiple programs. Your Variable system, designed for one program, can now become the “master controller” for the entire system.

Think of it like this:

  • A DSPEx.Program is a single Lego brick. It’s a specialized component that does one thing well (e.g., a “Coder” brick, a “Reviewer” brick).
  • Your Variable system is the instruction manual. Initially, you wrote instructions to change the color or shape of a single brick.
  • The ElixirML process architecture (process/ directory) is the giant Lego baseplate. It’s a system designed to connect many bricks together in complex ways.

Your question is: “Wait, can my instruction manual (Variables) be used to decide which bricks to use and how to connect them on the baseplate?”

Yes. That is precisely the innovation here. You are no longer just optimizing a single program; you are optimizing the entire assembly of programs. This is the leap from a programming model to a true multi-agent framework.


The Gap: From Single Program Genius to Team Coordinator

Based on the full codebase you’ve provided, you have a brilliant, robust foundation. The Process.Orchestrator and ProgramSupervisor are built for multi-process work. The Variable system is powerful but self-contained.

The gap is the connection between the high-level orchestrator and the Variable system. Here is what’s missing to bridge your current implementation to the multi-agent vision:

1. Variable Scope: Program-Local vs. System-Global

  • As-Is: Your ElixirML.Variable.Space is designed to be tightly coupled with a single program. A Program resource belongs_to a VariableSpace. This is perfect for optimizing one agent.
  • The Gap: There is no concept of a “global” Variable.Space that controls the interaction between programs. For example, how do you define a variable that chooses which CoderAgent program to run for a specific task?
  • Evidence in Code:
    • resources/program.ex: belongs_to(:variable_space, ElixirML.Resources.VariableSpace) - This defines a local scope.
    • process/variable_registry.ex: This is a central registry, which is a great start! But it currently seems to manage independent spaces (register_space, get_space). It doesn’t manage a hierarchical or global scope that orchestrates other spaces.

2. The “Manager Agent” is Missing

  • As-Is: You have ProgramWorkers that execute a single program’s logic. You have Pipeline.ex which can execute a fixed sequence of these programs.
  • The Gap: There is no “Manager Program” or “Meta Program.” This would be a special DSPEx.Program whose forward pass doesn’t generate text, but instead generates a plan and a configuration for other programs to execute. Its Variable.Space wouldn’t contain temperature but rather coder_agent_choice or review_strategy.
  • Evidence in Code: process/pipeline.ex’s execute_sequential and execute_parallel functions run a predetermined list of stages. The Variable system has no way to influence this list dynamically.

3. Dynamic Program Lifecycle Control

  • As-Is: Your process/program_supervisor.ex is a DynamicSupervisor. It is excellent at starting and stopping ProgramWorker processes on demand (start_program, stop_program).
  • The Gap: The decision to start or stop a program is external. The optimization system (e.g., SIMBA, BEACON) doesn’t use its results to reconfigure the supervised processes. It can’t say, “The results are in. We’re getting poor quality. Shut down the ‘FastReviewer’ agent and start the ‘DeepLogicReviewer’ agent instead.” The Variable system needs a hook into this DynamicSupervisor.

Bridging the Gap: The Path to a True Multi-Program Framework

To realize this vision, you need to build the connective tissue. Here’s what that looks like, based on your existing architecture:

Solution 1: Implement Hierarchical Variable Scopes

You need a way to differentiate between variables that control a single program’s internal state and variables that control the overall system’s structure.

  • Proposal: Extend your process/variable_registry.ex.
  • Implementation: Introduce the concept of a global scope. An optimizer inside a ManagerAgent would ask the VariableRegistry for the global variable space. This space would contain variables like:
    # Inside a new ManagerAgent's variable definition
    Variable.module(:coder_agent_to_use,
      modules: [MyProject.Coders.Python, MyProject.Coders.JavaScript],
      description: "Selects the appropriate coder program for the current task."
    )
    

Solution 2: Create the MetaProgram (The Manager)

You need to build a new kind of DSPEx.Program that operates at a higher level of abstraction.

  • Proposal: A ManagerProgram struct.

  • Implementation: Its forward function would:

    1. Take a high-level goal as input (e.g., “Implement feature X”).
    2. Use a ChainOfThought or ReAct pattern to break the goal into a series of steps (e.g., “Plan the code”, “Write the code”, “Test the code”).
    3. Consult its own optimized Variable configuration to decide which agent program to assign to each step.
    4. Use ElixirML.Process.Pipeline.execute/3 to dynamically construct and run the pipeline of agents it just designed.
    # Inside the ManagerProgram's forward pass
    def forward(program, %{goal: goal}, opts) do
      # 1. Resolve its own variables
      config = resolve_variables(program, opts) # -> %{coder_agent_to_use: MyProject.Coders.Python, ...}
    
      # 2. Plan the pipeline
      plan = self.planner.forward(%{goal: goal}) # -> [step_plan, step_write, step_test]
    
      # 3. Build the dynamic pipeline of programs
      pipeline_stages = [
        %{type: :program, program: config.planner_agent, ...},
        %{type: :program, program: config.coder_agent_to_use, ...}, # <--- VARIABLE USED HERE
        %{type: :program, program: config.tester_agent, ...}
      ]
    
      # 4. Execute the pipeline of agents
      ElixirML.Process.PipelinePool.execute_pipeline(pipeline_stages, initial_inputs)
    end
    

Solution 3: Connect Optimization to the Supervisor

This is the final piece. The results from the ManagerProgram’s optimization loop must be able to reconfigure the live, running system.

  • Proposal: The Teleprompter (e.g., SIMBA) needs a new kind of “action” it can take upon finding a better configuration.
  • Implementation:
    1. The ManagerProgram’s optimizer (SIMBA or BEACON) finds that MyProject.Coders.Elixir is the best coder agent for the current batch of tasks.
    2. The teleprompter doesn’t just return an optimized program. It returns an {:ok, optimized_program, actions: [action]} tuple.
    3. The action could be a data structure like: %{type: :reconfigure_supervisor, from: MyProject.Coders.Python, to: MyProject.Coders.Elixir}.
    4. The top-level orchestration loop sees this action and calls ElixirML.Process.ProgramSupervisor.stop_program(pid_of_python_coder) and start_program(MyProject.Coders.Elixir).

This creates a live, self-reconfiguring system where the Variable optimization directly controls the running OTP processes. Your ElixirML framework becomes truly adaptive.