Train on a MORPHEUS world¶
The flagship rung: every layer of this package running at once, against a live persistent world.
MORPHEUS worlds are operational
simulations — logistics, warehousing, order flows — that run continuously, raise incidents, and
score remediations with verifiable rewards; this page provisions one, works in it, trains
against it under a configuration schedule, scores the run, and tears the world down. It assumes
a reachable deployment (http://localhost:8282 below); every output shown was captured from a
real one — identifiers and exact figures will differ on yours.
A world you provision costs money until you remove it. Incident content is authored by model calls, so a world with generation enabled keeps spending for as long as the deployment is up, whether or not anything is driving it. This page ends with the teardown, and the teardown is part of the tutorial.
1. Provision, look around, and see the lifecycle¶
from skyfall_crl.env.morpheus import morpheus_world
env = morpheus_world(layout="process-outbound", max_steps=40,
reward_spec="paper", delete_on_close=True)
observation, info = env.reset(seed=1)
print(f"world: {env.world_id} (owned: {env.owns_world})")
print(f"observation fields: {sorted(observation)}")
print(f"open tickets: {int(observation['open_tickets'][0])}, "
f"by priority {observation['by_priority'].tolist()}")
print(f"regime: {info['regime']}")
env.close()
print("closed -- delete_on_close removed the world")
world: 6a9e8b6a0ee315d69a40de88 (owned: True)
observation fields: ['by_priority', 'by_status', 'mean_verifier_progress', 'open_tickets', 'recent_invalid_actions', 'step', 'tickets']
open tickets: 0, by priority [0.0, 0.0, 0.0, 0.0]
regime: {'id': 'baseline', 'origin': None, 'step': 0, 'boundary': True, 'axes': {}, 'applied': True}
closed -- delete_on_close removed the world
Three things in that exchange are the adapter in miniature. The observation is the
fixed seven-field projection of the incident stream — and
it opens quiet: a fresh world has not lived yet, and incidents arrive as its simulation runs,
over minutes, not steps. The regime label travels in info, never in the observation. And
delete_on_close=True made close() remove the world — the default is False, which is how a
world outlives a crashed script and keeps spending; passing it explicitly is the habit this page
teaches first.
2. Attach to a world that has lived, and remediate one incident¶
Give a world a few minutes and the incident stream is real. Attaching by world_id makes the
environment a guest — it will not delete, rebuild or reconfigure a world it does not own:
import json
from skyfall_crl.env.morpheus import morpheus_world
from skyfall_crl.env.morpheus.action import ALLOWED_METHODS
env = morpheus_world(world_id="6a9e8b8b0ee315d69a40e281", max_steps=40, reward_spec="eval8")
observation, info = env.reset(seed=1)
print(f"attached as a guest: {not env.owns_world}")
print(f"open tickets: {int(observation['open_tickets'][0])}")
incident = json.loads(observation["tickets"])[0]
print(f"first incident: {incident['id']} {incident['status']} "
f"{incident['priority']} {incident['chaos_type']}")
action = {
"method": ALLOWED_METHODS.index("PATCH"),
"path": f"/{env.world_id}/tickets/{incident['id']}/status",
"body": json.dumps({"status": "in_progress"}),
}
observation, reward, terminated, truncated, info = env.step(action)
moved = {k: round(v, 3) for k, v in info["reward_components"].items() if v}
print(f"reward {reward:+.4f} executed: {info['action']['executed']} moved: {moved}")
env.close()
print("closed; a guest leaves the world alone")
attached as a guest: True
open tickets: 8
first incident: 6a9e8bf60ee315d69a40e83e new high STEP_FAILURE
reward -0.2800 executed: True moved: {'chaos_tickets': -0.28}
That one step is the whole loop: observe an incident,
act on it with one of the world's own domain operations, get scored. The reward deserves reading:
the status transition paid — the eval8 specification rewards progressing a ticket — and the
step still nets −0.28, because eight incidents are open and the clock is running on all of
them. The reward reflects the operational state of the world, not just the action you took, and
the per-component breakdown on info["reward_components"] (and every trace row) is how you tell
which is which.
3. Train under a schedule, from one document¶
env:
id: skyfall_crl.env.morpheus:morpheus_world
kwargs:
base_url: http://localhost:8282
world_id: 6a9e8b8b0ee315d69a40e281
max_steps: 60
reward_spec: eval8
regime:
regimes:
- {regime_id: steady, duration_steps: 20}
- {regime_id: surge, duration_steps: 20, ticket_arrival_rate: 0.8}
- {regime_id: steady_again, alias_of: steady, duration_steps: 20}
algorithm: {name: discrete_hill_climbing, params: {n_actions: 2, seed: 0}}
rollout: {window_steps: 20, total_steps: 60}
run: {name: morpheus-run, seed: 0, trace_path: traces.jsonl}
The schedule sits under the environment's regime, not the harness's, because a MORPHEUS
world labels its own steps and — when it owns the world — pushes each regime's axes to the
deployment as the shift lands. Attached as a guest, the labels flow and the pushes are declined:
this run's surge was a labelled interval, not an applied one, which is exactly what a guest
should do to a shared world. Provision inside the run (drop world_id, add
delete_on_close: true) and the shifts are applied for real — at the price of the early
intervals running in a world that has not lived yet. Two further lines are load-bearing:
auto_reset stays off (a MORPHEUS reset destroys and rebuilds the world), and alias_of gives
forgetting its second encounter.
4. Score it¶
1 trace(s), 3 configuration(s), reward spec 'eval8'
Metric segment
-------------------------------------
Per-configuration reward -0.925
Adaptation speed 20
Forgetting 0.045
Recovery time 1
Stability (variance) 0.000868421
Zero-shot reward -0.94
Settled reward -0.94
Return (undiscounted) -55.5
...
Read it the way the metrics chapter taught: an untrained two-action
policy in a live operational world watches the meter run — the open backlog charges every
step, so per-step reward sits near −0.9 and adaptation reports the interval length (the running
mean never crosses under a reward this uniformly negative). Forgetting is a genuine +0.045: the
revisit went slightly worse, because the backlog had grown. These are honest numbers about an
untrained policy, and the pipeline that produced them — schedule, labels, per-component rows,
segmentation, the spec-derived table — is the thing this rung demonstrates. Training something
good here is the research problem, and needs the [train] backends and
the capstone's scaling section.
5. Tear it down¶
Generation off first — immediate, and safe with work in flight — then delete. A world with generation off costs nothing, so the two-step is also a fine resting state:
import time
from skyfall_crl.env.morpheus import HttpBackend
backend = HttpBackend(base_url="http://localhost:8282")
backend.set_chaos("6a9e8b8b0ee315d69a40e281", {"enabled": False, "processChaosEnabled": False,
"infraChaosEnabled": False})
print("generation off")
time.sleep(5)
backend.delete_world("6a9e8b8b0ee315d69a40e281")
backend.close()
print("world removed")
Never delete a world that still has queued work without switching generation off first — a cascade delete on a busy world can disrupt the deployment itself. The adapter page carries the full ownership rules.
Where next¶
The capstone is this workflow at experiment scale — families, seeds, aggregation, export; DiscoveryWorld is the same machinery pointed at a simulator with none of this world's shape.