Configuration shifts¶
The problem. Continual learning needs an environment whose configuration changes underneath the agent — and the agent must not be told when. Without shifts there is nothing to adapt to; with a visible label, adaptation is trivial and the benchmark measures nothing.
The shape here. A schedule of named configurations, each with a duration and a set of axes; a
provider answers which configuration is this step; the environment applies what a world
understands and reports the rest; the label reaches info and the trace — never the observation.
Regimes and schedules teaches the concepts; this page is
the service's practical surface. It is shared deliberately — the environment drives shifts, the
harness announces them, the metrics segment by them — so it belongs to none of the three.
What you implement: a RegimeProvider — one method, regime_id(step). Consumers derive
boundaries by watching the id change, so a schedule and a label-free detector satisfy the same
interface.
A schedule, in code¶
from skyfall_crl.regime import RegimeSchedule, ScheduledRegime
schedule = RegimeSchedule([
{"regime_id": "baseline", "duration_steps": 500,
"ticket_arrival_rate": 0.2,
"chaos_type_distribution": {"missing_data": 0.6, "stale_data": 0.4}},
{"regime_id": "surge", "duration_steps": 500,
"ticket_arrival_rate": 0.8, "capacity_multiplier": 0.5},
{"regime_id": "baseline_revisit", "alias_of": "baseline", "duration_steps": 500},
])
provider = ScheduledRegime(schedule)
Or from a file (RegimeSchedule.from_yaml_file), or — the usual way — from the experiment
document's regime: section, whose full schema is in the
configuration reference. The three shift
modes (abrupt, cyclic, linear_drift with its drift_window) are specified there too.
The baseline_revisit entry is the load-bearing one: alias_of repeats an earlier
configuration under a new label, which is what makes forgetting
measurable. The label
says which visit a step belongs to; the recorded origin says what is being visited; forgetting
pairs encounters through the origin, so both labels report one number.
Which axes reach a world¶
Two axes are pushed to a MORPHEUS deployment when a shift lands: ticket_arrival_rate and
chaos_type_distribution. Everything else — capacity_multiplier, priority_distribution,
affected_service_distribution, deadline_tightness_multiplier, demand_surge_multiplier,
workforce_capacity, and the job/ticket/service-level distributions — describes conditions this
side reasons about, reported on info["regime"]["axes"] for a task view or wrapper to act on. A
self-contained environment applies whatever axes it understands itself, as
L2's wrapper does with gravity.
Who may change a world¶
Only an environment that owns its world reconfigures it. One attached to somebody else's world reports the label and leaves the conditions alone — rewriting them would silently change another run's experiment. A world that refuses a change keeps running; the label and the local axes stay truthful.
Other providers¶
A schedule is one way to answer the question; anything answering it registers by name and is selected from configuration:
from skyfall_crl.regime import get_provider, register_provider
class MyDetector:
def regime_id(self, step: int) -> str:
...
register_provider("my-detector", MyDetector)
provider = get_provider("my-detector")()
ConstantRegime is the default when nothing drives shifts — a stationary control, useful as
a baseline and explicitly not a continual-learning setting.
Limits¶
The shipped providers are pre-scheduled: shifts happen where the document says, not where a detector infers them — a label-free change-point detector is a research contribution the interface deliberately leaves room for, not something that ships. And the axis vocabulary is open by design, which means a genuinely new axis name is accepted without anything reading it; the near-miss warning catches typos, not unread axes.
API: every public symbol, with signatures — Configuration shifts — API reference.