Agents
The toy agent that makes a search free, and register_agent for a scripted agent of your own.
An agent is what an operator calls to make one attempt. It gets a prompt and a candidate dir, and
it leaves a solution.py there. claude-code, codex and pi do this by calling a coding
agent's CLI, and claude-code is the one the examples run on. Two more exist so you can try a
climber without spending anything.
The toy agent
budget = Budget(evaluations=30)
climber.search(Problem("fitness-landscape"), budget=budget, agent="toy")toy is a small scripted agent for the pre-created fitness-landscape problem, where a solution is
a single point on a 2D terrain and the score is the height at that point (get the problem with
hillclimb problem get fitness-landscape). It moves according to the kind of attempt it is asked
for.
| kind | the toy's move |
|---|---|
create (a draft) | a random point |
refine (an improve) | a step away from the parent's point. A line toy: step=<float> in the prompt sets how far |
combine (an ensemble) | the average of the parent and its inspirations |
repair (a debug) | the parent, pulled back inside the domain |
It declares two tunable parameters, dx and dy, so tune trials happen too. It is
deterministic, so the same search replays the same points. It only knows this terrain and says so
plainly when asked for anything else, like a memory pass or another problem. The older dummy
agent copies the sample submission on every call, so its scores never change.
Your own
from hillclimb import Budget, Climber, Problem, register_agent
from hillclimb.agents import AgentRequest, AgentResult
from hillclimb.policies import Greedy
class GridWalker:
"""Ignores the prompt. Each call stands on the next point of a coarse grid."""
name = "grid"
def __init__(self, side: int = 6):
self.side, self.calls = side, 0 # each search builds its own instance
def invoke(self, request: AgentRequest) -> AgentResult:
row, column = divmod(self.calls % self.side**2, self.side)
self.calls += 1
x = -5 + 10 * (column + 0.5) / self.side
y = -5 + 10 * (row + 0.5) / self.side
(request.candidate_dir / "solution.py").write_text(
f'open("submission.csv", "w").write("x,y\\n{x},{y}\\n")\n'
)
return AgentResult(ok=True, cost_usd=0.0, total_tokens=0)
register_agent("grid", GridWalker)
budget = Budget(evaluations=36)
Climber(operator_policy=Greedy()).search(Problem("fitness-landscape"), budget=budget, agent="grid")An agent is any object with a name and an invoke(request) method that returns an
AgentResult. The request has the prompt, the operator, its kind, the candidate_dir
(with the parent's solution already copied in when the operator asked for that) and a
timeout_s. The result says whether it went ok, and can carry cost_usd, total_tokens, a
session_id and an error_message.
A registered agent only exists in the process that registered it. search and start
can use it. A detached hillclimb run cannot, and a fleet will refuse it with a message saying
so. It also runs inside the engine's own process, so the sandbox does not wrap it. The solutions
it writes are still verified inside the sandbox, as usual.