Export and serving — API reference¶
Every public symbol in skyfall_crl.serve, generated from the source.
Generated page — built from the source docstrings, so this file is blank when read on GitHub. Run
mkdocs serveto read it locally, or read the docstrings in the module itself.
The bundle¶
bundle ¶
A trained policy, described well enough to be used somewhere else.
A checkpoint on its own is not a servable policy. To drive an environment with it you need the shapes it was trained against, the reward it was trained under, and enough about the policy to rebuild it -- and none of that is in the weights. A bundle is that record.
The bundle is self-contained by default. It is a directory holding manifest.yaml beside a
checkpoint/ copied into it, and the manifest stores a relative path, so the whole thing can
be moved, archived, or handed to someone as one object. external_checkpoint records an
absolute path to a checkpoint that stays where it is and copies nothing -- for weights too large
to duplicate. The manifest records which of the two it is, so a reader can tell a portable bundle
from one that only works on the machine that wrote it.
Everything the environment supplies is optional, and its absence is recorded rather than
invented. A Tier-1 environment has no reward spec, may declare no spaces, and need not describe
itself; a bundle for one is still a useful record of what it was and what it was trained on. This
follows the rule describe_run already states: recording that
nothing was reported is honest where inventing a description is not.
Two details are deliberate and easy to get wrong:
- The reward spec is stored by value, not by name. A world can declare an inline spec through its reward-config endpoint, so the spec a policy was trained under may exist under no registered name and in no file. A name would not survive the trip.
describe()output is stored opaquely, under its own key. Its keys are unspecified by contract, and the one implementation today happens to include areward_specentry holding a bare name -- which is not the manifest's own full spec and must never be confused for it.
MissingCheckpoint ¶
Bases: FileNotFoundError
A bundle names weights that are not where it says they are.
WeightsFormat ¶
Bases: str, Enum
How a policy is reconstituted from its checkpoint.
There are genuinely two, because a language model is rebuilt from its checkpoint while an array policy is built and then filled -- and forcing one shape onto both would mean constructing a base model only to discard it, which for a large model is not a detail.
CHECKPOINT
The recorded factory takes the checkpoint directory as its first argument and returns a
ready policy. language_policy is one.
WEIGHTS
The recorded factory takes no checkpoint; the policy it returns is then filled by its own
load_weights(path). This is the path an array policy takes, and it is why the two
example backends can both write weights.npy with different shapes and still load: the
shape is the rebuilt policy's business, which is where it was always known.
PolicyRef ¶
Bases: BaseModel
How to get the trained policy back.
id and kwargs mirror the experiment configuration's policy section, so a bundle names
its policy the same way a run does. format selects which of the two reconstruction
protocols applies; None means no weights were recorded at all, which is what a backend
that cannot checkpoint produces.
Bundle ¶
Bases: BaseModel
A policy, its schemas, its reward and its provenance, as one serialisable record.
extra="allow", as the trace record is, so a manifest written by a later version survives
a round-trip through an earlier reader instead of losing the fields it did not recognise.
observation_space
class-attribute
instance-attribute
¶
provenance
class-attribute
instance-attribute
¶
save ¶
Write manifest.yaml into directory, creating it if needed.
load
classmethod
¶
Read a bundle from a directory, or from a manifest file directly.
observation ¶
The recorded observation space, or None if the environment declared none.
reward ¶
The reward this policy was trained under, or None for a Tier-1 run.
checkpoint_path ¶
Where this bundle's weights actually are. None when it recorded none.
The two ways a bundle can be broken get named errors rather than a stray failure deep in a model loader: a relative checkpoint that is not there (a bundle copied in part), and an absolute one that has since moved or been deleted.
export_bundle ¶
export_bundle(
destination: str | Path,
*,
env: Any,
algorithm: Any = None,
policy_id: str | None = None,
policy_kwargs: Mapping[str, Any] | None = None,
env_id: str | None = None,
env_kwargs: Mapping[str, Any] | None = None,
reward_spec: RewardSpec | None = None,
regime: Mapping[str, Any] | None = None,
run: Mapping[str, Any] | None = None,
external_checkpoint: str | Path | None = None,
) -> Bundle
Write a bundle for algorithm's policy as trained against env.
Called while both are still alive, which is the only moment the schemas can be read at all -- and the reason the export hook lives in the run rather than in a command of its own.
external_checkpoint writes the weights to that path and records it absolutely instead of
copying them into the bundle. The result is smaller and not portable, and says so.
A backend that cannot checkpoint still exports. The result records spaces, reward and provenance with no weights, which is the honest thing for the array-policy example backends the base install ships with -- and refusing would make export unusable on exactly those.
Serving one¶
runner ¶
Driving an environment with a bundled policy, and nothing else.
Serving here means inference against a live environment, not a web server. That is what the
research code this generalises actually does -- every one of its inference backends is a client
to somebody else's process, and a trained checkpoint never goes over HTTP -- and it is what makes
a served policy measurable: the run writes the same trace a training run writes, so
skyfall-crl eval scores it with no special case.
Inference is a rollout that never updates, so the collector does the driving. That is deliberate reuse rather than convenience: a served run then labels configurations, stamps provenance and writes trace rows by exactly the code a trained run used, and cannot quietly disagree with it.
The task view comes from the bundle, which is the defect this design exists to avoid. The reference selects the environment and the task separately at inference time, so a policy trained on one task can be evaluated under another with nothing objecting; here the schemas and the environment travel with the weights, and an override is something you ask for.
Nothing at module scope imports torch. A language-model bundle pulls it in when its policy is built, and an array bundle never does -- so a policy exported from the example backends can be served on a base install.
ServeResult
dataclass
¶
What one inference run produced.
provenance
class-attribute
instance-attribute
¶
load_policy ¶
Rebuild the policy a bundle describes.
Two reconstruction protocols, chosen by the recorded format, because there genuinely are two:
a language model is rebuilt from its checkpoint, while an array policy is built and then
filled. See WeightsFormat.
Every way this can fail names what is missing. A policy restored partially -- built fresh and then not filled -- would act, and act like something that was never trained, which is the one failure mode worth ruling out by construction.
build_environment ¶
Rebuild the environment a bundle was trained against.
Keyword overrides are merged into the recorded kwargs, so serving the same policy against a different world is an argument rather than a rewrite.
serve ¶
serve(
directory: str | Path,
*,
steps: int = 100,
env: Any | None = None,
trace_path: str | Path | None = None,
window_steps: int = 64,
auto_reset: bool = False,
seed: int | None = None,
env_kwargs: Mapping[str, Any] | None = None,
regime: Any | None = None,
use_recorded_regime: bool = True,
) -> ServeResult
Run a bundled policy against an environment and record what it did.
env overrides the environment the bundle names -- for serving a policy against a world it
was not trained on, which is a legitimate thing to want and should be explicit. An environment
passed in is still closed on the way out, for the same reason the training run closes one it
was handed: a rule about ownership is easy to state and easy to forget, and a world left open
costs money.
The configuration schedule the bundle recorded is rebuilt and applied, so a served trace is
segmented exactly as the training trace was and the two are comparable. regime overrides
it; use_recorded_regime=False runs stationary, which is what you want when serving against
a world the recorded schedule does not describe.
Spaces as plain data¶
spaces ¶
Spaces as plain data, so a schema survives leaving the process.
An exported policy is only usable if the shapes it was trained against travel with it, and
Gymnasium offers nothing for that. to_jsonable/from_jsonable exist on every space, but
their signature is to_jsonable(self, sample_n) -- they serialise batches of samples, not the
space itself; flatten_space returns a different space. So this is written rather than reused.
Four things are recorded here that a naive encoder loses, and each was a real round-trip break in the spaces this package already ships:
- Infinite bounds.
Box(-inf, inf)is ordinary and JSON has no infinity.json.dumpsemits a bare-Infinity, which is not valid JSON, andyaml.safe_loadreads that back as the string'-Infinity'. Non-finite bounds are encoded as tokens instead. Text.min_length. Gymnasium defaults it to1; everyTextspace in this package sets it to0. A reconstruction that took the default would reject the empty request body the tool action space emits routinely.Textcharacter sets.character_setis afrozenset, and the sets used here arestring.printable-- which contains tab, newline and carriage return. Therepris unordered and elides, so the characters themselves are stored, sorted for determinism.Discrete.startandMultiDiscrete.start, both silently defaulted to zero on reconstruction.
Correctness is space == space_from_dict(space_to_dict(space)) under Gymnasium's own equality,
which is what the tests assert rather than comparing encodings.
None is a supported input and output. Not every environment declares spaces -- three of
this package's own test doubles declare none -- so absence is a case to record, not an error.
Dispatch is on the exact type. A subclass of Box encodes as something this module could only
reconstruct as a plain Box, so it raises UnsupportedSpace rather than quietly
recording a schema that is not the one the policy was trained against.
UnsupportedSpace ¶
Bases: TypeError
A space this module cannot record faithfully, or an encoding it cannot read.
space_to_dict ¶
Encode a space as JSON- and YAML-safe plain data.
None in gives None out: an environment that declares no space has said something,
and recording that is more honest than inventing a shape for it.
space_from_dict ¶
Rebuild a space from space_to_dict output. None round-trips as None.