TL;DR
AutoEnergyDesign is a setup where an LLM agent hunts for the best configuration of a complex system in a loop. The agent reviews the history of past runs, proposes a single change and fires up the simulator. If the result improves, the change stays. If it gets worse, git rolls it back automatically. The “keep or roll back” call is made by the environment, not the model. The agent can't just decide that things look “pretty good overall.” Everything it remembers lives in files: an instruction file, domain knowledge, an experiment log and a reflection note.
Classic optimizers (random search, Bayesian optimization, evolutionary algorithms) are flying blind: they need hundreds of runs. When a single run costs minutes or hours, that gets expensive. On top of that, they can't explain why a particular configuration won. An LLM agent starts in a sensible region because it knows the domain, and it explains every step. In the paper, the agent got within 1% of the best design in 18–27 runs. Among classic methods, only the evolution strategy pulled that off: in half of its runs and after about 190 runs.
The method boils down to three things. One: split memory into “how to search” (the instruction file), “what's true in this domain” (the knowledge file) and “what we've already tried” (the log). Two: force the agent to write its reasoning before the run and check it against the result afterward. Three: hand the rollback decision to a script, not to the model.
Want this for your own case? Apply this study to your task: AI adapts its method to what you are working on and writes the prompt.
Method Blueprint
SETUP: instruction file (how to search) + knowledge file (what's true in the domain)
+ log (what we've tried) + reflection note
LOOP until the run budget is spent:
STEP 1: Read the instructions, knowledge, best design, history
(+ the last K rejected candidates)
STEP 2: Write the hypothesis and expectation to the log → BEFORE the run
STEP 3: Change the configuration via a restricted set of actions
STEP 4: Run the simulator → get a number (cost)
STEP 5: The environment decides: better → commit, worse → revert (git)
STEP 6: Write to the log what actually happened
Every Nₐ experiments: write a reflection note
(what we learned, which patterns are emerging)
This is a single agent loop per session. The “keep or roll back” decision lives outside the model.
Example in Practice
The method shines where there's a number a script can compute and several parameters you can tweak. Runs should be expensive enough that blind brute force is off the table. Copywriting and creative work, with their subjective scoring, are a poor fit.
Task: A coffee shop founder in Austin is building a unit-economics model in calc.py. Parameters: number of locations (1–6), average ticket, baristas per shift, Google Ads budget, and rent as a share of revenue. The metric is net profit over 3 years, provided the cash balance never dips below zero. He wants Claude Code to pick the parameters on its own and show why it chose them.
Prompt (goes into program.md, run in Claude Code):
<Role>
You are an optimizer agent for the unit economics of a coffee shop chain in Austin.
Your goal: maximize net profit over 3 years (the output of calc.py),
subject to one constraint: the cash balance never drops below 0.
Budget: 40 runs of calc.py.
</Role>
<Memory>
- program.md — this instruction file (how to run the search). Do not edit.
- insights.md — what's true about our business (edited by a human).
- log.md — experiment log: hypothesis → result → conclusion.
- notes.md — your reflection note (update it every 5 experiments).
</Memory>
<Actions>
You may ONLY:
1. Change values in config.yaml (within the allowed bounds).
2. Run: python calc.py
3. Append to log.md and notes.md.
Do not touch anything else.
</Actions>
<Loop>
Before each experiment:
1. Read insights.md, notes.md, the best config and the last 5 rejected ones.
2. In log.md write: “Experiment k. Changing: ... Because: ...
Expecting: profit to rise/fall by roughly ...”.
3. Edit config.yaml and run calc.py.
4. The “keep or roll back” decision is made by the decide.sh script
(beats the best → git commit, otherwise → git revert).
You do not bypass it or argue with it.
5. Append to log.md: “Actual: ... Conclusion: hypothesis confirmed/not, because ...”.
Change 1–2 parameters at a time so it's clear what caused the effect.
Do not repeat candidates from the rejected list.
</Loop>
<Output>
At the end: the best config, the profit, and a short breakdown — which 3 decisions
mattered most and which hypotheses didn't hold up.
</Output>
Result: The agent works through a series of experiments. Before each one, a hypothesis with an expectation shows up in log.md; after it, the actual outcome and a conclusion. Failed changes roll back automatically, so the best version always sits in the repo. By the end, notes.md fills up with observations like “which parameters matter most.” The final answer is the best config plus a breakdown of what worked and what didn't.
Why It Works
The weakness. If you simply ask a model to “find the optimal parameters,” it will give you a plausible answer, not a verified one. It can't predict the output of a complex model, can't reliably judge on its own whether things got better or worse, and tends to “justify” its latest decision. Classic optimizers are honest but blind: they need hundreds of tries just to feel out a direction.
The strength. An LLM is good at proposing meaningful changes based on a description of the domain and the history of attempts: “the battery is pricey, let's try without it.” It juggles many constraints at once and explains its thinking in plain language. So the search starts in a sensible region instead of with blind guessing.
How the method ties it together. The creative part (proposing a step) belongs to the model. The control part (better or worse) belongs to numbers from the simulator and git. A log with the hypothesis “before” and the facts “after” keeps the model from retrofitting explanations to the result. Splitting memory lets a human edit the knowledge in insights.md without touching the instructions.
Control knobs: - Run budget (50 in the paper) → fewer for cheap problems, more for hard ones. - Number of rejected candidates in the history (K) → more means the agent repeats itself less, but the context grows. - Reflection frequency (Nₐ) → more often means the agent draws conclusions faster, but burns more tokens. - Knowledge file → add expert rules (“downtown rent in our city never drops below X”). The search narrows. - Set of allowed actions → the tighter it is, the less chance the agent breaks something it shouldn't. - Model → the stronger the model, the more reliably it lands on the best option (see “How They Tested It”).
Prompt Template
The paper describes the method through its memory structure and loop, so the template below is reconstructed from that description. The authors' verbatim system prompt isn't in the available part of the text. The structure is preserved: procedural memory, semantic memory, episodic memory with a decision log, a reflection note and a restricted action set.
<Role>
You are an optimizer agent for this task: {task}.
Goal: {minimize/maximize} {metric}, subject to the constraints: {constraints}.
Budget: {number} runs of {run_command}.
</Role>
<Memory>
- program.md — procedural memory: how to run the search (this instruction file). Do not edit.
- insights.md — semantic memory: what's true about {domain}.
Read it before every experiment.
- log.md — episodic memory: the decision log. Each entry = hypothesis before the run + facts after.
- notes.md — reflection note: your conclusions about patterns.
Update it every {Na} experiments.
- Working context: the best valid candidate and the last {K} rejected ones.
</Memory>
<Actions>
You may ONLY:
1. Change {what_to_change} in {config_file} within {bounds}.
2. Run {run_command}.
3. Append to log.md and notes.md.
Everything else is off-limits.
</Actions>
<Loop>
Until the budget is spent:
1. Read insights.md, notes.md, the best candidate, the last {K} rejected ones.
2. Write to log.md BEFORE the run: what you're changing, why, what result you expect.
3. Make the change and run it.
4. The “keep/roll back” decision is made by {rollback_mechanism} based on the metric.
You do not bypass it or argue with it.
5. Write to log.md AFTER the run: the facts, whether they matched your expectation, the conclusion.
6. Every {Na} experiments, update notes.md.
</Loop>
<Output>
The best candidate, its metric value and a breakdown: which changes mattered most,
which hypotheses didn't hold up.
</Output>
What to fill in:
- {task}, {metric} — what you're optimizing and how a script computes it.
- {run_command} — the script that outputs a number.
- {rollback_mechanism} — a simple script: “better → git commit, otherwise → git revert.” You can have the model write it for you.
- {Na}, {K} — for example, 5 and 5.
🚀 Quick start — paste this into a chat (ideally Claude Code or Cursor):
Here's a template for an optimizer agent. Adapt it to my task: [your task].
Ask me questions to fill in the fields.
[paste the template above]
The LLM will ask how the result is measured (which script outputs the number), what can be changed and within what bounds. It will also ask which expert rules to put into insights.md and what the run budget is. Without these, the loop won't work: it needs an honest metric, permitted levers and domain knowledge. It will take the pattern from the template and build the files and the rollback script for your task.
Limitations
⚠️ You need an honest metric: the method works when the result comes from a script or a simulator. If the score is subjective (a text you “liked”), the keep/roll-back loop loses its point.
⚠️ Not a replacement for an exact solver: the authors themselves note that if the problem can be written as mixed-integer linear optimization, an exact solver gives an optimality guarantee the agent doesn't have. The agent is for cases where a formulation isn't feasible or the simulator is a “black box.”
⚠️ Reliability depends on the model: a weak model gets close but rarely hits the target exactly. The mid-tier model failed to get near the best option at all in some runs. The strong one succeeded in every run. The costlier a mistake, the stronger the model you need.
⚠️ A single problem: testing covered one case (one building, one dataset). Transfer to other domains is plausible but untested in the paper.
⚠️ Component contributions aren't isolated: the available text has no ablation showing what each memory element contributes on its own. The whole agent was compared against classic methods.
⚠️ Truncated text: in the input, the methodology is cut off and the results and discussion sections aren't visible. The authors' exact instruction wording can't be verified. The conclusions rely on the abstract and introduction.
How They Tested It
The team took on the problem of choosing a building's energy system: eight technologies (solar panels, wind turbines, batteries, heat pumps and others), measured load profiles, and a levelized cost calculation over 25 years. One run is a year-long hourly simulation. The agent was run on three Claude models of different strength (Haiku, Sonnet, Opus), with 10 runs each and a budget of 50 simulations.
It was compared with three baselines at 300 evaluations across 10 random seeds: random search, Bayesian optimization (TPE) and a (1+1) evolution strategy with the same acceptance rule as the agent (“better — keep it”). That makes the comparison fair: it shows what the model's smarts contribute, as opposed to the keep/roll-back rule itself.
All three models landed at a cost of about €3.53 million. That's 1.2% better than TPE and 0.23% better than the evolution strategy. Here's where it gets interesting: the agent gets within 1% of the best design in a median of 18 runs (Opus) to 27.5 (Haiku). The evolution strategy manages this in only half of its runs and after 192 runs. The other methods never got there.
The surprise is in reliability. Opus found the best design in all 10 runs. Sonnet did in six, and in two runs it didn't get within 1%. Haiku got within 1% in every run but hit the exact optimum only once. The practical takeaway: a weak model consistently delivers “almost good,” while precision and reliability come from strong models. And the gains aren't necessarily monotonic: the mid-tier model stumbled more often than the weakest one.
Adaptations and Extrapolations
🔧 Technique: numeric expectations → agent calibration
In the authors' setup, the agent justifies an experiment before running it and checks it against the outcome afterward. You can tighten that check by requiring a concrete number:
Before the run, write down your forecast for the metric as a number (e.g., “profit +3–5%”).
After the run, add how far the forecast was from the actual result and a correction:
“what I got wrong and what it means for the next hypotheses.”
If the forecasts keep missing, the agent doesn't understand the problem. That's your cue to beef up insights.md.
Extrapolation: a cheap model for scouting, a strong one for the finish. This isn't from the paper; it's an inference from its results: Haiku reliably gets close, Opus hits the target precisely. You can build a two-stage setup. First, the cheap model runs the loop and builds up log.md and notes.md. Then the strong model picks up with that history and polishes the result:
You are continuing the search after a previous agent. Read log.md and notes.md.
Do not repeat candidates that were already tested. Your task: improve the best candidate found
within {number} runs, testing hypotheses the previous agent didn't get to.
The paper didn't test this setup, so validate it on your own problem.
Resources
- Paper: AutoEnergyDesign: An LLM-Driven Agent for Automatic Energy System Design Optimization
- Authors: Amedeo Buonanno, Salvatore Fabozzi, Maria Valenti — Department of Energy Technologies and Renewable Sources, ENEA, Portici (Italy)
- References: AutoResearch (Karpathy) — the prototype of the loop with git-based rollback; OPRO (Yang et al.); FunSearch; AlphaEvolve; LLAMBO; OptiMUS; Eureka; ReEvo