<!-- Source: https://docs.biosimulant.com/runtime/model-manifest -->

# model.yaml schema

`model.yaml` describes one packaged BioModule: its name and version, how to load it, its input and output ports, its dependencies, and optional ONNX and compatibility details.

## Schema

```yaml
schema_version: "2.0"
title: string
description: string
standard: sbml | neuroml | cellml | nmodl | onnx | other
package: namespace/name
version: 1.2.3
license_type: unknown | mit | apache_2 | cc_by | cc_by_sa | proprietary
authors: [string]
tags: [string]
species: [string]

biosim:
  entrypoint: module.path:ClassName
  init_kwargs: object
  setup: object
  communication_step: positive number
  execution_policy: once_before_run | each_window | once_after_run

io:
  inputs: [SignalSpec]
  outputs: [SignalSpec]

runtime:
  python_version: "3.12"
  dependencies:
    packages: [exact==pins]
    requirements_file: relative/path.txt
    lockfile: relative/lock.file
  remote:
    requirements:
      accelerator: gpu
      gpu_count: positive integer

onnx:
  task: string
  model_file: relative/path.onnx
  class_labels: [string]
  inputs: [TensorSpec]
  outputs: [TensorSpec]
```

## Top-level fields

| Field | Type | Required | Meaning |
|---|---|---:|---|
| `schema_version` | string | Current packages | Manifest shape; current value is `"2.0"` |
| `title` | string | Recommended | Human-facing model name |
| `description` | string | Recommended | Scope, method, and intended-use summary |
| `standard` | enum | Yes | Primary model/artifact family |
| `package` | string | For releases | Stable `namespace/name` identity |
| `version` | SemVer | For releases | Exact release version, such as `1.0.0` |
| `license_type` or `license` | enum/string | Recommended | Distribution/use license; unknown values normalize to `unknown` |
| `authors` | string[] | No | Author names or attributions |
| `tags` | string[] | No | Search and discovery tags |
| `species` | string[] | No | Species, for search and discovery. To check compatibility, set species in a port `contract` instead. |
| `biosim` | object | Yes | How to load the model's Python code |
| `io` | object | Recommended | The model's input and output ports |
| `runtime` | object | No | Python, dependency, and remote resource requirements |
| `onnx` | object | For ONNX | ONNX artifact and tensor metadata |

## Package identity

`package` is the stable `namespace/name` identifier and `version` is the exact
SemVer package version, such as `1.0.0`. Together they form the package ref
`namespace/name@x.y.z`.

When you build or publish a package, `version` must be exact SemVer. `latest`
and other non-SemVer values are rejected. This rule is for manifests. CLI
commands such as `labs pull` accept a ref without a version; see
[Package and publish](/hub/publish#pull-and-run-versions).

A lab does not reference a model by package. Its `models[]` entries use a
relative `path`. To add a model source tree to a lab:

```bash
biosimulant labs add-model ./models/hh-population --lab ./my-lab --alias hh
```

`add-model` writes the relative path and alias into the lab's `lab.yaml`.

## `biosim` block

```yaml
biosim:
  entrypoint: "src.hh_population:HHPopulation"
  init_kwargs:
    n: 100
  setup:
    seed: 1234
  communication_step: 0.001
```

| Field | Type | Required | Behavior |
|---|---|---:|---|
| `entrypoint` | string | Yes | Python symbol in `module.path:ClassName` form |
| `init_kwargs` | object | No | Constructor kwargs merged with lab-level parameter overrides |
| `setup` | object | No | Default `setup()` config for the module |
| `communication_step` | number > 0 | Yes | Default coupling step for this packaged model |
| `execution_policy` | enum | No | Checked copy of the Python class's execution policy; see [Execution policy](#execution-policy) |

Model-entry `parameters` in a lab are constructor overrides. They are merged into
`biosim.init_kwargs` before the entrypoint factory is called. Runtime inputs are
separate: declare them under `io.inputs` and pass values through run-time input
payloads.

### Execution policy

`biosim.execution_policy` repeats the execution policy your Python class
declares, so tools that can't import model code (Studio, Desktop and the Hub)
can tell when the model runs. It needs `biosimulant>=0.0.33`.

```yaml
biosim:
  entrypoint: "src.predictor:Predictor"
  communication_step: 0.01
  execution_policy: once_before_run
```

| Value | Python declaration |
|---|---|
| `once_before_run` | `ExecutionPolicy.ONCE_BEFORE_RUN` |
| `each_window` | `ExecutionPolicy.EACH_WINDOW` |
| `once_after_run` | `ExecutionPolicy.ONCE_AFTER_RUN` |

The Python `execution_policy` attribute is still the only thing that decides
when BioWorld runs the module. The manifest field is a copy that Biosimulant
checks against your code:

- When the model loads, the runtime compares the field with the policy of the
  constructed module. If they differ, the model doesn't load, and the error names
  both values.
- `biosimulant labs validate` reads the entrypoint source without importing it.
  It reports an error when the source clearly resolves to a different policy, and
  a warning when it can't verify the value, for example when `__init__` sets the
  policy from a parameter.
- For a model that doesn't declare the field, `labs validate` suggests the value
  to add when it can read the policy from source.

Declare the policy your module resolves to:

| Your class | Declare |
|---|---|
| Sets `execution_policy` as a class attribute | That value |
| Sets `self.execution_policy` to a fixed value in `__init__` | That value |
| Implements `execute()` and sets no policy | `each_window`. BioWorld warns that the policy is implicit; declaring `each_window` in `model.yaml` clears the warning. |
| Implements `advance_window()` | `each_window` |

Leave the field out when the policy depends on constructor parameters. Tools
then treat the model as undeclared.

A lab can't override a model's policy. You can mix policies in one lab, as long
as the wiring between them is valid. See
[Execution timing](/runtime/lab-manifest#execution-timing) for the wiring
rules and for how declared policies change the run settings a lab shows.

## Ports (`io`)

Some tooling still accepts plain lists of port names, but new manifests should use typed ports like these:

```yaml
io:
  inputs:
    - name: current
      signal_type: scalar
      dtype: float64
      description: External current injection
    - name: spikes
      signal_type: event
      schema:
        ids: list[int]
  outputs:
    - name: membrane_potential
      signal_type: scalar
      dtype: float64
      emitted_unit: mV
    - name: state
      signal_type: record
      schema:
        v: float64
        u: float64
```

Use the manifest to describe what the module publishes and accepts. The runtime signal classes are still emitted from Python code.

Run-time input payload values are checked and converted against these input
declarations before the module receives them. Raw scalar, array, record, and event payloads become
typed `BioSignal` instances when exactly one accepted input profile matches. If a
port accepts multiple profiles or units, provide an explicit typed input envelope
with `value`, `signal_type`, `dtype`, `shape`, `schema`, and/or `emitted_unit`.

## `SignalSpec`: common port fields

| Field | Type | Default | Rules |
|---|---|---|---|
| `name` | string | — | Required and unique within its direction |
| `signal_type` | `scalar \| array \| record \| event` | — | Structural signal category |
| `kind` | `state \| event` | Derived | Events require `event`; all other signals require `state` |
| `dtype` | string | null | Numeric or implementation dtype; linear interpolation requires a numeric dtype |
| `shape` | integer/wildcard list | null | Arrays require shape; scalars, records, and events cannot use an array shape in runtime declarations |
| `schema` | object | null | Record field schema; records require it |
| `interpolation` | `zoh \| linear \| none` | `zoh` | Events require `none`; `linear` is numeric scalar/array only |
| `max_age` | number ≥ 0 | null | Maximum accepted state age before staleness policy |
| `stale_policy` | `ignore \| warn \| error` | `warn` | Runtime response to an older-than-`max_age` value |
| `description` | string | null | Human port description |
| `value_type` or `type` | `string \| number \| integer \| boolean \| record \| array \| file` | null | Input-form and coercion metadata |
| `format` | string | null | Format hint, such as URI or a domain-specific textual format |
| `required` | boolean | null | Whether a user/run input must be supplied |
| `default` | JSON value | null | Default run-input value |
| `advanced` | boolean | null | Marks advanced authoring/UI controls |
| `examples` | JSON[] | null | Example values |
| `allowed_values` | JSON[] | null | Closed authoring choice list |
| `file` | object | null | File-picker, media, extension, or size metadata |
| `ui` | object | null | UI presentation hints; not runtime semantics |
| `contract` | object | null | Compatibility declaration with one exact `profile` and, when relevant, `species` and `identifier_namespace` |

All defaults, examples, allowed values, `file`, `ui`, and `contract` values must be JSON-serializable.

## Input-only fields

An input can set `accepted_units` directly when its other structural fields have
one representation. Use `accepted_profiles` only when it genuinely accepts
multiple structural representations.

| Field | Type | Meaning |
|---|---|---|
| `accepted_units` | string[] | Units accepted without conversion |

`accepted_profiles` lists alternate structural representations accepted by one input. Each entry supports:

| Field | Type | Meaning |
|---|---|---|
| `signal_type` | signal enum | Accepted signal category |
| `dtype` | string | Accepted dtype |
| `shape` | list | Accepted array shape |
| `schema` | object | Accepted record schema |
| `accepted_units` | string[] | Directly accepted units |
| `format` | string | Accepted file or textual format |
| `description` | string | Representation explanation |
| `contract` | object | Optional semantic contract for this representation |

If `accepted_profiles` is omitted, the input’s own structural fields form its single accepted profile. An input cannot set `emitted_unit`.

## Output-only fields

`emitted_unit` is the unit an output sends. Outputs cannot declare
`accepted_units` or `accepted_profiles`. An output's `contract` describes what
the output sends.

## Lifecycle and staleness example

```yaml
io:
  inputs:
    - name: concentration
      signal_type: scalar
      kind: state
      dtype: float64
      interpolation: linear
      max_age: 0.5
      stale_policy: error
      accepted_profiles:
        - dtype: float64
          accepted_units: [nM, uM]
  outputs:
    - name: spike
      signal_type: event
      kind: event
      interpolation: none
      schema:
        neuron_id: string
        time: float64
```

## Dependencies

```yaml
runtime:
  python_version: "3.12"
  dependencies:
    packages:
      - numpy==1.26.4
      - scipy==1.12.0
    requirements_file: requirements.txt
```

| Field | Rule |
|---|---|
| `python_version` | Supported minor version, currently 3.10–3.14 in the open-source packager |
| `dependencies.packages` | Every installable package uses an exact `==` pin |
| `dependencies.requirements_file` | Relative path to a requirements declaration |
| `dependencies.lockfile` | Relative path to a dependency lock |

Open-source `biosimulant labs run` only installs exact-pinned package specs into the
current Python environment. Runs on the Biosimulant platform use isolated
per-lock-hash environments and apply allow/deny dependency policy. Keep manifests
portable by pinning dependencies exactly when package installation is expected.

## Remote execution requirements

Models that require accelerator-backed remote execution can declare the requirement in `runtime.remote.requirements`:

```yaml
runtime:
  remote:
    requirements:
      accelerator: gpu
      gpu_count: 1
```

`accelerator: gpu` marks the model as requiring a GPU-backed remote size. In the remote execution catalog, GPU-backed means the size has a non-empty `gpu_type`. `gpu_count` is the number of GPUs required. CPU-compatible models omit this block.

Lab authors should not duplicate model resource requirements in `lab.yaml`; Biosimulant aggregates model requirements into the resolved lab graph at run time.

## ONNX metadata

When `standard: onnx`, include an `onnx` block describing the model artifact and tensor signatures:

```yaml
onnx:
  task: classification
  model_file: data/assets/model.onnx
  class_labels: [quiescent, subthreshold, spiking]
  inputs:
    - name: input
      dtype: float32
      shape: [-1, 10]
  outputs:
    - name: probabilities
      dtype: float32
      shape: [-1, 3]
```

| Field | Meaning |
|---|---|
| `task` | Computational task, such as classification or regression |
| `model_file` | Relative path to the ONNX model file |
| `class_labels` | Output labels, in order |
| `inputs`, `outputs` | Tensor name, dtype, shape, and description metadata |

If `io` is absent, some ingestion tooling can derive ports from ONNX tensor metadata. New packages should declare `io` explicitly so every Biosimulant tool sees the same ports.

## Compatibility

A `contract` directly on an input or output tells Biosimulant which versioned
profile describes the port. A model with any contract must declare the standard
once at the top level.

```yaml
compatibility:
  standard: biosimulant.model-compatibility
  version: "0"

io:
  inputs:
    - name: protein_sequence
      signal_type: scalar
      dtype: str
      format: sequence
      contract:
        profile: protein.sequence/v1
        species: any
```

`profile` is required when the contract is present. `species` and
`identifier_namespace` are optional and only allowed when the profile permits
them. Shape, format, value type and units remain normal port fields rather than
being repeated in the contract. The v0 compatibility block accepts only
`standard` and `version`; the model does not copy catalogue or profile digests.

Contracts are optional. If only one side of a wire declares a profile, a
structurally valid connection is allowed with a `PROFILE_PARTIAL` warning. The
declared profile still checks live values, but scientific compatibility is not
verified. When both ports declare profiles, they must reference the same exact
profile and agree on representation and required context. Values are checked
again during a run.

See [Use compatibility in a model](/standards/model-compatibility/model-yaml) for
a complete example and [Propose a profile](/standards/model-compatibility/propose-profile)
when no existing profile is accurate.

## Directory layout

```text
my-model/
  model.yaml
  src/
    my_module.py
  artifacts/
  data/
    assets/
      model.onnx
  tests/
  requirements.txt
```

## Validate and build

Add the model to `biosimulant-packages.yaml` with `type: model`, then validate
and build:

```bash
biosimulant labs release validate biosimulant-packages.yaml
biosimulant labs release build biosimulant-packages.yaml --out dist/biosimulant-packages
```

Port contracts stay in `payload/model.yaml`. The package builder does not create
a separate compatibility lock file.

## See also

- [lab.yaml schema](/runtime/lab-manifest)
- [How to write a lab.yaml](/runtime/write-lab-manifest)
- [How to add an ONNX model](/runtime/add-onnx-model)
- [Model compatibility](/standards/model-compatibility)
