Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ReASearch: The Optimizer Is the Agent

Reasoning-driven Agentic Search across prompts, programs, and ML workflows.

Junbo Li1, Boyi Liu2, Canwen Xu2, Yite Wang2, Zhewei Yao2,
Zhangyang Wang1, Qiang Liu1, Yuxiong He2

1UT Austin Β  2Snowflake AI Research

Python 3.10+ Providers Tasks

ReASearch β€” the optimizer is the agent

Prior LLM-based optimizers slot the model into an external scaffold β€” bandits, evolutionary loops, textual gradients β€” that decides which candidate to branch from, when to verify, how to allocate budget, and when to give up. ReASearch removes the scaffold. A single tool-using reasoning agent owns the whole search policy: it analyzes outcomes, allocates its own budget, deliberately reverts unproductive branches, double-verifies promising gains, and adapts exploration as it goes. We instantiate the same agent on three substrates β€” prompts (e.g., AIME, GSM8K, HotpotQA), programs (e.g., Circle Packing, ARC-AGI), and ML training pipelines (e.g., image classification, NanoGPT, Atari/MuJoCo RL) β€” and across 14 tasks it consistently matches or outperforms specialized systems built around explicit search procedures.

Beyond results, the framework is built to be easy to extend and effectively controllable. Hooking a new optimization domain in is a handful of methods on a base class β€” give the agent a metric and an editable artifact (prompt, function, or training script), and the same reasoning loop, persistent memory, and search-review machinery just work (see Add your own task). Every lever on the search itself is exposed: budget caps (--max-total-evaluations, --max-agent-turns, --max-wall-clock), declarative constraints (no_change for forbidden moves, with checker-LLM enforcement), seeded prior knowledge (--lessons), and explicit search-tree review β€” so you can shape how the agent searches, not just whether it runs.



πŸ—οΈ Architecture

ReASearch/
β”œβ”€β”€ run.py                          # entry point β€” dispatches to task.run()
β”œβ”€β”€ config.py                       # global config (providers, budgets, paths)
β”‚
β”œβ”€β”€ core/                           # task-agnostic agent runtime
β”‚   β”œβ”€β”€ agent.py                    # the reasoning loop (one function, no controller)
β”‚   β”œβ”€β”€ context.py                  # auto-compaction; lessons.md survives
β”‚   β”œβ”€β”€ tools.py                    # python_exec / bash / read_file / write_file / list_files / compact
β”‚   β”œβ”€β”€ state.py                    # AgentState container
β”‚   β”œβ”€β”€ prompts.py                  # shared prompt utilities
β”‚   └── opencode.py                 # opencode CLI bridge for repo-mode editing
β”‚
β”œβ”€β”€ clients/                        # six LLM provider adapters with a unified message+tool interface
β”‚   β”œβ”€β”€ openai_client.py            # OpenAI
β”‚   β”œβ”€β”€ anthropic_client.py         # Anthropic
β”‚   β”œβ”€β”€ gemini_client.py            # Google Gemini (OpenAI-compat)
β”‚   β”œβ”€β”€ azure_openai.py             # Azure OpenAI
β”‚   β”œβ”€β”€ bedrock.py                  # AWS Bedrock
β”‚   └── cortex.py                   # Snowflake Cortex
β”‚
└── tasks/
    β”œβ”€β”€ base.py                     # BaseTask, search-review, shared helpers
    β”œβ”€β”€ eval_store.py               # evaluation caching (deduplication)
    β”œβ”€β”€ prompt_optimization/        # aime Β· gsm8k Β· hotpotqa Β· terminal_bench
    β”œβ”€β”€ program_evolution/          # arc_agi Β· circle_packing Β· heilbronn Β· eplb Β· txn_scheduling
    └── ml_workflow_optimization/   # nanogpt Β· market Β· atari Β· mujoco

πŸš€ Quick start

Prerequisites: Python 3.10+, uv (ML-workflow tasks use it for per-task Python envs), an LLM provider key (see LLM providers), and a CUDA GPU for ML-workflow and some program-evolution tasks.

# 1. Clone and install
git clone <repo-url>
cd ReASearch
pip install -r requirements.txt

# 2. Configure a provider
cp .env_template .env
# edit .env: set OPENAI_API_KEY=... (or ANTHROPIC_API_KEY=...) and AGENT_PROVIDER=openai

# 3. Run a task
python run.py \
    --task nanogpt \
    --max-agent-turns 1000 \
    --enable-search-review \
    --exp-name my_first_run

Outputs land in workspace/<task>/<timestamp>_<exp-name>/. Every prompt edit, every experiment, every checker decision is captured.

Optional β€” opencode for --repo-mode. With --repo-mode, an ML-workflow task edits a whole multi-file repo via opencode. Install with curl -fsSL https://opencode.ai/install | bash; it reads the same .env credentials you already set. Snowflake Cortex is the one provider that needs an extra step β€” run bash scripts/setup_opencode.sh once. See OPENCODE_SETUP.md.


🧩 Task families

Family What the agent edits Evaluator Tasks
Prompt Optimization A system or user prompt Student LLM on train/val/test split aime, gsm8k, hotpotqa, terminal_bench
Program Evolution A solve() function Direct execution against a problem instance arc_agi, circle_packing, heilbronn, eplb, txn_scheduling
ML Workflow Optimization A training script (single-file or full repo) uv run train.py reporting a metric nanogpt, market, atari, mujoco

Each family has its own README with task-specific setup notes. The same agent loop drives all three.


πŸ”¬ Selected results

Prompt optimization

Method AIME 2025 GSM8K HotpotQA Terminal-Bench 2.0
Baseline 46.00 81.20 63.00 35.56
GEPA 50.67 82.11 65.80 42.20
ReASearch 52.00 83.40 67.60 53.33

Here is an example. The agent grew the HotpotQA prompt from a one-line stub into a four-step CoT protocol with answer-type rules, span-extraction discipline, and a verification step:

Baseline (V1, test 63.0%) ReASearch best (V14, test 67.6%)
You are a question answering assistant. You will be given context documents and a question. Read the documents carefully and answer the question.

Output your final answer as ### <answer> on its own line.
You are an expert question answering assistant for multi-hop reasoning tasks…
Step 1 β€” Build the reasoning chain: trace fact A β†’ fact B β†’ final answer.
Step 2 β€” Determine the answer type from the question: Who… β†’ person; Which entity… β†’ that entity; How many… β†’ number; Is/Are/Was… β†’ yes/no only.
Step 3 β€” Extract the answer span: a direct quote, minimum phrase, no surrounding context.
Step 4 β€” Verify your answer: right type? right entity? not too much, not too little?

Scientific & algorithmic discovery

Three panels: a 31-circle packing in the unit square, the Heilbronn n=14 point placement with the minimum-area triangle highlighted, and an ARC-AGI test puzzle with the agent's exact-match output on the held-out test pair

Circle packing (sum of radii in unit square, Sonnet 4.6):

n Human best AdaEvolve ReASearch
26 2.635 2.630 2.636
30 2.842 2.843 2.843
31 2.889 2.887 2.890
32 2.939 2.810 2.940

Heilbronn triangle (minimum triangle area, Sonnet 4.6):

n Human best AdaEvolve ReASearch
12 0.03260 0.02552 0.03260
13 0.02702 0.02142 0.02700
14 0.02430 0.00299 0.02429

ARC-AGI-2 (120 puzzles, Sonnet 4.6, pass@2):

Train acc Test acc
AdaEvolve 21.9% 12.5%
ReASearch 85.0% 50.0%

Systems programming (best-of-paper, GPT-5 backbones; TXN: makespan score ↑; EPLB: balance Γ— speed ↑):

TXN EPLB
AdaEvolve 3,636 0.1976
ReASearch 4,237 0.2305

ML workflow optimization

MuJoCo HalfCheetah reward and cumulative tokens vs experiment number, ReASearch vs Claude Code
Task Direction Train budget Baseline ReASearch Discovered configuration
Image classification (from scratch) val acc 5 min 58.7 83.3 ResNet-34 widened [96,192,384,512] + AutoAugment + SWA + TTA + bs=512; the +14-pt jump came from the agent computing per-step wall-clock and realizing the cosine LR schedule never reached its low-LR phase in 5 min (estimated_epochs=100 fix)
Atari Q*bert (PPO) reward 30 min 475 18,600 48 envs Β· 8 PPO epochs Β· entropy=0.001 Β· 12 minibatches; the 5Γ— jump came from deepening FC layers (512β†’1024β†’512) after the agent observed that doubling envs from 8β†’16 left reward unchanged β€” learning efficiency, not data quantity, was the bottleneck
MuJoCo HalfCheetah (TD3) reward 10 min 3,947 7,840 TD3 bs=384, min(Q1,Q2) clipping, buffer=500k, reward_scale=5.2; agent benchmarked priority-sampling vs uniform np.random.randint, measured a 133Γ— slowdown, and rejected PER on computational grounds before wasting an experiment
NanoGPT (FineWeb-Edu) val_bpb 5 min 0.9929 0.9696 Muon momentum ramp + softcap=15 + RoPE/warmdown/weight-decay co-tuning, after the agent diagnosed batch-size co-dependence (lr 0.04β†’0.027 when bs goes 2¹⁸→2¹⁷)
Crypto (DRW Kaggle) val_corr (IC) 15 min 0.0953 0.1110 Ridge + MLP ensemble + per-feature winsorization (2.8–97.2 pctl) under purged time-series CV; within 15 experiments the same configuration moved the Kaggle leaderboard from 36th β†’ 6th

✨ Key features

The framework is small. The interesting part is the cognitive scaffolding around the agent β€” features that let the model itself drive search rather than be driven.

A coding sandbox for the agent β€” python_exec + workspace IO

The agent isn't just emitting candidates β€” it has a real workspace and a Python REPL. python_exec runs in a persistent namespace (variables, loaded dataframes, model handles all survive across turns), with json / re / math / statistics / collections pre-loaded and the cwd set to the run dir so open("logs/evals/...") just works. Pair that with workspace-scoped read_file / write_file / list_files and a sandboxed bash (120s timeout, dangerous-command deny-list), and the agent becomes a capable analyst β€” it inspects its own training logs, plots score histories, greps eval outputs, and writes helper scripts while it's deciding what to try next.

lessons.md β€” persistent memory across context compressions

Every run starts with an empty lessons.md in the workspace. The agent is instructed to read it at the start of each session and update it as it learns: what architectures or hyperparameters worked, what didn't, what to try next. Critically, the file is re-injected into the system prompt on every turn (core/context.py), so when the conversation gets compacted at long horizons, lessons survive. Without this the agent forgets ablation results around turn 200 and starts repeating itself.

Pass --lessons PATH to resume from prior memory β€” the file is copied into the new run's workspace at startup, so a follow-up run picks up where a previous one left off (or starts from a hand-curated set of takeaways). Useful for chaining long horizons, swapping models mid-search, or transferring insights from a sibling task.

--enable-search-review β€” agent-callable search-tree analysis

Adds a review_search_tree tool. When the agent calls it, the tool renders the experiment history as a structured tree with branch concentration, score progression, plateau signals, and a four-question analysis prompt (plateau? diversity? budget allocation? next action?). The agent typically calls it every 3-5 experiments. In our runs, the output drove real strategy decisions β€” e.g., "The search tree shows we're on the right track with ResNet-18 + AMP. The key insight is that larger models all fail because they run fewer epochs in 5 minutes."

### Search Tree
#attempt_0001  score=60.93  Baseline: ResNet-18, SGD, cosine LR
β”œβ”€β”€ #attempt_0002  score=45.85 [discard]  WideResNet-28-10 + AutoAugment + CutMix
└── #attempt_0003  score=63.75 β˜…  ResNet-18 + TrivialAugmentWide + CutMix + AMP
    β”œβ”€β”€ #attempt_0004  score=55.37 [discard]  ResNet-50 + TrivialAugment + CutMix
    └── #attempt_0005  score=60.83 [discard]  WideResNet-28-4 + TrivialAugment

Stagnation nudge β€” automatic intervention when nothing improves

tasks/program_evolution/prompts.py injects a ## Strategy Advisory section into the system prompt when no improvement has occurred for 3+ evaluations. After 7 evaluations of stagnation it escalates: "Your current strategy isn't working β€” try something fundamentally different." Each task can also override _stagnation_suggestions() to add domain-specific kick-out advice. This is the difference between an agent that grinds productively and one that loops on local optima.

no_change constraint enforcement

A task can declare a no_change property β€” a free-form natural-language constraint the agent must respect. The constraint is shown to the agent in its system prompt and every code edit is reviewed by a checker LLM that compares the diff against the rule. Violating diffs are auto-reverted with feedback to the agent; decisions log to logs/no_change_checks.jsonl. Concrete uses:

  • Image classification (from scratch): forbid pre-trained weights / transfer learning.
  • market: ban temporal leakage in time-series feature engineering.

In practice the rule shapes search up-front: the agent reads the constraint section and plans around it, rather than discovering it via repeated reverts.

--repo-mode β€” multi-file editing via opencode

For ML-workflow tasks that ship multi-file repos rather than a single train.py, the agent uses understand_repo and edit_repo (powered by opencode) instead of edit_train_file. The same multi-file pattern is also a natural fit for program-evolution problems whose solution is structurally complex β€” for example, evolving a search algorithm whose state, heuristic, and scoring components are easier to reason about as separate modules, or a multi-stage solver (preprocess β†’ core β†’ post-process) where the agent benefits from editing each stage independently rather than threading every change through a single solve() function. (Opencode reads its credentials directly; for OpenAI / Anthropic / Gemini / Bedrock / Azure this is the same env keys you've already set. Snowflake Cortex is the one provider that needs an extra step β€” see OPENCODE_SETUP.md.)

Context compression that doesn't lose state

core/context.py automatically compresses the conversation when context grows large. state_summary() provides the authoritative state β€” current experiment count, budget remaining, recent score progression, and the best candidate β€” that survives compression. The agent never wakes up amnesic.


πŸ› οΈ Add your own task

Hooking a new domain into ReASearch is the main supported extension path. The agent loop is task-agnostic; you only specify a few methods.

Family Base class Core methods you implement
Prompt Optimization PromptOptTask load_dataset(), default_prompt(), extract_answer(), score_answer()
Program Evolution ProgramEvoTask load_problem(), default_code(), evaluate(code, problem, timeout)
ML Workflow MLWorkflowTask metric_name, metric_direction, template_files(), verify_data()

Plus a system_prompt_task_section() that gives the agent task-specific guidance, and a one-line registration in tasks/__init__.py. You can optionally override no_change to gate edits, _stagnation_suggestions() for domain-specific stuck advice, or post_run_evaluation() for held-out scoring. SKILLS.md is written as a brief that an AI coding agent (Claude Code, Codex, etc.) can read end-to-end and use to scaffold a new task in this repo without further help β€” point your coding assistant at it.


πŸ€– LLM providers

Set AGENT_PROVIDER in .env:

Provider Value Auth opencode (repo-mode)
OpenAI openai OPENAI_API_KEY works out of the box
Anthropic anthropic ANTHROPIC_API_KEY works out of the box
Google Gemini gemini GEMINI_API_KEY works out of the box (via OpenAI-compat endpoint)
Azure OpenAI azure_openai AZURE_ENDPOINT + AZURE_API_KEY works out of the box
AWS Bedrock aws_bedrock AWS creds + AWS_REGION works with Bedrock invoke perms
Snowflake Cortex cortex SNOWFLAKE_PAT needs scripts/setup_opencode.sh

See .env_template for all configuration options.


πŸŽ›οΈ Common flags

Flag Description
--task NAME Which task to run
--max-total-evaluations N Evaluation budget (default 500)
--max-agent-turns N Maximum agent reasoning turns
--max-wall-clock SECONDS Wall-clock limit
--lessons PATH Seed agent with an existing lessons.md
--enable-search-review Give the agent the review_search_tree tool
--repo-mode Multi-file editing via opencode (ML-workflow or program-evolution tasks with a repo template)
--exp-name NAME Tag the run directory
--agent-model-id MODEL Override agent model

Each task may add its own flags; run python run.py --task <name> --help to see them.


πŸ“¦ Outputs

Every run produces a workspace directory:

workspace/<task>/<timestamp>_<exp-name>/
β”œβ”€β”€ repo/                                # task working tree (where edits land)
β”œβ”€β”€ experiments.json                     # full kept/discarded experiment history
β”œβ”€β”€ lessons.md                           # agent-curated insights
β”œβ”€β”€ search_review.md                     # latest search-review snapshot (if enabled)
└── logs/
    β”œβ”€β”€ agent.log                        # reasoning trace
    β”œβ”€β”€ tool_calls.jsonl                 # one JSON per tool call
    β”œβ”€β”€ turn_log.md                      # human-readable turn-by-turn summary
    β”œβ”€β”€ code_snapshots/                  # before/after diffs of each edit
    β”œβ”€β”€ runs/exp###_run.log              # per-experiment train.py output
    β”œβ”€β”€ opencode_runs/                   # (repo-mode only) opencode subprocess logs
    └── no_change_checks.jsonl           # (constrained tasks only) checker decisions

πŸ“š Documentation

Doc Audience
README.md Project overview (you are here)
SKILLS.md Brief for an AI coding agent adding a new task to this repo
OPENCODE_SETUP.md opencode auth setup (Cortex-specific path)
tasks/*/README.md Per-task-family setup notes

πŸ“œ Citation

If you use this framework in research, please cite:

Li, J., Liu, B., Xu, C., Wang, Y., Yao, Z., Wang, Z., Liu, Q., He, Y. The Optimizer Is the Agent: Reasoning-Driven Search across Prompts, Programs, and ML Workflows. Preprint, 2026.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages