Regimes and schedules¶
A regime is the configuration the world is currently in — the set of conditions the agent is
acting under. The prose and the API use the same word: a step's label is regime_id, a run's
provider is a RegimeProvider, the configuration section of an experiment is regime:. A maximal
stretch of steps under one regime is a configuration interval, the unit the
metrics segment a run by.
Non-stationarity is the point of a regime, not a nuisance to it. An agent that only ever sees one regime is not learning continually — it is training on a stationary problem with extra vocabulary. Everything in this chapter exists to make the world change in a way that is controlled (so a result is reproducible), recorded (so a trace can be segmented), and invisible to the policy (so noticing the change remains part of the task).
Providers: who says what the regime is¶
The contract is one method:
from skyfall_crl.regime import RegimeProvider, ConstantRegime, ScheduledRegime
class Anything:
def regime_id(self, step: int) -> str: ...
A provider is a pure function of the step: the same step always answers with the same label,
which is what every downstream segmentation rests on. Two are built in. ConstantRegime answers
one label forever — a stationary control, useful as a baseline and explicitly not a
continual-learning setting. ScheduledRegime follows a schedule written in advance, and is how
nearly every run here shifts. A label-free change detector is a third kind a research project
might register; the contract deliberately does not require knowing boundaries in advance, only
answering for a step.
A run selects its provider by name in the experiment document:
regime:
provider: scheduled
params:
schedule:
regimes:
- {regime_id: calm, duration_steps: 400}
- {regime_id: windy, duration_steps: 400, ticket_arrival_rate: 0.85}
- {regime_id: calm_again, alias_of: calm, duration_steps: 400}
Schedules¶
A schedule is a list of regimes, each with a regime_id, a duration_steps, and its axes —
every other key is one. An axis is a named condition: an arrival rate, a capacity multiplier, a
distribution over failure kinds. The set of axes is deliberately open, because what a world can
vary is the world's business; a name within a small edit distance of a known axis raises a
warning naming the likely intent, and a key literally called axes is rejected, because both are
almost certainly mistakes that would otherwise cost nothing at parse time and everything at run
time.
How a shift arrives is the schedule's shift_mode:
| Mode | Behaviour |
|---|---|
abrupt (default) |
the next regime's axes apply whole, at the boundary |
linear_drift |
numeric axes ease linearly into the next regime over the final drift_window steps — including numeric values nested inside distributions; values that cannot be blended switch at the window's midpoint |
cyclic |
the entries repeat for as long as the run lasts |
Abrupt shifts test reaction to sudden change; drift tests tracking of gradual change. They are different experimental questions, selected by one key.
Recurrence, and why alias_of exists¶
Forgetting is a comparison: how the policy performed under some
conditions the first time against how it performs when those conditions return. A schedule
that never returns to anything cannot measure it — so a schedule built to measure forgetting
brings a regime back, and alias_of is how: the entry above gives calm_again exactly the axes
of calm, under its own name.
The new name is not cosmetic. Reusing the label calm outright would make the return
indistinguishable from the first encounter in the trace; a fresh label keeps the two intervals
apart in every table. Each step's row records both the label and its origin — what the regime
repeats — and forgetting pairs encounters through the origin, so calm and calm_again report
one number rather than none each.
What the agent is told: nothing¶
The regime label is deliberately withheld from the policy. It travels in info — alongside a
boundary flag on the first step of each interval — and into the trace, never into the
observation. The reason is the integrity of the benchmark: inferring that conditions changed is
part of the problem being posed. An agent handed the label adapts trivially, and its adaptation
numbers measure the plumbing rather than the policy. An environment that leaks the label into its
observation trains perfectly well and produces numbers that mean nothing — which is why
skyfall-crl conformance treats a leaked label as a failure, and why nothing downstream
will tell you it happened.
The label still has to exist somewhere, because the metrics need it: adaptation is measured from a boundary, intervals are cut by the label. That is the split to keep in mind — the evaluation layer sees everything, the policy sees only the world.
Applying a shift, versus reporting one¶
A provider reports labels. Whether anything applies the corresponding axes to a world is the environment's affair, and the two are deliberately separable:
- A self-contained environment (CartPole with a wrapper, a simulator you control in-process) applies the axes itself — a wrapper reads the schedule and sets gravity, spawn rates, whatever the axes name.
- A live world is reconfigured through its own interface. The MORPHEUS adapter pushes the axes a deployment understands at each boundary — when it owns the world. An environment attached to a world someone else owns reports labels and changes nothing, because rewriting the configuration of a shared world is not a guest's decision.
- A stationary control applies nothing, which is the point of running one.
Either way the labels flow into the trace, and the metrics work identically — they segment on what was recorded, not on what was applied.