EVOLVE_API_KEY from the environment, or takes a HostedClientConfig(api_key=..., base_url=...). Create a key on the dashboard’s API keys page.
Every call is a coroutine, and every client is an async context manager with close(). A request never follows a redirect: the client refuses every 3xx and raises it as an error, so the key only reaches the host you configured.
Start a job
datasets is a list of selectors, as dicts or DatasetSelector dataclasses. A bare name resolves to the active version; task_names, exclude_task_names and n_tasks narrow the task set. Every arm in agents, a dict or an AgentArm, names a model_name, and version, reasoning_effort, kwargs, preset and skills are optional.
sandbox_provider picks "e2b", "daytona" or "modal". The retry policy is a JobRetryConfigInput dict and comes back resolved as JobRetryConfig. The fields are the same ones a -c config file takes on the CLI.
Results are dataclasses with the wire’s snake_case names and Literal vocabularies for every closed set. Four keys the wire spells in camelCase are mapped: next_cursor, has_more, by_status and task_matrix. A refused request raises EvolveAPIError; see errors.
Wait for it
watch() returns a handle you either await, for the final job, or iterate, for each event. Pick one form per call.
event.data is a plain dict; branch on event.type and read it by key.
Read the result
pass_at_k(job) reads a finished job’s pass@k out of its stats as sorted numbers.
A trial id is global, so trials() reaches one without the job.
artifact() takes the same names as evolve trial download --stream. None means the trial never stored that artifact; it is a normal answer, not an error.
Download
evolve job download unpacks.
Analyze
analyze() enqueues one analysis per trial and returns at once. watch_analysis() follows the batch of analyses to its end.
The catalog
Everything else
analyses(), checks(), skills(), agents(), auth() and orgs() are built the same way. meta() fetches the platform’s capability document, the live list of harnesses, models and limits, and needs no key. hosted() builds every client from one configuration.
async def main() and start it with asyncio.run(main()).
