python / agentshillclimb

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.

kindthe 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.