python / resultshillclimb

Results

After search, what the search found reads off the climber. The full record is a SearchOutcome.

After search, what the search found is on the climber, the way a fitted model carries its coefficients.

from hillclimb import Budget, Climber, Problem
from hillclimb.policies import Greedy

problem = Problem("fitness-landscape")
budget = Budget(evaluations=30)
climber = Climber(operator_policy=Greedy())

climber.search(problem, budget=budget)

climber.best                  # the best candidate by validation score
climber.selected              # the one that ships. Usually the same, unless a holdout split disagrees
climber.candidates            # every candidate, in the order they were created
climber.history               # [(minutes, candidate_id, score), ...] for each time the best went up
climber.spend                 # evaluations, tokens, cost_usd, seconds
climber.solution              # the source that ships (best/solution.py)
climber.params                # the parameter values it ran with
climber.to_frame()            # a pandas table, one row per candidate
climber.result                # the whole SearchOutcome, for the rest

These always refer to the latest search the climber ran, with start as well as search. While a stepped search is still open they are a live reading.

The full record

climber.result is a SearchOutcome, and so is what run, climber.finish() and climber.close() return. It is the search in a form you can read without going through runs/ by hand. It reads through the store like every other viewer, it never writes, and the journal you get from it refuses writes too.

outcome = climber.result

outcome.state                 # done, parked or stopped
outcome.ref                   # the <run-id>/<search-id> every CLI command accepts
outcome.source("c004")        # any candidate's solution.py
outcome.journal               # the full journal, read only
outcome.meta                  # the search's record: problem, metric, climber block, budget
attributewhat it is
state, errordone, parked or stopped, and the reason when it is not done
ref, run_dir, search_dirthe <run-id>/<search-id> every CLI command accepts, and where the search lives on disk
bestthe best scored candidate by validation score
selectedthe candidate that ships. The best one, unless a holdout split ranks another higher. For a parked or stopped search, what it would ship as things stand
candidatesevery Candidate, in creation order
historyeach time the best score went up, oldest first, as (minutes, candidate_id, score). This is the staircase hillclimb chart draws
spendevaluations (scored attempts and tune trials the climber caused), tokens, cost_usd, seconds
solution, paramsbest/solution.py, and the {name: value} dict of the selected candidate's best trial
source(candidate_id)a candidate's solution.py, or None if it never wrote one
journalthe full journal, holdout scores included. Read only
meta, statusthe search's record (problem, metric, climber block, budget, tags) and the engine's last status
to_frame()a pandas.DataFrame indexed by candidate id with lineage, verdict, scores, cost, timing, and every number the verifier reported (x and y on the toy terrain)

A Candidate is the journal's record of one attempt. It has candidate_id, parent_id, operator, kind, status, val_score, holdout_score, trials, agent (tokens, cost, duration), summary and metrics.

from hillclimb import open_search

open_search("20261002-221822-fitness-landscape/fitness-landscape")   # by ref
open_search("20261002-221822-fitness-landscape")                     # a run that has one search
open_search()                                                        # the most recently active one

You get the same kind of object for a search that already exists. A finished search is read once. A running one, still being climbed by another process, is read again every time you look, so state, candidates and spend keep up with it. A ref that matches nothing raises LookupError.