Advanced Search
Advanced search is Limen's artifact-rich experiment path. It adds a mutable parameter domain, a search strategy abstraction, checkpointing, resumability, and mid-run feedback on top of the normal Universal Experiment Loop.
Use this page when you want:
- a search strategy that is not the legacy
ParamSpacesweep - a durable
experiment_dirwith resumable artifacts - mid-run interventions that can mutate the remaining search space
- promotion-ready runs for Trainer
For a first experiment, stay on the standard UEL path. Advanced search is real and supported, but it is the extension-oriented layer of Limen.
The Core Pieces
| Piece | What it owns |
|---|---|
ParamDomain | the mutable legal values for each parameter |
SearchStrategy | how new combinations are generated from the domain |
MSQ | the mutable search queue that applies interventions, filters, injections, and trims |
FeedbackController | reducer, callback, and file-driven feedback cycles |
CheckpointManager | durable save and restore of advanced-run state |
The important mental model is:
SearchStrategyproposes combinationsMSQdecides what is still legal- feedback mutates
MSQand sometimes the underlyingParamDomain - checkpoints preserve enough state to continue later with
resume=True
Minimal Custom SearchStrategy
Limen ships two built-in strategies (GridStrategy for exhaustive search, RandomStrategy for lazy sampling) and the SearchStrategy abstraction for writing your own. A strategy must at minimum:
- hold a reference to a shared
ParamDomain - yield dictionaries of round parameters
- expose
get_state()andset_state()for checkpointing - react to domain changes if its internal state depends on the current domain
This is the smallest practical exhaustive strategy shape:
import itertools
from limen.experiment.param_domain import ParamDomain
from limen.experiment.param_search import SearchStrategy
class MiniGrid(SearchStrategy):
@property
def is_finite(self):
return True
def __init__(self, domain: ParamDomain, *, seed: int | None = None):
super().__init__(domain, seed=seed)
self._combos = self._build_combos()
self._index = 0
def _build_combos(self):
params = self._domain.params
keys = sorted(params.keys())
return [
dict(zip(keys, vals, strict=True))
for vals in itertools.product(*(params[k] for k in keys))
]
def __next__(self):
if self._index >= len(self._combos):
raise StopIteration
combo = self._combos[self._index]
self._index += 1
self._generated_count += 1
return combo
def on_domain_changed(self, _domain, _changed_params):
self._combos = self._build_combos()
self._index = 0
def get_state(self):
return {'index': self._index, 'generated_count': self._generated_count}
def set_state(self, state):
self._index = state['index']
self._generated_count = state['generated_count']
First Artifact-Rich Run
import limen
from limen.experiment.param_domain import ParamDomain
from limen.experiment.reducer import BudgetReducer
domain = ParamDomain(limen.sfd.random_binary.params())
strategy = MiniGrid(domain)
uel = limen.UniversalExperimentLoop(
sfd=limen.sfd.random_binary,
search_strategy=strategy,
pruning_strategies=[
BudgetReducer(max_permutations=4, check_after_pct=0.25),
],
feedback_interval=2,
checkpoint_interval=3,
experiment_dir='advanced-budget',
)
uel.run(
experiment_name='advanced-budget',
n_permutations=6,
)
On a live local run over the bundled test data, this requested 6 permutations but finished with:
4rows inresults.csv4entries inround_data.jsonl1feedback-cycle entry inaudit.jsonl- a checkpoint saved after round
3
The reducer intervention that caused the trim was:
{'op': 'trim', 'target_count': 4, 'reason': 'budget trim to 4 total permutations'}
What experiment_dir Stores
When experiment_dir is set, advanced search writes:
| File | Meaning |
|---|---|
results.csv | streaming round log |
round_data.jsonl | round params, stored predictions, and alignment metadata |
checkpoint.json | search state for resume |
audit.jsonl | feedback-cycle audit trail |
metadata.json | experiment metadata used by Trainer |
interventions.json | optional input file polled by the feedback controller if you create it |
Unlike the other files above, interventions.json is not produced by the run. It is an optional external control file that the feedback controller watches when experiment_dir is present.
Resume Flow
Resumption belongs to the advanced path only.
uel.run(
experiment_name='advanced-budget',
n_permutations=6,
resume=True,
)
In a live local shutdown-and-resume run in this repo:
- the first phase stopped after
2completed rounds round_data.jsonlandresults.csveach contained2entries- the resumed phase finished the remaining work
- the final files contained
4rows with round ids0, 1, 2, 3
For resume to work cleanly, keep these stable:
- the same
experiment_dir - the same
SearchStrategytype - the same parameter content
- the same configured reducer stack
What Advanced Search Adds Beyond Standard UEL
Compared to the standard path, advanced search gives you:
- mutable pruning and focus during a run
- stored round-level predictions and alignment data
- auditability of feedback cycles
- resumability after interruption
- the artifact contract that Trainer expects
What it does not change:
- SFDs still define the research unit
- manifests still govern split-first prep
- post-run analysis still flows through
Log
Read Next
- Continue to Reducers And Feedback for the intervention system that acts on the mutable search queue.
- Continue to Universal Experiment Loop for the broader run contract around standard and advanced execution.
- Continue to Trainer if you want to promote finished advanced runs into reusable sensors.