Skip to main content
SkyDiscover’s architecture lets you plug in custom search algorithms without rewriting the generate-evaluate loop. You only implement what changes—everything else is inherited.

Two Levels of Customization

There are two levels depending on what you need:

Database Only

Customize add() and sample() for different parent selection or storage logic

Database + Controller

Override run_discovery() for cross-iteration behavior like stagnation response or acceptance gating

Level 1: Database Only

Subclass ProgramDatabase and implement two abstract methods. The default controller runs the loop unchanged.

Complete Implementation Example

Here’s the full implementation of TopK search (56 lines):
skydiscover/search/topk/database.py

Key Points

  1. Store the program: self.programs[program.id] = program
  2. Update iteration tracking: self.last_iteration = max(self.last_iteration, iteration)
  3. Persist to disk: self._save_program(program) (if config.db_path is set)
  4. Call self._update_best_program(program) — required for tracking global best
Returns (parent, context_programs) where:
  • parent is a single Program to mutate
  • context_programs is a List[Program] shown as examples to the LLM
Optionally, you can return dict-wrapped results to add metadata:
  • ({"island_3": parent}, {"top_performers": context_programs})
  • self.programsdict[str, Program] storing all programs
  • self._update_best_program(program) — updates global best (call in add())
  • self._save_program(program) — persists to disk
  • self.get_top_programs(n) — returns top N by score
  • self.get_best_program() — returns highest-scoring program

Program Dataclass

Every program has these fields:

Registration

Add your algorithm to skydiscover/search/route.py:
route.py
That’s all. Now --search my_algo works:
Simple algorithms at this level: topk/ (56 lines), best_of_n/ (85 lines), beam_search/ (527 lines)

Level 2: Database + Controller

Use this when you need behavior that spans across iterations:
  • Tracking improvement history
  • Reacting to stagnation
  • Filtering results before they enter the population
  • Multi-island search with migration
  • Acceptance gating
The key point: you do not rewrite generate-evaluate logic. You call _run_iteration(), which runs the full sample → prompt → LLM → evaluate cycle, then decide what to do with the result.

Controller Template

Controller Primitives

async
Runs the full sample → prompt → LLM → evaluate cycle for one iteration. Returns SerializableResult.
method
Stores result to database, logs metrics, and triggers checkpoint callback.
method
Returns the best program seen so far.
property
Returns True when graceful shutdown is requested.

SerializableResult Fields

Registration

Register both database and controller:
route.py
Complex algorithms at this level: adaevolve/ (multi-island UCB search), gepa_native/ (acceptance gating + merge), evox/ (co-evolves the search algorithm itself)

Custom Configuration

If your algorithm has custom settings, add a dataclass in skydiscover/config.py:
config.py
Add it to the config type mapping:
config.py
Now users can configure it in config.yaml:
config.yaml
Access in your database:

Complete Reference

ProgramDatabase API

DiscoveryController API

Complete Registration Example

route.py

Next Steps

Custom Benchmarks

Add your own optimization tasks

Context Builders

Customize prompt generation