Optimizes any text artifact (prompts, code, configs) via LLM-driven search. Write an evaluator, switch backends (GEPA, AutoResearch, MetaHarness) with one argument.
How this skill is triggered — by the user, by Claude, or both
Slash command
/gepa-optimize-anything:gepa-optimize-anythingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
**Naming, precisely.** `optimize_anything` is the tool: a general API for optimizing text
optimize_anythingNaming, precisely. optimize_anything is the tool: a general API for optimizing text
artifacts. GEPA is one specific optimizer behind it — reflective evolutionary search, the
default backend (engine="gepa") — and, for legacy reasons, also the name of the Python package
that ships all of this. In this skill, "the gepa backend" always means the optimizer; statements
about "the optimizer" or "the backend" apply to whichever engine you chose.
optimize_anything does black-box optimization: you provide (1) a seed artifact, (2) an
evaluator that scores any artifact and returns feedback, and (3) a backend, which repeatedly
proposes improved artifacts and scores them through your evaluator. "Black-box" refers to the
evaluator, not the artifact: the backend never sees how the score is computed — no gradients,
no metric internals — only the scalar score and the feedback text you emit. The candidate itself
is visible: the proposer reads and rewrites it, applying the LLM's understanding of your artifact.
The framework just imposes no structure on it — any string an evaluator can score works. The
leverage is in your score and your feedback.
You write the task and evaluator once, then choose the search algorithm with one engine
argument — and the same code runs under any of them:
gepa — the GEPA optimizer: reflective evolutionary search, in-process (an LLM reflects on
feedback and mutates candidates; keeps a Pareto frontier). The default; strongest when feedback
is rich.autoresearch — an agentic optimizer: one Claude Code subprocess iterates like a researcher in
a work dir, scoring candidates through an HTTP eval server.meta_harness — an agentic proposer (Claude subprocess) that reads the frontier/history each
iteration and writes new candidates for the engine to benchmark.(There is also a best_of_n engine — sample N independent candidates, keep the best. It is
deliberately naive: use it as a baseline to compare an optimizer against, not as the optimizer.)
This makes it easy to start with one backend and benchmark others on the identical task/evaluator.
There are also composition/pipeline helpers that combine backends over the same task:
optimize_sequential (a pipeline — each stage's best seeds the next), optimize_parallel,
optimize_best_of, optimize_vote (re-score each branch's best for a fair pick), and an adaptive
scheduler that rotates backends on score plateaus — see references/api.md.
A candidate is any string your evaluator can score. "Text in → a number out (higher is better), plus optional feedback" is the entire contract, which covers a wide range of artifacts:
The score can be an objective metric (accuracy, pass rate, runtime, cost) or an LLM-as-judge
rating for subjective tasks (writing quality, helpfulness, style). At this API the candidate is a
single string (seed_candidate: str | None; None = seedless — the engine bootstraps from
objective/background). Multi-component dict candidates exist only in the lower-level
gepa.gepa_launcher.optimize_anything API, not here.
The mode is determined by whether you provide dataset and valset:
dataset=None, valset=None) — solve one hard problem; the candidate is the
solution; the evaluator is called with no example. E.g. one CUDA kernel, a circle-packing layout.dataset=<list>, valset=None) — solve a batch of related problems with one shared
candidate, transferring insight across them; evaluator called per example. E.g. a single prompt
that works across many tasks.dataset=<list>, valset=<list>) — build a candidate that transfers to
unseen problems; optimize on dataset, select on valset. E.g. a prompt tuned to generalize.
Note: GEPA is the algorithm designed around this mode, and valset-based held-out selection is
currently implemented only by the gepa backend — the other backends fold valset into the
training pool (they can still generalize; there's just no separate selection split).test_set is separate from the modes and reporting-only: the seed and the final candidate are
scored on it after optimization for an unbiased number — it never enters the search, selection, or
budget (for the agentic backends it is sealed at the eval server's HTTP layer, so the agent cannot
even see it). See references/api.md for details and when to use each mode.
pip install "gepa[full]" # [full] pulls cloudpickle — needed to pickle closure evaluators for
# parallel workers / opt-in evaluation caching; plain `pip install gepa`
# can fail there when your evaluator closes over data.
# Proposer LLM: the gepa backend's reflection LM defaults to "openai/gpt-5.1" (a LiteLLM id) — set
# that provider's key (OPENAI_API_KEY), or pass your own id (e.g. "anthropic/claude-sonnet-4-6" with
# ANTHROPIC_API_KEY, or a Bedrock ARN with AWS creds). You can also pass any callable implementing
# GEPA's LM protocol — a self-hosted / custom inference engine — instead of a model-id string.
# Agentic backends (autoresearch, meta_harness) additionally need the `claude` CLI on PATH (plus
# `jq` for the generated eval.sh):
npm install -g @anthropic-ai/claude-code # or: curl -fsSL https://claude.ai/install.sh | bash
# ...then run `claude` once to authenticate.
# On Linux they also need bubblewrap: the default sandbox=True jails the claude subprocess with
# bwrap (`sudo apt install bubblewrap` / `sudo dnf install bubblewrap`) and the run aborts at
# launch if it's missing. sandbox=False opts out — the agent then runs unsandboxed (loud warning).
str). The seed is the start; None = seedless.evaluate(candidate, example) -> (score, info) — score is a float (higher is better);
info is a free-form dict of feedback the backend's proposer reads to make better candidates
(the gepa backend's reflection LM, or the agentic backends' agent; the best_of_n baseline
ignores feedback). When evals batch better than they stream (e.g. a provider batch API), pass
batch_evaluator= instead — all pending (candidate, example) pairs in one call; see
references/api.md."gepa" (default), "autoresearch", "meta_harness", the "best_of_n"
baseline, or a constructed Engine instance.max_evals (server-side eval-call cap, default 100) and/or max_token_cost
(USD cap on the backend's own proposer-LLM spend). Setting both to None makes the run unbounded
(only a warning). Size max_evals for many proposal rounds, not one (see below) — this is the
most common way agents misuse this API.max_evals is the main control over how long the optimizer runs. Leave it at the default (100)
with a large valset, or set it too low, and the run stops after a single proposal, then reports
a "best candidate" that looks fine but is barely optimized.
max_evals off that set, not off a single proposal. Which set that is depends on the mode:
generalization (dataset + valset): max_evals ≳ 15–20 × len(valset) # scored & selected on valset
multi-task (dataset only): max_evals ≳ 15–20 × len(dataset) # scored & selected on dataset
single-task (no dataset/valset): max_evals ≳ 15–20 # 1 eval per candidate, so this
# IS the number of proposals
The constant is the same everywhere — let the backend propose AND evaluate ~15–20 candidates
(more if you can afford it). Anything much less and the run tries only a couple of candidates —
i.e. it's barely optimizing. (This arithmetic is exact for the gepa backend — and the best_of_n
baseline — which score every candidate on the full selection set; the agentic backends decide
themselves how to spend eval calls, so treat it as a floor.)After the run, check how many proposals actually happened (on the gepa backend,
result.metadata["gepa_result"] holds every candidate; with engine.write_agent_state=True the
run_dir/iterations/ tree shows each one). If it stopped after one proposal, the budget was too
low — raise it and rerun.
max_evals bounds eval calls, but add explicit stops so runs end at the right moment:
stop_at_score — set it whenever your metric has a known ceiling (e.g. 1.0 for a pass rate /
accuracy). The backend stops the moment a candidate reaches it instead of burning the rest of the
budget at the optimum.max_token_cost — a hard USD cap on the backend's own proposer/agent LLM spend. Especially
important for the agentic backends (autoresearch, meta_harness), whose Claude subprocesses
spend tokens between eval calls.timeout on the process you launch (e.g. timeout 1200 python run.py) as a
backstop.engine_config={"engine": {"cache_evaluation": True}} on
the gepa backend — it is off by default), be aware max_evals then counts only cache misses:
a converged search can keep proposing cache-hitting candidates without consuming eval budget, so
stop_at_score/max_token_cost become mandatory, not optional.The example optimizes a system prompt for concreteness, but the shape is identical for any
candidate — swap SEED for a code file / config / etc. and have evaluate compile/run/measure it.
from gepa.optimize_anything import optimize_anything, OptimizeAnythingConfig
SEED = "You are an expert. Solve the task. Output only the final answer."
def evaluate(candidate: str, example) -> tuple[float, dict]:
output = run_my_model(system_prompt=candidate, user_prompt=example["prompt"]) # your call
score = grade(output, example) # float, higher=better
return score, { # everything here is shown to the proposer LLM
"score": score,
"output": output,
"error": example.get("error"), # concrete, actionable feedback drives good proposals
}
result = optimize_anything(
seed_candidate=SEED,
evaluator=evaluate,
dataset=trainset, # optimize on these (multi-task/generalization mode)
valset=valset, # select the best candidate on these (generalization mode)
test_set=testset, # OPTIONAL, reporting-only: seed + final candidate scored here at the end
objective="Produce a prompt that maximizes task accuracy.",
background="Domain rules, constraints, output format the model must follow.",
config=OptimizeAnythingConfig(
engine="gepa", # swap to "autoresearch" / "meta_harness" — same code
# ("best_of_n" runs the same way, as a comparison baseline)
name="my_run",
max_evals=300, # ≳ 15-20 × len(valset): enough for ~15-20 proposals (see above)
stop_at_score=1.0, # stop at the optimum (set when your metric has a known ceiling)
max_concurrency=16,
run_dir="runs/my_run", # engine workspace (gepa run dir / agent work dir)
output_dir="outputs/my_run", # eval server: per-eval JSON, progress_log.jsonl, summary.json
engine_config={ # gepa backend: a GEPAConfig-shaped dict, validated strictly —
"reflection": { # an unknown key raises TypeError immediately (fail fast)
# a LiteLLM id (set the provider key) OR any callable implementing the LM protocol
"reflection_lm": "anthropic/claude-sonnet-4-6",
"reflection_minibatch_size": 5,
},
"engine": {"max_workers": 32, "seed": 0}, # seed = reproducibility
},
),
)
print(result.best_candidate, result.best_score)
# held-out (only present if you passed test_set): "test_score" = average, "test_scores" = per-example
print("held-out:", result.metadata.get("test_score"),
"seed held-out:", result.metadata.get("baseline_test_score"))
dataset/valset you pass.references/gotchas.md, reward hacking).evaluate. The info dict is the proposer's signal — return errors,
diffs, partial credit, not just a number (oa.log() and capture_stdio can route diagnostics in
automatically). See references/writing_evaluators.md.max_evals sized per above, and/or max_token_cost) plus stop_at_score when
the metric has a ceiling.python scripts/preflight.py to fail fast on missing creds / CLI before a long run.result.best_candidate and run_dir/ (and result.metadata["test_score"] if you
passed a test_set).These silently degrade results — skim before launching:
references/gotchas.md.valset — an optimistic estimate. Use enough valset examples (and N>1 for stochastic models), and
report on a test_set for an unbiased number. → references/gotchas.md.evaluate. → references/writing_evaluators.md.dataset has real
failures. → references/gotchas.md.engine_config is validated strictly per backend — an unknown key (including a leftover key
from a different backend after swapping engine=) raises TypeError at construction. Swapping
engine= means swapping the engine_config block. → references/api.md.autoresearch, meta_harness) shell out to the claude CLI and abort at
launch with install instructions if it's missing — likewise for bwrap (bubblewrap) on Linux,
which the default sandbox=True needs. sandbox=False runs the agent unconfined (loud warning).
Run scripts/preflight.py first; details in references/api.md.references/api.md — OptimizeAnythingConfig, the backends and their typed engine_config
options, the three modes, the LM protocol, budget/cost semantics, Result shape, and the
composition/pipeline helpers.references/writing_evaluators.md — the (score, info) contract, oa.log()/capture_stdio,
LLM-as-judge scoring, multi-objective via info["scores"], N>1 averaging, feedback design.references/tracking.md — enabling wandb / mlflow experiment tracking and what gets logged.references/gotchas.md — reward hacking, selection bias, the three modes, backend prerequisites.scripts/preflight.py — validate creds / proposer LM / claude CLI before launching.npx claudepluginhub gepa-ai/gepa --plugin gepa-optimize-anythingOptimizes text artifacts (code, prompts, configs, agent architectures) using GEPA's reflective evolutionary search with a declarative API.
Runs autonomous optimization loops to iteratively improve prompts, templates, configs, or code using four-way separation of main agent, eval agent, test runner, and deterministic eval.py judge. Invoke via /autoresearch or 'optimize this prompt'.
Systematically optimizes prompts using an eval set and search methods (instruction tuning, few-shot selection, decomposition) instead of manual tweaking.