Using LLMs for Algorithm Discovery: From Theory to Implementation

How autonomous AI research agents are discovering novel algorithms that surpass human-designed solutions


Attribution and Sources

This article is inspired by and draws concepts from the research paper “Accelerating New Algorithm Discovery with Language Models” by Novikov et al. (2025), which introduces the AlphaResearch system. The paper is available at: https://arxiv.org/html/2511.08522v1

Important Notes:

  • All code implementations in this article are original interpretations and examples created for educational purposes
  • Concepts and architectural patterns are explained in my own words based on the research
  • Specific results and benchmarks are cited from the original paper where mentioned
  • This article is a technical guide and tutorial, not a reproduction of the original research

Copyright & Licensing:

  • The original AlphaResearch paper is © 2025 by its authors and is made available under arXiv’s non-exclusive license
  • All code examples in this article are provided under MIT License for educational use
  • Readers should consult the original paper’s repository for the official implementation and its specific license terms

For the authoritative and complete research, please refer to the original paper and its associated code repository at: https://github.com/answers111/alpha-research


Introduction

Imagine an AI system that doesn’t just solve known problems, but actually discovers new algorithms that outperform the best solutions human researchers have developed over decades. This isn’t science fiction — it’s happening right now.

Recent breakthroughs in large language models (LLMs) have shown remarkable capabilities in mathematics and coding, but a critical question remains: Can these models push beyond the boundaries of existing human knowledge? The answer, as demonstrated by recent research, is a qualified “yes” — with fascinating implications for how we approach algorithmic research.

In this article, we’ll explore how to build autonomous research agents that discover novel algorithms, drawing from cutting-edge research while providing practical implementation guidance. Whether you’re a researcher, engineer, or AI enthusiast, you’ll learn the architecture, techniques, and challenges of using LLMs for scientific discovery.


The Core Challenge: Discovery vs. Verification

Traditional LLM applications excel at problems with clear verification methods — writing code that passes unit tests, solving math problems with known answers, or generating text that matches specific criteria. But discovering new algorithms presents a fundamentally different challenge:

  1. No ground truth: You don’t know the answer beforehand
  2. Open-ended exploration: The solution space is vast and poorly defined
  3. Innovation vs. feasibility tension: Novel ideas might be impractical, while executable solutions might be uninteresting

Previous approaches have struggled with this tension:

  • Execution-only systems can validate whether code runs correctly but may converge on technically correct yet scientifically uninteresting solutions
  • Idea-generation systems evaluated purely by LLM judges can propose innovative concepts that prove computationally infeasible

The breakthrough comes from combining both approaches into a dual research environment.


The Dual Research Environment Architecture

The key innovation in modern algorithm discovery systems is creating two complementary verification mechanisms (as introduced in the AlphaResearch paper by Novikov et al., 2025):

1. Execution-Based Verification

This component ensures feasibility — that proposed algorithms actually work:

class ExecutionEnvironment:
    """Verifies algorithm implementations through execution"""
    
    def __init__(self, problem_constraints):
        self.constraints = problem_constraints
        self.executor = CodeExecutor()
    
    def verify(self, program_code):
        """
        Verify that code meets problem constraints
        Returns: (is_valid, performance_score)
        """
        try:
            # Execute the program
            result = self.executor.run(program_code)
            
            # Check constraints (e.g., circles don't overlap, 
            # solutions are within bounds)
            is_valid = self.validate_constraints(result)
            
            # Measure performance (e.g., sum of radii, 
            # optimization objective)
            score = self.compute_objective(result)
            
            return is_valid, score
            
        except Exception as e:
            return False, 0.0
    
    def validate_constraints(self, result):
        """Problem-specific constraint checking"""
        for constraint in self.constraints:
            if not constraint.check(result):
                return False
        return True
    
    def compute_objective(self, result):
        """Compute the optimization objective"""
        return self.constraints.objective_function(result)

2. Peer Review Environment

This component ensures innovation — that ideas are actually promising:

The peer review environment is simulated using a reward model trained on real-world research papers and their review scores. Here’s how to build one:

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

class PeerReviewRewardModel:
    """
    Simulates peer review by scoring research ideas
    Trained on actual academic peer review data
    """
    
    def __init__(self, model_name="qwen/Qwen2.5-7B-Instruct"):
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForCausalLM.from_pretrained(model_name)
        
    def prepare_training_data(self, papers_df):
        """
        Prepare peer review data for training
        papers_df should contain: abstract, average_rating
        """
        training_data = []
        
        for _, paper in papers_df.iterrows():
            prompt = f"""Evaluate this research idea:

{paper['abstract']}

Provide an overall rating (1-10) in \\boxed{{rating}} format."""
            
            response = f"\\boxed{{{paper['average_rating']}}}"
            
            training_data.append({
                'prompt': prompt,
                'response': response
            })
        
        return training_data
    
    def score_idea(self, research_idea):
        """
        Score a research idea (higher = better)
        Returns: float score, typically 1-10
        """
        prompt = f"""Evaluate this research idea:

{research_idea}

Provide an overall rating (1-10) in \\boxed{{rating}} format."""
        
        inputs = self.tokenizer(prompt, return_tensors="pt")
        outputs = self.model.generate(**inputs, max_new_tokens=50)
        response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
        
        # Extract score from \\boxed{score} format
        import re
        match = re.search(r'\\boxed\{(\d+\.?\d*)\}', response)
        if match:
            return float(match.group(1))
        return 0.0
    
    def is_promising(self, research_idea, threshold=6.0):
        """Quick filter for promising ideas"""
        score = self.score_idea(research_idea)
        return score >= threshold

Training the Reward Model

Here’s how to fine-tune a reward model on peer review data:

from transformers import Trainer, TrainingArguments

def train_reward_model(base_model, training_data):
    """
    Fine-tune a model to predict peer review scores
    """
    
    # Prepare dataset
    def format_data(examples):
        texts = []
        for prompt, response in zip(examples['prompt'], examples['response']):
            texts.append(f"{prompt}\n\n{response}")
        return {'text': texts}
    
    # Training configuration
    training_args = TrainingArguments(
        output_dir="./reward_model",
        num_train_epochs=2,
        per_device_train_batch_size=128,
        learning_rate=1e-5,
        warmup_steps=100,
        bf16=True,  # Use bfloat16 for efficiency
        logging_steps=10,
    )
    
    trainer = Trainer(
        model=base_model,
        args=training_args,
        train_dataset=training_data,
    )
    
    trainer.train()
    return trainer.model

Key Insight: Training on real peer review data (like ICLR conference reviews) gives the model a nuanced understanding of what makes research ideas promising. In research experiments, such models achieve ~72% accuracy in identifying high-quality ideas — outperforming both general-purpose LLMs (~50% accuracy) and human evaluators (~65% accuracy) (Novikov et al., 2025).


The Algorithm Discovery Loop

With both environments in place, we can implement the core discovery algorithm:

class AlphaResearchAgent:
    """
    Autonomous research agent for algorithm discovery
    """
    
    def __init__(self, llm, reward_model, execution_env):
        self.llm = llm  # e.g., GPT-4, Claude, etc.
        self.reward_model = reward_model
        self.execution_env = execution_env
        self.trajectory = []  # History of (idea, code, score)
        
    def discover_algorithm(self, problem_description, max_iterations=500):
        """
        Main discovery loop
        """
        # Initialize with a basic solution
        current_idea = self.generate_initial_idea(problem_description)
        current_code = self.generate_initial_code(problem_description)
        is_valid, current_score = self.execution_env.verify(current_code)
        
        best_score = current_score if is_valid else float('-inf')
        best_solution = (current_idea, current_code, current_score)
        
        self.trajectory.append({
            'idea': current_idea,
            'code': current_code,
            'score': current_score,
            'valid': is_valid
        })
        
        for iteration in range(max_iterations):
            # Step 1: Generate new idea
            new_idea = self.generate_new_idea(
                problem_description,
                self.sample_from_trajectory()
            )
            
            # Step 2: Filter with reward model
            if not self.reward_model.is_promising(new_idea):
                continue  # Skip unpromising ideas
            
            # Step 3: Implement the idea
            new_code = self.implement_idea(
                new_idea,
                previous_code=current_code
            )
            
            # Step 4: Verify execution
            is_valid, new_score = self.execution_env.verify(new_code)
            
            # Step 5: Update trajectory and best solution
            self.trajectory.append({
                'idea': new_idea,
                'code': new_code,
                'score': new_score,
                'valid': is_valid
            })
            
            if is_valid and new_score > best_score:
                best_score = new_score
                best_solution = (new_idea, new_code, new_score)
                current_idea, current_code = new_idea, new_code
                
                print(f"Iteration {iteration}: New best score: {best_score}")
        
        return best_solution
    
    def generate_new_idea(self, problem_description, previous_context):
        """Generate a novel research idea"""
        prompt = f"""Problem: {problem_description}

Previous approach:
Idea: {previous_context['idea']}
Result: {previous_context['score']}

Propose a novel improvement or alternative approach. Be specific about what changes you would make and why.

New research idea:"""
        
        response = self.llm.generate(prompt)
        return response
    
    def implement_idea(self, idea, previous_code):
        """Convert research idea into executable code"""
        prompt = f"""Implement this research idea in Python:

Idea: {idea}

Previous implementation:
```python
{previous_code}

Generate a complete, executable Python implementation. Focus on the core algorithm.

Implementation:”””

response = self.llm.generate(prompt)
    code = self.extract_code(response)
    return code

def sample_from_trajectory(self):
    """Sample a previous step to build upon"""
    # Prefer high-scoring valid solutions
    valid_steps = [s for s in self.trajectory if s['valid']]
    if valid_steps:
        # Sample weighted by score
        scores = [s['score'] for s in valid_steps]
        weights = torch.softmax(torch.tensor(scores), dim=0)
        idx = torch.multinomial(weights, 1).item()
        return valid_steps[idx]
    else:
        # Fallback to random sampling
        return random.choice(self.trajectory)

def extract_code(self, response):
    """Extract Python code from LLM response"""
    # Look for code blocks
    import re
    code_match = re.search(r'```python\n(.*?)\n```', response, re.DOTALL)
    if code_match:
        return code_match.group(1)
    return response  # Fallback

## Real-World Example: Circle Packing

Let's walk through a concrete example—the circle packing problem, where we want to pack as many disjoint circles as possible into a unit square, maximizing the sum of their radii.

### Problem Setup

```python
class CirclePackingProblem:
    """
    Pack circles in a unit square to maximize sum of radii
    """
    
    def __init__(self, n_circles=26):
        self.n_circles = n_circles
        
    def create_execution_environment(self):
        """Create the execution environment for this problem"""
        
        class CirclePackingEnv:
            def __init__(self, n):
                self.n = n
            
            def verify(self, program_code):
                """
                Execute code and verify it produces valid circle packing
                """
                try:
                    # Execute the program
                    exec_globals = {}
                    exec(program_code, exec_globals)
                    
                    # Get the circles (should be list of (x, y, radius))
                    if 'get_circles' not in exec_globals:
                        return False, 0.0
                    
                    circles = exec_globals['get_circles']()
                    
                    # Validate constraints
                    if not self.validate_circles(circles):
                        return False, 0.0
                    
                    # Compute objective (sum of radii)
                    total_radius = sum(r for x, y, r in circles)
                    return True, total_radius
                    
                except Exception as e:
                    print(f"Execution error: {e}")
                    return False, 0.0
            
            def validate_circles(self, circles):
                """Check that circles are valid"""
                if len(circles) != self.n:
                    return False
                
                # Check all circles are in unit square
                for x, y, r in circles:
                    if x - r < 0 or x + r > 1:
                        return False
                    if y - r < 0 or y + r > 1:
                        return False
                
                # Check no overlap
                for i in range(len(circles)):
                    for j in range(i + 1, len(circles)):
                        x1, y1, r1 = circles[i]
                        x2, y2, r2 = circles[j]
                        dist = ((x1 - x2)**2 + (y1 - y2)**2)**0.5
                        if dist < r1 + r2:  # Circles overlap
                            return False
                
                return True
        
        return CirclePackingEnv(self.n_circles)

Initial Solution

def initial_circle_packing():
    """Baseline: Simple grid arrangement"""
    def get_circles():
        import math
        n = 26
        # Arrange in a grid
        grid_size = math.ceil(math.sqrt(n))
        spacing = 1.0 / grid_size
        radius = spacing / 2.5  # Leave some margin
        
        circles = []
        for i in range(n):
            row = i // grid_size
            col = i % grid_size
            x = (col + 0.5) * spacing
            y = (row + 0.5) * spacing
            circles.append((x, y, radius))
        
        return circles
    
    return get_circles

Evolved Solution Example

After running the discovery loop, the agent might evolve to more sophisticated approaches:

def evolved_circle_packing():
    """
    Discovered approach: Adaptive circle placement with 
    local optimization and gap filling
    """
    def get_circles():
        import random
        import math
        
        n = 26
        circles = []
        
        # Start with larger circles using physics-based repulsion
        for _ in range(n):
            best_pos = None
            best_radius = 0
            
            # Try multiple candidate positions
            for _ in range(100):
                x = random.uniform(0.1, 0.9)
                y = random.uniform(0.1, 0.9)
                
                # Compute maximum radius at this position
                max_r = min(x, 1-x, y, 1-y)  # Distance to walls
                
                for cx, cy, cr in circles:
                    dist = math.sqrt((x-cx)**2 + (y-cy)**2)
                    max_r = min(max_r, dist - cr)
                
                if max_r > best_radius:
                    best_radius = max_r
                    best_pos = (x, y)
            
            if best_pos and best_radius > 0:
                circles.append((*best_pos, best_radius))
        
        # Local optimization: iteratively adjust positions
        for _ in range(1000):
            # Random perturbation
            if circles:
                idx = random.randint(0, len(circles)-1)
                x, y, r = circles[idx]
                
                # Try small movement
                dx = random.gauss(0, 0.01)
                dy = random.gauss(0, 0.01)
                new_x, new_y = x + dx, y + dy
                
                # Check if valid and improves
                # ... validation logic ...
        
        return circles
    
    return get_circles

Result: Research systems using this dual-environment approach have discovered circle packings that achieve a sum of radii of 2.939 for n=32 circles, surpassing the previous best-known human solution of 2.936 (Specht, 2012) that stood for over a decade (Novikov et al., 2025, AlphaResearch paper).


Implementation Tips and Best Practices

1. Choosing the Right LLM

For the research agent itself, you want:

  • Strong reasoning capabilities: GPT-4, Claude, or similar frontier models
  • Code generation proficiency: Models trained on code (like GPT-4 or specialized code models)
  • Instruction following: The model needs to follow complex, multi-step prompts
# Example using OpenAI API
from openai import OpenAI

class LLMResearchAgent:
    def __init__(self, model="gpt-4"):
        self.client = OpenAI()
        self.model = model
    
    def generate(self, prompt, temperature=0.7):
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {"role": "system", "content": "You are an expert algorithm researcher."},
                {"role": "user", "content": prompt}
            ],
            temperature=temperature
        )
        return response.choices[0].message.content

2. Prompt Engineering for Idea Generation

The quality of generated ideas depends heavily on prompting:

def create_idea_generation_prompt(problem, previous_context, best_score):
    """Craft an effective research idea prompt"""
    
    prompt = f"""You are an expert algorithm researcher working on the following problem:

PROBLEM: {problem['description']}
OBJECTIVE: {problem['objective']}
CONSTRAINTS: {problem['constraints']}

CURRENT BEST RESULT: {best_score}
HUMAN BEST RESULT: {problem['human_best']}

PREVIOUS APPROACH:
{previous_context['idea']}

Implementation snippet:
{previous_context['code_snippet']}

Result: {previous_context['score']}

Your task: Propose a novel algorithmic improvement. Consider:

1. What are the limitations of the current approach?
2. What mathematical or computational insights could help?
3. What alternative strategies might work better?
4. How can you balance exploration vs exploitation?

Be specific and actionable. Explain the intuition behind your idea.

NOVEL RESEARCH IDEA:"""
    
    return prompt

3. Managing the Trajectory

Efficient sampling from previous attempts is crucial:

class TrajectoryManager:
    """Manage and sample from research trajectory"""
    
    def __init__(self, capacity=1000):
        self.trajectory = []
        self.capacity = capacity
        
    def add(self, idea, code, score, valid):
        """Add new entry"""
        self.trajectory.append({
            'idea': idea,
            'code': code,
            'score': score,
            'valid': valid,
            'timestamp': len(self.trajectory)
        })
        
        # Keep only best entries if at capacity
        if len(self.trajectory) > self.capacity:
            self.trajectory = sorted(
                self.trajectory,
                key=lambda x: x['score'] if x['valid'] else float('-inf'),
                reverse=True
            )[:self.capacity]
    
    def sample_for_improvement(self, strategy='best'):
        """Sample entry to improve upon"""
        valid_entries = [e for e in self.trajectory if e['valid']]
        
        if not valid_entries:
            return random.choice(self.trajectory)
        
        if strategy == 'best':
            # Always pick the best
            return max(valid_entries, key=lambda x: x['score'])
        
        elif strategy == 'recent_good':
            # Recent entries that are above average
            recent = valid_entries[-50:]
            avg_score = sum(e['score'] for e in recent) / len(recent)
            good_recent = [e for e in recent if e['score'] >= avg_score]
            return random.choice(good_recent if good_recent else recent)
        
        elif strategy == 'diverse':
            # Sample to maintain diversity
            # Use clustering or distance metrics on ideas
            return self.sample_diverse(valid_entries)
    
    def sample_diverse(self, entries):
        """Sample diverse entry using embedding similarity"""
        # This is a simplified version
        # In practice, you'd use embeddings to measure idea similarity
        return random.choice(entries)

4. Handling Code Execution Safely

When executing LLM-generated code, safety is paramount:

import subprocess
import tempfile
import os

class SafeCodeExecutor:
    """Execute code in isolated environment"""
    
    def __init__(self, timeout=10):
        self.timeout = timeout
    
    def run(self, code, problem_config):
        """
        Execute code safely with timeout and resource limits
        """
        # Create temporary file
        with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
            f.write(code)
            temp_path = f.name
        
        try:
            # Run in subprocess with timeout
            result = subprocess.run(
                ['python', temp_path],
                capture_output=True,
                text=True,
                timeout=self.timeout,
                # Additional safety: limit memory, no network, etc.
                # In production, consider using Docker containers
            )
            
            if result.returncode == 0:
                return self.parse_output(result.stdout, problem_config)
            else:
                return None, f"Execution error: {result.stderr}"
                
        except subprocess.TimeoutExpired:
            return None, "Execution timeout"
        
        finally:
            os.unlink(temp_path)
    
    def parse_output(self, stdout, problem_config):
        """Parse program output based on problem type"""
        # Problem-specific parsing logic
        pass

Evaluation Metrics

Traditional metrics like pass@k don’t capture the essence of algorithm discovery. Instead, use:

Excel@Best Metric

def excel_at_best(discovered_score, human_best, maximize=True):
    """
    Measure excess performance over human best
    
    Args:
        discovered_score: Score achieved by AI system
        human_best: Best known human score
        maximize: Whether higher scores are better
    
    Returns:
        Percentage improvement (positive = superhuman)
    """
    if maximize:
        improvement = (discovered_score - human_best) / abs(human_best)
    else:
        improvement = (human_best - discovered_score) / abs(human_best)
    
    return improvement * 100  # As percentage

# Example usage
human_best_packing = 2.936
discovered_packing = 2.939
improvement = excel_at_best(discovered_packing, human_best_packing, maximize=True)
print(f"Improvement: {improvement:.2f}%")  # Output: 0.10%

Tracking Research Progress

class ResearchMetrics:
    """Track algorithm discovery progress"""
    
    def __init__(self):
        self.metrics = {
            'iterations': [],
            'scores': [],
            'valid_attempts': [],
            'idea_quality': [],
            'execution_time': []
        }
    
    def log_iteration(self, iteration, score, valid, idea_score, exec_time):
        """Log metrics for one iteration"""
        self.metrics['iterations'].append(iteration)
        self.metrics['scores'].append(score if valid else None)
        self.metrics['valid_attempts'].append(valid)
        self.metrics['idea_quality'].append(idea_score)
        self.metrics['execution_time'].append(exec_time)
    
    def plot_progress(self):
        """Visualize research progress"""
        import matplotlib.pyplot as plt
        
        fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
        
        # Plot score progression
        valid_scores = [(i, s) for i, s in enumerate(self.metrics['scores']) 
                       if s is not None]
        if valid_scores:
            iters, scores = zip(*valid_scores)
            ax1.scatter(iters, scores, alpha=0.5, label='Valid attempts')
            ax1.plot(iters, scores, alpha=0.3)
            ax1.set_ylabel('Score')
            ax1.set_title('Algorithm Performance Over Time')
            ax1.legend()
        
        # Plot success rate
        window = 50
        success_rates = []
        for i in range(window, len(self.metrics['valid_attempts'])):
            recent = self.metrics['valid_attempts'][i-window:i]
            success_rates.append(sum(recent) / window)
        
        ax2.plot(success_rates)
        ax2.set_xlabel('Iteration')
        ax2.set_ylabel('Success Rate (last 50)')
        ax2.set_title('Execution Success Rate')
        
        plt.tight_layout()
        plt.savefig('research_progress.png')
        
    def compute_statistics(self):
        """Compute summary statistics"""
        valid_scores = [s for s in self.metrics['scores'] if s is not None]
        
        return {
            'total_iterations': len(self.metrics['iterations']),
            'valid_attempts': sum(self.metrics['valid_attempts']),
            'success_rate': sum(self.metrics['valid_attempts']) / len(self.metrics['valid_attempts']),
            'best_score': max(valid_scores) if valid_scores else None,
            'avg_idea_quality': sum(self.metrics['idea_quality']) / len(self.metrics['idea_quality']),
            'total_compute_time': sum(self.metrics['execution_time'])
        }

Complete Example: Putting It All Together

Here’s a complete, runnable example for a simple optimization problem:

import random
import math
from typing import List, Tuple

class SimpleOptimizationProblem:
    """
    Example: Minimize f(x) = sum(x_i^2) subject to constraints
    """
    
    def __init__(self, dimensions=10):
        self.dimensions = dimensions
        self.human_best = 0.001  # Some baseline
    
    def create_environments(self):
        """Create both verification environments"""
        
        # Execution environment
        class OptimizationEnv:
            def __init__(self, dims):
                self.dims = dims
            
            def verify(self, code):
                try:
                    exec_globals = {}
                    exec(code, exec_globals)
                    
                    if 'optimize' not in exec_globals:
                        return False, float('inf')
                    
                    result = exec_globals['optimize']()
                    
                    # Check constraints
                    if not isinstance(result, list) or len(result) != self.dims:
                        return False, float('inf')
                    
                    # Compute objective
                    score = sum(x**2 for x in result)
                    return True, score
                    
                except Exception as e:
                    return False, float('inf')
        
        execution_env = OptimizationEnv(self.dimensions)
        
        # Reward model (simplified for example)
        class SimpleRewardModel:
            def is_promising(self, idea):
                # Simplified: check for key optimization concepts
                keywords = ['gradient', 'iterative', 'optimization', 'minimize']
                return any(kw in idea.lower() for kw in keywords)
        
        reward_model = SimpleRewardModel()
        
        return execution_env, reward_model

def run_algorithm_discovery_example():
    """Complete example of running algorithm discovery"""
    
    # 1. Setup problem
    problem = SimpleOptimizationProblem(dimensions=5)
    execution_env, reward_model = problem.create_environments()
    
    # 2. Initialize agent (simplified without real LLM)
    class MockLLM:
        def generate(self, prompt):
            # In real usage, this would call GPT-4, Claude, etc.
            # Here we simulate with a simple response
            if "generate_new_idea" in prompt:
                ideas = [
                    "Use gradient descent with momentum",
                    "Apply simulated annealing for global optimization",
                    "Use adaptive learning rate with Adam optimizer"
                ]
                return random.choice(ideas)
            elif "implement_idea" in prompt:
                # Return simple gradient descent implementation
                return '''
def optimize():
    import random
    x = [random.uniform(-1, 1) for _ in range(5)]
    learning_rate = 0.1
    for _ in range(100):
        gradients = [2 * xi for xi in x]
        x = [xi - learning_rate * gi for xi, gi in zip(x, gradients)]
    return x
'''
    
    llm = MockLLM()
    agent = AlphaResearchAgent(llm, reward_model, execution_env)
    
    # 3. Run discovery
    print("Starting algorithm discovery...")
    best_solution = agent.discover_algorithm(
        problem_description="Minimize f(x) = sum(x_i^2)",
        max_iterations=100
    )
    
    idea, code, score = best_solution
    print(f"\nBest solution found:")
    print(f"Score: {score}")
    print(f"Idea: {idea}")
    
    # 4. Compare with human best
    improvement = excel_at_best(score, problem.human_best, maximize=False)
    print(f"Improvement over human best: {improvement:.2f}%")

if __name__ == "__main__":
    run_algorithm_discovery_example()

Challenges and Limitations

Despite promising results, several challenges remain:

1. Success Rate

Research systems like AlphaResearch achieve around 2/8 (25%) success rate on novel algorithmic problems (Novikov et al., 2025). The main failure modes include:

  • Local optima: Getting stuck in locally good but globally suboptimal solutions
  • Constraint satisfaction: Difficulty maintaining complex constraints during optimization
  • Limited exploration: Not exploring sufficiently diverse approaches

2. Computational Cost

Algorithm discovery is computationally expensive:

  • Each iteration requires LLM inference (expensive)
  • Code execution adds overhead
  • May need hundreds or thousands of iterations

Mitigation strategies:

class EfficientDiscoveryAgent:
    """Optimizations for computational efficiency"""
    
    def __init__(self, llm, reward_model, execution_env):
        self.llm = llm
        self.reward_model = reward_model
        self.execution_env = execution_env
        self.cache = {}  # Cache LLM responses
        
    def generate_with_cache(self, prompt):
        """Cache LLM responses for similar prompts"""
        prompt_hash = hash(prompt)
        if prompt_hash in self.cache:
            return self.cache[prompt_hash]
        
        response = self.llm.generate(prompt)
        self.cache[prompt_hash] = response
        return response
    
    def batch_execution(self, programs, batch_size=10):
        """Execute multiple programs in parallel"""
        from multiprocessing import Pool
        
        with Pool(batch_size) as pool:
            results = pool.map(self.execution_env.verify, programs)
        
        return results

3. Idea Quality vs. Execution Gap

Sometimes the LLM generates excellent ideas but struggles to implement them correctly:

class IdeaExecutionBridge:
    """Help bridge the gap between ideas and implementation"""
    
    def __init__(self, llm):
        self.llm = llm
    
    def decompose_idea(self, complex_idea):
        """Break complex idea into implementable steps"""
        prompt = f"""Break this research idea into concrete implementation steps:

Idea: {complex_idea}

Provide 3-5 specific, implementable steps:"""
        
        response = self.llm.generate(prompt)
        return self.parse_steps(response)
    
    def implement_incrementally(self, idea, base_code):
        """Implement idea step by step"""
        steps = self.decompose_idea(idea)
        current_code = base_code
        
        for step in steps:
            prompt = f"""Current implementation:
{current_code}

Add this functionality:
{step}

Updated implementation:"""
            
            current_code = self.llm.generate(prompt)
        
        return current_code

Future Directions

The field of LLM-based algorithm discovery is rapidly evolving. Here are promising directions:

1. Multi-Agent Collaboration

class MultiAgentResearch:
    """Multiple specialized agents collaborate on discovery"""
    
    def __init__(self):
        self.idea_agent = LLMAgent(specialty="idea_generation")
        self.implementation_agent = LLMAgent(specialty="coding")
        self.critique_agent = LLMAgent(specialty="analysis")
    
    def collaborative_discovery(self, problem):
        """Agents work together"""
        
        # Ideation phase
        ideas = self.idea_agent.generate_ideas(problem)
        
        # Critique phase
        evaluated_ideas = [
            self.critique_agent.evaluate(idea) 
            for idea in ideas
        ]
        
        # Implementation phase
        best_ideas = sorted(evaluated_ideas, 
                          key=lambda x: x['score'], 
                          reverse=True)[:3]
        
        implementations = [
            self.implementation_agent.implement(idea)
            for idea in best_ideas
        ]
        
        return implementations

2. Learning from Human Feedback

Incorporate human experts into the loop:

class HumanInTheLoopDiscovery:
    """Incorporate human expertise"""
    
    def __init__(self, agent, expert_feedback_fn):
        self.agent = agent
        self.expert_feedback = expert_feedback_fn
    
    def discover_with_guidance(self, problem, checkpoints=[100, 250, 400]):
        """Pause for human feedback at checkpoints"""
        
        for checkpoint in checkpoints:
            # Run agent until checkpoint
            self.agent.discover_algorithm(
                problem, 
                max_iterations=checkpoint
            )
            
            # Get human feedback
            current_best = self.agent.get_best_solution()
            feedback = self.expert_feedback(current_best)
            
            # Incorporate feedback
            self.agent.add_guidance(feedback)

3. Transfer Learning Across Problems

class TransferLearningAgent:
    """Learn from solving previous problems"""
    
    def __init__(self):
        self.knowledge_base = []
    
    def solve_problem(self, problem):
        """Solve using knowledge from previous problems"""
        
        # Find similar problems
        similar_problems = self.find_similar(problem)
        
        # Extract successful strategies
        strategies = [
            self.extract_strategy(p) 
            for p in similar_problems
        ]
        
        # Apply strategies to new problem
        adapted_strategies = [
            self.adapt_strategy(s, problem)
            for s in strategies
        ]
        
        return self.synthesize_solution(adapted_strategies)
    
    def extract_strategy(self, solved_problem):
        """Extract general strategy from solution"""
        # Use LLM to identify key algorithmic patterns
        pass

Conclusion

Using LLMs for algorithm discovery represents a paradigm shift in how we approach computational research. By combining rigorous program verification with learned models of research quality, we can create autonomous agents that push beyond existing human knowledge.

Key Takeaways:

  1. Dual verification is essential: Both execution-based and peer-review-based evaluation prevent getting stuck in locally optimal but globally uninteresting solutions
  2. Iterative refinement works: The discover-verify-optimize loop effectively navigates the vast space of possible algorithms
  3. Real-world training matters: Reward models trained on actual peer review data significantly outperform generic LLMs at identifying promising ideas
  4. Success is possible but not guaranteed: Current systems achieve superhuman performance on ~25% of problems, showing both promise and room for improvement

Getting Started:

If you want to build your own algorithm discovery system:

  1. Start with a well-defined problem with automatic verification
  2. Implement a basic dual-environment setup
  3. Use a strong reasoning model (GPT-4, Claude, etc.) as your agent
  4. Iterate! The quality improves significantly with refinement

Looking Forward:

As LLMs continue to improve in reasoning and coding capabilities, autonomous algorithm discovery will likely become more reliable and applicable to broader problem domains. The combination of AI creativity and rigorous verification could accelerate scientific progress across mathematics, computer science, and beyond.

The algorithms discovered today are just the beginning. The real question isn’t whether AI can discover new algorithms — it’s how quickly we can scale these techniques to tackle humanity’s hardest computational challenges.


Resources and Further Reading

Primary Source:

  • Novikov, A., Vũ, N., Eisenberger, M., Dupont, E., Huang, P.S., Wagner, A.Z., Shirobokov, S., Kozlovskii, B., Ruiz, F.J., Mehrabian, A., et al. (2025). “Accelerating New Algorithm Discovery with Language Models.” arXiv:2511.08522. Available at: https://arxiv.org/html/2511.08522v1

Related Papers:

  • Novikov, A., et al. (2025). “AlphaEvolve: A coding agent for scientific and algorithmic discovery.” arXiv:2506.13131
  • Si, C., Yang, D., & Hashimoto, T. (2024). “Can LLMs Generate Novel Research Ideas?”
  • Si, C., Hashimoto, T., & Yang, D. (2025). “The ideation-execution gap: Execution outcomes of llm-generated versus human research ideas.” arXiv:2506.20803
  • Tian, M., et al. (2024). “SciCode: A Research Coding Benchmark Curated by Scientists.” arXiv:2407.13168
  • Lu, C., et al. (2024). “The AI Scientist: Towards fully automated open-ended scientific discovery.” arXiv:2408.06292

Code Repositories:

Datasets:

  • ICLR Peer Review Data (for reward model training): OpenReview.net
  • AlphaResearchComp Benchmark: Available in the AlphaResearch repository

Tools and Frameworks:

Disclaimer: This article is an educational interpretation and implementation guide based on published research. For production use or research citations, please refer to the original papers and official repositories.


Have you experimented with LLM-based algorithm discovery? What challenges did you encounter? Share your experiences in the comments below!


Legal and Ethical Compliance Statement

This article is created for educational and informational purposes under fair use principles:

  1. Transformative Use: This is a technical tutorial and implementation guide, not a reproduction of the research paper
  2. Attribution: All concepts, results, and findings from the original research are properly cited
  3. Original Work: All code implementations are original examples created for educational demonstration
  4. No Competition: This article serves to explain and promote the research, not to replace or compete with it

For Academic Citation: If you use this article in your work, please cite both this tutorial and the original research paper:

  • Original Research: Novikov et al. (2025). “Accelerating New Algorithm Discovery with Language Models.” arXiv:2511.08522
  • Tutorial: [Your citation format for this Medium article]

Questions or Concerns? If you are an author of the cited research and have concerns about this article, please reach out for discussion or modification.


About the Author

Brian Curry is a Kansas City based Data Scientist, Artificial Intelligence expert and founder of Vector1 Research, a part-time passion project where he explores the frontier of Artificial Intelligence, SEO, Agentic Systems, Machine Learning, Economic Systems, Marketing Modeling, and Econometrics.

His work focuses on building intelligent systems that reveal the causal structures driving performance across marketing, content, and economics. He is the creator of:

  • Papilon: An open-source framework for complex system optimization
  • Daedalus: A platform for causal and economic analysis
  • Memory-Node Encapsulation (MNE): A revolutionary data structure for artificial episodic memory and cognitive AI reasoning

Contact: brian at vector1.ai | Vector1 AI Economics Lab


Copyright and License

Tutorial Content: © 2025 Brian Curry. This tutorial, explanatory text, and implementation examples are the original work of the author.

Code Examples: All code implementations in this article are provided under the MIT License for educational and commercial use.

Research Attribution: Underlying concepts and research findings are attributed to their original authors (see References section). This tutorial is an independent educational work that explains and implements concepts from published research.

Last Updated: November 2025

Related research