Learn how to use GEPA’s optimize_anything API to evolve entire agent architectures—not just prompts, but the complete system including code, control flow, sub-agents, and helper functions. This tutorial demonstrates how to nearly triple an agent’s accuracy through architectural evolution.
Unlike traditional prompt optimization, agent architecture evolution treats the entire agent system as a text artifact to be optimized. This includes:
Agent control flow and decision logic
Sub-agent architectures and coordination
Helper functions and utilities
Prompts and instructions
Error handling and validation
Real-world result: Gemini Flash on ARC-AGI improved from 32.5% to 89.5% test accuracy by evolving from a 10-line naive agent to a 300+ line sophisticated system.
1
Install GEPA
pip install gepa
The optimize_anything API is available in GEPA’s main package.
2
Understand the Three Optimization Modes
optimize_anything supports three distinct modes:1. Single-Task Search: Solve one hard problem
Agent architecture evolution uses Generalization mode—the agent must work on unseen test cases.
3
Define Your Agent's Seed
Start with a minimal agent implementation:
seed_agent = """import jsonfrom typing import List, Dict, Anydef solve_puzzle(train_examples: List[Dict], test_input: Any) -> Any: """ Solve an ARC-AGI puzzle given training examples. Args: train_examples: List of {"input": grid, "output": grid} pairs test_input: Input grid to solve Returns: Predicted output grid """ # Naive baseline: return first training output if train_examples: return train_examples[0]["output"] return test_input"""
This 10-line baseline achieves ~30% accuracy. GEPA will evolve it into a sophisticated system.
4
Create Your Evaluator
The evaluator runs the agent and returns a score plus diagnostic feedback:
import gepa.optimize_anything as oadef evaluate_agent(candidate: str, example: Dict) -> tuple[float, dict]: """ Evaluate agent on a single puzzle. Returns: (score, diagnostics): Score in [0, 1] and diagnostic information """ try: # Execute the agent code namespace = {} exec(candidate, namespace) solve_fn = namespace["solve_puzzle"] # Run on test input prediction = solve_fn( train_examples=example["train"], test_input=example["test_input"] ) # Score accuracy ground_truth = example["test_output"] correct = (prediction == ground_truth) score = 1.0 if correct else 0.0 # Capture diagnostics as ASI (Actionable Side Information) diagnostics = { "Correct": correct, "Prediction": str(prediction)[:200], "GroundTruth": str(ground_truth)[:200], "TrainExamples": len(example["train"]), } # Log for reflection if not correct: oa.log(f"Failed on puzzle {example['id']}: predicted {prediction}, expected {ground_truth}") return score, diagnostics except Exception as e: # Execution errors are valuable feedback oa.log(f"Execution error: {type(e).__name__}: {e}") return 0.0, {"Error": str(e)}
Key insight: The evaluator returns both a score AND diagnostic feedback. This Actionable Side Information (ASI) helps the LLM understand failures and propose fixes.
from gepa.optimize_anything import ( optimize_anything, GEPAConfig, EngineConfig, ReflectionConfig,)result = optimize_anything( seed_candidate=seed_agent, evaluator=evaluate_agent, dataset=train_puzzles, valset=val_puzzles, objective="""Optimize a Python agent that solves ARC-AGI puzzles. The agent should: - Analyze training examples to identify patterns - Apply learned rules to test inputs - Handle various transformation types (rotation, mirroring, color mapping, etc.) - Verify outputs when possible """, background="""ARC-AGI puzzles test abstract reasoning through grid transformations. Each puzzle has 2-4 training input/output pairs and one test input. Grids are 2D lists of integers (0-9 representing colors). Successful agents identify the transformation rule from examples. """, config=GEPAConfig( engine=EngineConfig( max_metric_calls=100, # Budget: 100 evaluations ), reflection=ReflectionConfig( reflection_lm="openai/gpt-4o", # Strong model for reflection reflection_minibatch_size=3, # Focus on 3 puzzles per iteration ), ),)print(f"\nOptimization complete!")print(f"Best validation score: {result.val_aggregate_scores[result.best_idx]:.2%}")print(f"Total iterations: {result.total_metric_calls}")
What happens during optimization:
GEPA evaluates the seed agent on training puzzles
Reflection LLM reads error messages and failed predictions
LLM proposes architectural improvements (new functions, better logic, etc.)
Improved agents are evaluated and selected via Pareto frontier
Process repeats, evolving increasingly sophisticated agents
7
Review the Evolved Architecture
Examine what GEPA discovered:
print("\nEvolved Agent Architecture:")print("=" * 60)print(result.best_candidate[:1000]) # First 1000 charsprint("...")print(f"\nTotal code size: {len(result.best_candidate)} characters")# Save the optimized agentwith open("optimized_agent.py", "w") as f: f.write(result.best_candidate)print("\nSaved to optimized_agent.py")
Example evolved architecture (from the ARC-AGI experiment):The agent evolved from 10 lines to 300+ lines including:
Rule induction: Analyzes training examples to extract transformation rules
Code generation: Generates Python code to apply rules
Iterative verification: Tests generated code on training examples
Multiple strategies: Tries direct LLM prediction if code generation fails
Structured fallbacks: Graceful degradation when rules are ambiguous
ASI is the text-optimization analogue of gradients. It tells the LLM why a candidate failed:
# Bad: Only return a scorereturn 0.35# Good: Return score + diagnosticsreturn 0.35, { "Error": "IndexError: list index out of range on line 47", "FailedTest": "puzzle_023", "Prediction": "[[1, 0], [0, 1]]", "Expected": "[[0, 1], [1, 0]]",}# Better: Include visual feedback for vision modelsfrom gepa import Imagereturn 0.35, { "Error": "Predicted grid has wrong rotation", "PredictionImage": Image(base64_data=render(prediction)), "ExpectedImage": Image(base64_data=render(expected)),}
Don’t know where to start? Use seed_candidate=None:
result = optimize_anything( seed_candidate=None, # GEPA bootstraps the first agent evaluator=evaluate_agent, dataset=train_puzzles, valset=val_puzzles, objective="Create a Python agent that solves ARC-AGI puzzles...", background="""Technical context: - Use numpy for grid operations - Available libraries: numpy, scipy, skimage - Grids are 2D lists of integers 0-9 - Must define: def solve_puzzle(train_examples, test_input) """, config=...,)
The reflection LM writes the initial agent based on your objective and background.