Skip to content

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.