climbers / selector-policyhillclimb

2. Selector policy

The first decision of every step, which node the next attempt starts from.

The selector policy (πsel) reads the history and answers one question: which node does the next attempt start from? Its answer is one of four things:

  • a failing candidate, which gets repaired first,
  • nothing, which makes the step a root step (a fresh draft),
  • a scored candidate to build on,
  • the top few candidates, to be combined into one in the final part of the budget.

The base class handles that schedule. A selector of your own only decides the third case, in select:

# oldest.py
from hillclimb.sdk import Selection, SelectorPolicy


class Oldest(SelectorPolicy):
    """Build on the oldest scored candidate."""

    def select(self, state, *, busy=frozenset()):
        scored = state.journal.scored_candidates()
        return Selection(scored[0].candidate_id, prompt_context="Oldest first.") if scored else None
climber: {selector_policy: oldest.py, operator_policy: greedy}

select returns a Selection: the node to build on, and whatever comes with it. That can be inspirations (other candidates copied in beside it), a paragraph for the prompt, and a note recorded on the new candidate. None means there is nothing to build on yet, so the step is a root step. busy holds the candidates an attempt in flight is already working on.

The schedule's settings

These belong to every selector policy and go in selector_params:

settingdefaultwhat it does
num_drafts3root candidates before anything is built on
debugtruerepair failing candidates at all
max_debug_depth3failed fixes in a row before a failing chain is left
ensembletruecombine the top candidates in the final window
ensemble_reserve_fraction0.2the final share of the budget kept for it
ensemble_top_k3how many candidates are combined
ensemble_max_attempts2how many combinations are tried

The built-in ones

  • best builds on the best scored candidate nobody is building on yet, so parallel attempts spread over the top few.
  • map-elites keeps OpenEvolve's MAP-Elites archive: a population spread over feature dimensions, on islands with migration, with a parent and inspirations sampled per island. It takes OpenEvolve's settings (num_islands, feature_dimensions, population_size, random_seed, …) and num_inspirations. The built-in feature dimensions are complexity, diversity and score; any other must be a number the verifier reports beside the score.

A selector that keeps state rebuilds it from the history in sync(state), so a resumed search makes the same choices a live one would.