Docs
/
Models
/

Structured Input Spaces

Structured Input Spaces

Most models read every input the same way: one window length, one sample rate, applied to all of them. A structured input space breaks that assumption. The model's flat input vector is carved into named inputs, and each one carries its own window length, sample rate, and interpolation method.

That means a flow reading sampled every second and a temperature sampled every hour can feed the same model, in one inference, without either one being resampled to the other's cadence.

A structured model's input tensor is rank-2 — [1, flat_dim] — where flat_dim is the sum of every input's window length. The per-input windows are declared in the model file (or carved during upload), not derived from the graph. See Model Inference Requirements for how this compares to Flat and Time-series shapes.

When to Use One

Use a structured space whenUse Flat or Time-series when
Your inputs are sampled at genuinely different rates (a 1s control signal alongside an hourly ambient reading)Every input shares one cadence
Some inputs need a long lookback and others only the latest valueAll inputs need the same lookback
The policy was trained against a gymnasium Dict or Tuple observation and you want the live vector packed the same wayThe model was trained on a plain 2-D or 3-D array
You want each signal's staleness and history checks sized to that signalOne model-wide window is an accurate description of the data

There is no benefit to a structured space when every input would end up with the same window and rate. A Flat or Time-series model is simpler to configure and supports Memory Only, which a structured model does not.

Anatomy of a Space

A structured space is a small tree. Containers group inputs; each leaf of the tree is one signal, and one input binding is created per leaf.

obs                          Dict
   ├── flow                    Box, window 5      → Input 1, slots [0–4]
   ├── temperature             Box, window 12     → Input 2, slots [5–16]
   └── ambient                 Box, window 1      → Input 3, slot  [17]

  Flat vector, leaf-major:
  ┌───────────────┬───────────────────────────────────┬────────┐
  │  flow ×5      │  temperature ×12                  │ amb ×1 │
  └───────────────┴───────────────────────────────────┴────────┘
   0             4  5                                16   17
                          flat dim 18

Each leaf occupies a contiguous run of slots, and the runs tile the vector exactly — no gaps, no overlap, and the total must equal the model graph's input width.

Leaf propertyMeaning
PathWhere the input sits in the tree, for example obs/temperature. A tuple child appends its index, obs/sensors[0]
Binding order1-based position in the flatten order. Input 1 is the first window in the vector
WindowHow many samples this input contributes (its slice length)
Sample rateSeconds between this input's history samples
InterpolationHow raw history is resampled onto this input's grid

Declaring a Space

There are two routes. Either the model file already declares its own space, or you carve one during upload.

Route 1: Embedded in the model file

An ONNX file can carry its input space as embedded metadata under the koios.observation_space key, alongside the training and binding metadata described in Managing Model Files. The koios-model-utils library writes it:

from koios_model_utils import embed_koios_metadata
from koios_model_utils.metadata import LeafSampling
from koios_model_utils.gym import observation_space_from_gymnasium

space = observation_space_from_gymnasium(
    env.observation_space,
    sampling={"obs/ambient": LeafSampling(sample_rate=3600.0)},
)

embed_koios_metadata(model, inputs=..., outputs=..., observation_space=space)

The flatten order is the observation space's own resolved child order, used verbatim, so the live vector Koios assembles matches the one the training script fed the model. Building a space from a live gymnasium space needs the library's optional gymnasium extra:

pip install "koios-model-utils[gymnasium]"

The library supports continuous Box leaves inside Dict and Tuple containers. Discrete, multi-binary, and multi-dimensional spaces are not supported.

A file that carries this metadata is recognized as Structured on upload, and the space is re-applied every time that file is activated.

Route 2: Carve it in the Input Shape step

A plain rank-2 file with no embedded space can be carved by hand. Every upload continues to an Input Shape step after the file is accepted, offering four cards:

CardShapeWhen it is selectable
Flat[1, N]Rank-2 files with no embedded input space
Time-series[1, D, F]Rank-3 files
Structured[1, flat_dim]Rank-2 files. Pre-selected and locked, with a Detected from model file badge, when the file declares its own space
Multi-input{a: …, b: …}Disabled — "Coming in a later release"

Cards that contradict the file are disabled and give the reason on hover. A rank-3 file cannot be read as Structured, and a file that declares its own space cannot be read as anything else — for that file the step shows the space as a read-only tree and you click Continue.

Choosing Structured on a file with no embedded space opens the slice builder.

The Slice Builder

The builder carves the model's flat input vector into named inputs. One row per input:

ColumnNotes
Input nameRequired, unique, and cannot contain / — it becomes the last segment of the input's path, obs/<name>
WindowWhole number of samples, at least 1
Sample rateStarts on Model rate, meaning this input inherits the model's sample rate. Click it to set an explicit rate; the small × returns it to the model rate
InterpolationPCHIP (default), Linear, Previous, or Nearest
SlotsRead-only: the flat-vector positions this input occupies, e.g. [5–16]

Use Add input to append a row — its window defaults to whatever is left unassigned. A progress bar and an N / M slots assigned badge track the carve: indigo while slots remain, red if you overshoot, teal when it tiles exactly.

Save shape stays disabled until every row is named and sized and the windows sum to exactly the model's input width. Its tooltip says what is missing: Assign all 18 slots (12 assigned), or Name every input (names must be unique).

The carve is set during upload. To change it later, upload the file again and carve it differently.

How Bindings Map to Inputs

One input binding is created per leaf, in flatten order:

  • Binding order is 1-based. Input 1 is the first window in the flat vector, input 2 the next, and so on.
  • Binding orders must be unique. Windows are keyed by binding order at assembly, so a duplicate would silently collapse two inputs into one. Duplicates are rejected and the model refuses to run — see Model Configuration Errors for what that looks like and how to renumber.
  • Binding orders must be dense. They run 1..N with no gaps, and the count must equal the number of leaves the space declares.
  • The binding's name defaults to the last segment of its path — obs/flow becomes flow.

Some binding fields belong to the model file and some are yours to edit:

FieldOwner
PathThe file. Re-stamped on every activation
Window lengthThe file. Read-only in the UI
Sample rateYou. An optional per-input override of the model sample rate
InterpolationYou. Defaults to PCHIP
Tag, scaling, calibration, failure detection, rate of changeYou, exactly as on any other binding

On the Bindings tab

Input cards are grouped under the input they belong to, and each card reports the window in use — for example 12 samples × 1.0 h. Hover that text for the full story: sample spacing, lookback, the slots the input occupies in the flat vector, and its interpolation method.

Open a card and the drawer gains a fourth section, Sampling:

ControlDescription
Override model sample rateOff by default — the input uses the model's sample rate, shown beside the switch. Turn it on to give this input its own cadence
Sample rateTime between history samples for this input, used only when the override is on
WindowRead-only: sample count and the span it covers — for a 12-sample window at one-hour spacing it reads 12 samples · 11.0 h. The window length comes from the model's input space and cannot be changed here
Advanced → InterpolationHow raw history is resampled onto this input's grid. Default (PCHIP) unless you pin a method

Full detail on the other sections is in Assigning Bindings.

Assembly at Scan Time

On every scan, Koios:

  1. Fixes one reference instant for the whole model — the right edge every input's window ends at.
  2. Resamples each input on its own cadence onto its own grid, ending at that instant.
  3. Normalizes and calibrates each value using that binding's own settings.
  4. Lays the windows end to end in ascending binding order, producing the rank-2 [1, flat_dim] vector the model was trained on.

Because the windows are concatenated whole rather than interleaved by time step, the result matches a gymnasium flatten of the same observation. Samples within each window run oldest first unless the model file declares that input as newest-first.

Warm-Up and History

A model cannot infer until every input has enough history to fill its window. For a structured model that happens per input, so one binding can be ready while another is still filling — the model waits for all of them.

  • On enable, Koios makes a single attempt to backfill each input from long-term history, at that input's own rate and for that input's own window. Where no history exists, it falls back to holding the tag's current live value flat across the window.
  • Until a window fills, that binding reports Not Enough Historical Depth. This is normal for a newly bound tag and clears on its own as data accumulates.
  • Initialize history seeds each input over its own window rather than one model-wide window. Run it for the whole model from the three-dot menu in the model header, or for a single binding from that binding's configuration drawer.
  • Stale-data detection measures the allowed missed samples against that input's own sample rate.

An input with a long window over a slow signal takes correspondingly longer to warm up. An hourly input with a 12-sample window needs 12 hours of history before it is ready, unless backfill or Initialize history can supply it.

Restrictions

RestrictionDetail
Memory Only is not supportedA structured model with Memory Only enabled saves cleanly and then refuses to run. It is left Stopped with Model configuration is invalid and the detail Structured input spaces do not support in-memory history yet — disable Memory Only for this model
Rank-2 graphs onlyThe structured shape applies to flat input tensors. A rank-3 (time-series) file cannot be carved
Windows must tile exactlyThe per-input windows must sum to the declared flat dimension, and that must match the graph's input width
Binding orders must be unique and denseDuplicates, gaps, or a count that disagrees with the space stop the model
PCHIP is the only method appliedAn input can carry another interpolation selection, but this release resamples every input with PCHIP and records a warning in the model log when the configuration loads
Multi-input models are not supportedA model must present a single input tensor

Where to See the Space

LocationWhat it shows
Model detail → OverviewThe Input field carries the shape label, for example "3 inputs, flat dim 18 (Structured)". The model list has only Inputs, Outputs and Depth columns — no shape label
File detail → Space tabThe space as a tree, with each input's window, order, and path, plus badges for the input count, flat dimension, and element type. Only present when the file declares a space
File detail → OverviewInput and output shapes, including the flattened dimension
Bindings tabInput cards grouped by input, each showing its window
ExportThe leaf_path, leaf_size, leaf_sample_rate, and leaf_interpolation columns of a binding export. See Importing and Exporting

What's Next