Era
Agent skills for Claude Code & skills.sh-compatible agents.
npx -y skills add mehdiforoozandeh/skills --skill eraAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Run ERA-style empirical-software search (Google ERA / Flat UCB Tree Search) to evolve whole programs toward a scalar objective. Use for software/algorithm DESIGN: an LLM writes and rewrites complete candidate programs, a sandbox scores each, and a flat PUCT bandit (FUTS) keeps a population and decides which to improve next — escaping local optima and returning a diverse portfolio of winners. Domain-agnostic. Triggers: ERA, FUTS, Flat UCB Tree Search, empirical software search, evolutionary program search, generate_fn/execute_fn.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
8.2 KB, as published. Nobody here has run it
ERA — empirical-software search via Flat UCB Tree Search
Faithful port of Google Research's ERA (Nature 2026, repo): an AI system that helps write high-quality empirical software. An LLM proposes whole candidate programs, a sandbox scores each, and a flat PUCT bandit (FUTS) decides which existing solution to improve next — keeping a population and escaping local optima instead of greedily climbing one trajectory. The candidate is the source code; ERA evolves the algorithm/model/pipeline, not a config.
What ERA is for: optimizing a problem that already has (a) a runnable seed program, (b) a fast automated scalar score, (c) a fixed problem definition. It evolves the code within that frame — it does not invent the frame. Skip ERA for problems with no automated score, or a single known change (just make it).
The engine (scaffold/futs.py — FROZEN, vendored verbatim)
futs.py is vendored verbatim from google-research/era
(implementation/futs.py, commit eb56676ce0a9b22b0c357c218de09ac61e94fc4b,
Apache-2.0). It is the neutral judge — never edit it during a search (that
invalidates all node scores and cross-run comparison).
Primitives:
Problem(description) # the problem statement + interface + scoring contract
Solution(program) # a complete, self-contained program (source text)
Node(index, parent_index, solution, score, num_visits, rank_score, puct)
search(problem, init_sol, init_score, generate_fn, execute_fn, num_iterations, c_puct=1.0)
generate_fn: (problem, parent_solution, parent_score) -> Solution
execute_fn: (problem, solution) -> float
PUCT selection each iteration picks the node maximising
rank_score + c_puct · (1/N) · √(total_visits) / (1 + num_visits), where
rank_score = rank/(N−1) ∈ [0,1] is the node's score normalised by rank, the
prior is uniform 1/N, and a new node's visit backpropagates up its ancestors
(backpropagate_visit). "Flat" = one global pool ranked by score; the only tree
structure is parent links + ancestor visit-backprop. The 1/N prior is
load-bearing: a constant prior makes exploration grow unbounded and never converge.
Concurrency + logging (scaffold/futs_batched.py — labeled extension)
Upstream ships only the sequential search() with no node hook. This separate,
non-upstream module adds, reusing the frozen primitives unchanged:
search(..., on_node=...)— identical behaviour tofuts.searchplus a per-node callback (for livetree.json). Parity is asserted inselftest.py.batched_search(..., batch_size)— selects B parents per round via virtual visits so B candidates are executed concurrently (B independent SLURM jobs). Generation stays sequential (one LLM call each); only execution is parallel.batch_size=1reduces to the sequential path.
The pieces you author (per problem)
Copy scaffold/ to a working dir. Only problem.py + config.yaml change.
| File | Role |
|---|---|
problem.py | get_problem() -> (Problem, initial_solution, initial_score). Start from problem_template.py. The Problem.description states the problem, the data/interface, the rules + fixed budget, and the exact scoring contract. initial_solution is a real runnable seed program (anchors the root); initial_score=None makes the driver score it once, or pass a float. No baseline? Ship a stub printing ERA_SCORE: -1e9 and cold-start. |
config.yaml | backend, exec_mode, num_iterations, batch_size, c_puct, SLURM knobs, paths. |
generate.py / execute.py | The generic generator/executor (below). Rarely need edits. |
Scoring contract. Every candidate program must run under the fixed budget and
print exactly one line ERA_SCORE: <float> (higher = better). Crash / OOM /
timeout / missing footer → executor returns -1e9 and FUTS abandons the node.
Define one clear objective in the problem and don't change it mid-search (it
invalidates cross-node comparison). The candidate runs in its own temp workdir;
it must be self-contained (no editing the scaffold or shared files).
Generator — subscription-authed CLI, with failover
generate_fn shells out to a terminal coding-CLI in headless mode, billing
your subscription (included tokens), never a metered API key. Both run with a
hard timeout and stdin-piped prompt (Cursor's -p can hang otherwise).
- Default: Claude / latest Opus —
claude -p --model opus --output-format text. - Failover: Cursor / latest Composer —
cursor-agent -p -m composer --output-format text. On a Claude rolling-limit hit (detected from output), switch for a cooldown, then re-probe and switch back once it recovers. - Check availability:
python generate.py --check.
Execution — SLURM job per run (default) or interactive (off-cluster)
sbatch_per_run(default). Each candidate is its own short SLURM job; a batch is submitted concurrently and harvested as jobs finish. All cluster specifics are config-driven:account,gres(omit for CPU clusters),cpus,mem,time_limit, andsetup_cmds(env/module lines). Nothing cluster-specific is hardcoded.interactive. Runs each candidate as a local subprocess — on a heldsallocallocation, or just locally for small problems / the regression example / selftest.
Running a search
# from this skill's own directory (where this SKILL.md and scaffold/ live):
cp -r scaffold <workdir> && cd <workdir>
cp examples/regression/problem.py ./problem.py # or write your own from problem_template.py
python selftest.py # verify the harness (no GPU/tokens/SLURM)
python generate.py --check # verify claude / cursor-agent are logged in
python run.py --config config.yaml
Output: tree.json (every node, rewritten live → resumable/inspectable) and a
printed top-k diverse portfolio — a set of strong, distinct programs, not just
the single best. A portfolio winner is a hypothesis: scores come off one
evaluation, so confirm a winner with a repeat run before trusting it.
Worked example
examples/regression/ — California Housing tabular regression (faithful to the
repo's playground_s3e1): the candidate is a whole program (load → features →
model → evaluate), scored by negative held-out RMSE. Runs locally with
exec_mode: interactive (no GPU/SLURM). Expect ERA to evolve the linear baseline
into feature engineering + gradient boosting.
Defaults & knobs
c_puct(default 1.0): lower → greedier exploit (→0 ≈ greedy hill-climb); higher → more breadth (too high → random thrash). Collapsing to one lineage → raise it; thrashing → lower it.batch_size(default 4): candidates per round = concurrent jobs (serial ininteractive).num_iterations: total candidates generated+executed.- Whole-program candidates are higher-variance than config knobs — expect many
-1e9(broken imports, shape errors, NaN). Keep the seed clean and the scoring contract crisp; raisec_puctmodestly so the search doesn't abandon the lineages that compile.
Anti-patterns
- Editing
futs.py(the frozen judge) or putting non-upstream logic inside it — extensions go infuts_batched.pyand reuse its primitives. - Non-self-contained candidates (editing the scaffold or shared files).
- A composite score with no clear main term, or changing the metric mid-search.
- Treating it as deep MCTS — it is flat UCB (one ranked pool; only ancestor visit-backprop, no value back-prop).