File formats¶
Two artifacts cross machine and time boundaries: the trace a run writes and the bundle an export produces. Both formats are specified here completely — and both examples on this page are produced by executing it, so the row shown is a row that round-tripped and the manifest shown is a manifest that was written.
The trace¶
A trace is JSON Lines: one JSON object per step, in step order, in a file conventionally named
traces.jsonl. It is the only thing the evaluation layer reads — no environment, no model — so a
conforming file from any producer scores identically.
import json
from pathlib import Path
from skyfall_crl.eval.ingest import load_trace
from skyfall_crl.rewards.trace import EpisodeStep, TraceWriter
row = EpisodeStep(step=17, regime_id="windy", reward=0.62,
reward_components={"upright": 0.62})
with TraceWriter(Path("traces.jsonl"),
meta={"run_id": "example", "seed": 3, "algorithm_id": "steady"}) as writer:
writer.write(row)
print(Path("traces.jsonl").read_text().strip())
back = load_trace("traces.jsonl")
print(f"read back: {len(back)} run(s), {len(back[0].steps)} row(s), "
f"run_id={back[0].run_id!r}, seed={back[0].seed}")
{"run_id": "example", "seed": 3, "algorithm_id": "steady", "step": 17, "regime_id": "windy", "regime_origin": null, "observed_at": null, "action": null, "reward": 0.62, "reward_components": {"upright": 0.62}, "reward_detail": {}, "invalid_action": false, "effective_rank": null, "ticket_snapshot": {}, "verifier_snapshot": {}}
read back: 1 run(s), 1 row(s), run_id='example', seed=3
Row fields¶
| Field | Type | Meaning |
|---|---|---|
step |
int | The step index within the run. Required. |
reward |
float | The step's scalar reward. Required. |
regime_id |
str or null | The regime label — what segments the run into configuration intervals. Null rows form one unlabelled segment. |
regime_origin |
str or null | What the label repeats, for a revisit scheduled under a new name; forgetting pairs encounters through it. |
reward_components |
mapping | Each component's weighted contribution to reward, by name — the per-term attribution every composed reward carries. |
reward_detail |
mapping of mappings | Each component's internal sub-terms, when it reports them. |
invalid_action |
bool | Whether the environment refused the action; the invalid-action rate is the mean of this column. |
observed_at |
timestamp or null | When the step was observed — recorded so a reward term that reads a clock can be replayed exactly; null for environments with no such term. |
action |
mapping or null | The action taken, as data. |
effective_rank |
float or null | The policy's representation rank at this step, when capture is on; null means not measured, never zero. |
ticket_snapshot, verifier_snapshot |
mappings | Operational records from environments that have them (the MORPHEUS adapter); empty elsewhere. |
Run metadata, and how rows group into runs¶
The writer merges run identity into every row — top-level keys beside the step fields — so a
trace file is self-describing and a directory of them aggregates with no side-channel manifest.
Grouping uses the named identity fields, in order: run_id, seed, policy_id, task,
algorithm_id. Rows carrying none of them form one anonymous run. Any other merged key is
carried as metadata and ignored by grouping — which is deliberate: producers add per-step extras,
and grouping on everything would put each row in a run of its own.
Tolerance is part of the format. A reader ignores fields it does not know and defaults fields
that are absent, so the minimal conforming row is {"step": 0, "reward": 0.0} — everything else
sharpens what the metrics can say. To check a file you produced elsewhere:
skyfall-crl eval --traces yours.jsonl.
The bundle¶
An exported policy as one movable directory: a manifest.yaml describing everything, beside the
checkpoint. Written by skyfall-crl run --export DIR (or run_experiment(..., export_dir=...)),
consumed by skyfall-crl serve.
The block below trains a deliberately tiny run and exports it; the manifest that follows is the one it wrote, in full.
from pathlib import Path
from skyfall_crl.train import ExperimentConfig, run_experiment
config = ExperimentConfig.from_dict({
"env": {"id": "CartPole-v1"},
"algorithm": {
"name": "discrete_hill_climbing",
"params": {"seed": 1},
"policy": {"id": "skyfall_crl.train.backends.hill_climbing:linear_menu_policy",
"kwargs": {"n_actions": 2, "seed": 1}},
},
"rollout": {"window_steps": 50, "total_steps": 100, "auto_reset": True},
"run": {"name": "format-example", "seed": 1},
})
run_experiment(config, export_dir="bundle")
print(Path("bundle/manifest.yaml").read_text())
version: 1
policy:
id: skyfall_crl.train.backends.hill_climbing:linear_menu_policy
kwargs:
n_actions: 2
seed: 1
format: weights
checkpoint: checkpoint
external: false
observation_space:
type: Box
low:
- -4.800000190734863
- -inf
- -0.41887903213500977
- -inf
high:
- 4.800000190734863
- inf
- 0.41887903213500977
- inf
shape:
- 4
dtype: float32
action_space:
type: Discrete
n: 2
start: 0
reward_spec: null
env:
id: CartPole-v1
kwargs: {}
regime:
provider: constant
params: {}
run:
run_id: format-example
seed: 1
algorithm_id: discrete_hill_climbing
provenance: {}
Manifest fields¶
| Field | Meaning |
|---|---|
version |
The manifest format version; currently 1. Readers tolerate fields they do not know, so the format can grow without breaking older bundles. |
policy.id, policy.kwargs |
How to rebuild the policy object — the same dotted-factory form the configuration uses. Null id means the run's backend built its own default policy, and the bundle cannot be served. |
policy.format |
How the checkpoint restores: checkpoint (the policy loads a directory, the language-model path) or weights (the rebuilt policy's load_weights(path) is handed a file, the array path). Absent means the backend saved nothing — the bundle records the run but cannot be served. |
policy.checkpoint |
Where the weights are, relative to the bundle by default — which is what makes it movable. |
policy.external |
True when --export-external recorded an absolute path instead of copying; the bundle then only works where that path resolves, and says so if it does not. |
observation_space, action_space |
The Gymnasium spaces, encoded losslessly — including the details a naive encoding drops: infinite bounds, dtypes, Discrete.start, Text alphabets and zero minimum lengths. Null when the environment declared none. |
reward_spec |
The reward specification the run was scored under, by value — a name would not resolve wherever the bundle lands. Null for a Tier-1 run: the environment scored itself, and recording that nothing was reported is honest where inventing a description is not. |
env |
The environment's id and kwargs, so serve can rebuild it with nothing else — overridable, because serving against a different world should be something you asked for. |
regime |
The configuration-shift provider the run trained under; serve replays it by default so served traces segment like training traces. |
run |
The training run's identity, merged into every served trace's rows. |
provenance |
Whatever the environment's describe() reported, stored opaquely. Empty when it reported nothing. |