Describe Envelope: `rlmesh.describe.v1`
The JSON schema used to describe an environment factory or model.
The describe envelope is a JSON artifact for an environment factory or model. It records parameters, variants, the IO contract, observation and action spaces, and the runtime that generated it. The managed platform uses it to show what a package can run before starting a workload.
This document is the cross-language contract. The format (the field set,
versioning, ordering, and serialization) is owned by the Rust crate
rlmesh-adapters (build_describe_envelope). Any producer (the Python SDK
today; a future C++ or TypeScript SDK) emits a byte-identical envelope for the
same logical input by handing its gathered pieces to that one builder, or by
implementing this contract exactly.
Versioning
schema_versionis an integer, stamped by the builder: a producer never sets it. It is1today.- The wire discriminant is the metadata key
rlmesh.describe.v1(theDESCRIBE_METADATA_KEYconstant). Withinv1the format evolves additively only: new optional fields with defaults. A breaking restructure ships under a new key (rlmesh.describe.v2) and bumpsschema_version; a v2 reader keeps reading v1. - Serialization is canonical: the builder serializes the whole tree through one
serde_jsonpass, and object keys sort (BTreeMap), so the bytes do not depend on the producer’s language or JSON formatting.
Layers: who produces what
| Concern | Owner | Notes |
|---|---|---|
schema_version, kind, ordering, final serialization |
Rust builder | Stamped/validated/serialized once; no producer can disagree. |
generated_at |
producer-supplied, Rust-validated | RFC-3339; omit for a content-addressable artifact (do not use wall-clock in a reproducible build). |
target, params, variants, env_spec, env_contracts, runtime |
per-language gatherer | Requires introspecting/executing the producer’s own language (signature reflection, running the author’s variant enumeration, constructing the env, reading local versions). |
env_tags, model_spec, env_spec.* space dicts |
shared codecs | Already canonical from their own serializers (EnvTags/ModelSpec/SpaceSpec); embedded as-is. |
A producer’s only language-specific job is the gathering. Everything about the format is shared.
Envelope shape
Environment (kind: "env")
{
"schema_version": 1,
"kind": "env",
"target": { "entrypoint": "mypkg.envs:Libero" | null, "qualname": "mypkg.envs:Libero" },
"generated_at": "2026-06-28T19:30:00Z",
"env_spec": {
"observation_space": { ... },
"action_space": { ... },
"num_envs": 8
},
"env_tags": { ... } | null,
"env_contracts": {
"discriminants": ["action_type"],
"branches": [
{ "params": { "action_type": "delta" }, "default": true, "env_tags": { ... }, "env_spec": { ... } },
{ "params": { "action_type": "abs" }, "default": false, "env_tags": { ... }, "env_spec": { ... } }
]
},
"params": {
"param_spec": { "params": [ ... ], "extra": "forbid" } | null,
"signature_tier": [ { "name": "...", "type": "...", "default": ..., "required": false } ]
},
"variants": {
"catalog": [ { "id": "libero_10/0", "params": { ... }, "metadata": { ... } } ],
"variations": { "seed": [0, 1, 2] }
},
"runtime": {
"component": "rlmesh-python",
"language": "python",
"language_version": "3.11.8",
"package_version": "0.1.0",
"os": "linux", "os_version": "...", "arch": "x86_64",
"framework_versions": { "numpy": "...", "torch": "..." }
}
}
env_specis captured from one representative constructed env: one per contract branch. A factory’s variants share spaces, so a factory with no declared contract discriminants has exactly one shape; a factory that declares them (tag_params) has one per branch, and the top-levelenv_spec/env_tagsare the default branch’s. For a vectorized env it carriessingle_*spaces plusnum_envs.env_spec.observation_space/action_spaceare theSpaceSpecJSON form. The envelope is strict JSON, so a non-finite number anywhere in it isnull: anullin a Boxlow/highmeans unbounded on that edge (-infunderlow,+infunderhigh).env_contractsappears only for a factory that declares contract discriminants. In that table,discriminantsnames the axes, and every branch carries its full binding (never a subset) plus theenv_tags/env_specthat binding produces – so a reader that cannot run the producer’s code can still say which branch a contract belongs to, and a static check can name the branch it validated. Exactly one branch hasdefault: true, and itsenv_tags/env_specare the top-level ones.
Model (kind: "model")
Same wrapper; drops env_spec/env_tags, adds model_spec, corners, and
native_chunk:
{
"schema_version": 1,
"kind": "model",
"target": { ... },
"model_spec": { "input": { ... }, "output": { ... } } | null,
"corners": ["predict", "predict_chunk_batch"],
"native_chunk": 30,
"params": { ... },
"variants": { ... },
"runtime": { ... }
}
cornerslists the predict corners the model class actually defines, drawn from the closed set["predict", "predict_chunk", "predict_batch", "predict_chunk_batch"]and reported in that order (general -> specific). It is introspected, not declared, so a packaging claim likesupportsBatchingcan be checked against the code that ships. Omitted when the producer resolved no corner at all (a duck-typed callable it could not introspect); an empty list is never emitted.native_chunkis the model’s own declaration of its chunk length K: how many per-step actions one chunk-corner call returns. Omitted for the elastic (undeclared) contract, in which the runtime takes themin(len, h)prefix of whatever comes back. It is an integer (boolis not accepted), read off the class, so a K a model only sets while loading its weights is deliberately invisible here – describe runs without weights.
Best-effort / error badges
Any gathered piece that fails (an env that needs a GPU to build, a make() that
needs unavailable args, a model spec that can’t be published, a broken
enumerate_*) is replaced by an {"error": "<message>"} badge in place of that
field (env_spec, model_spec) or as a sibling *_error key (catalog_error,
variations_error). The envelope is always emitted: a no-GPU build still
produces a useful artifact with env_spec: {"error": ...}.
Invariants enforced by the builder
kindis a closed enum ("env"|"model"); anything else is rejected.- Env-only fields (
env_spec,env_tags,env_contracts) never appear on amodelenvelope, and the model-only fields (model_spec,corners,native_chunk) never appear on anenvenvelope. The builder rejects a misplaced field by name rather than dropping it. - Unknown top-level fields are rejected (the key set is part of the contract).
generated_at, if present, must be RFC-3339.