<!-- Source: https://docs.biosimulant.com/how-to/write-lab-manifest -->

# How to Write a lab.yaml

`lab.yaml` composes models into a graph with shared runtime settings and explicit
wiring. Keep a `README.md` beside it for the About content users see in
Biosimulant.

## Annotated example

```yaml
schema_version: "2.0"
title: "Predator-Prey Ecosystem"
description: "Lotka-Volterra style toy composition"

models:
  - path: ../models/environment
    alias: env
    parameters:
      seed: 42

  - path: ../models/organism-population
    alias: rabbits
    parameters:
      name: Rabbits
      initial_count: 100

  - path: ../models/predation
    alias: predation
    parameters:
      predation_rate: 0.01

wiring:
  - from: env.conditions
    to:
      - rabbits.conditions
  - from: rabbits.population_state
    to:
      - predation.prey_state

runtime:
  duration: 100.0
  communication_step: 0.1
  settle_steps: 0
```

## Key rules

- `runtime.communication_step` is required and must be positive
- `runtime.settle_steps` is optional, defaults to `0`, and must be a non-negative integer
- set input values when starting a run; persistent scalar defaults should be model or lab parameters when appropriate
- the old external tick field is not supported
- every model entry needs an `alias` and a relative `path`; validation rejects `package` or `version` on a model entry
- model entries should not declare legacy per-module timing or ordering fields
- wiring references use `alias.port`

For finite downstream report, export, or visualisation work, implement
`execute()` with `ExecutionPolicy.ONCE_AFTER_RUN`; BioWorld drains any dependent
chain automatically. Keep `settle_steps` only for legacy temporal modules that
deliberately need zero-time propagation. Settling does not extend simulated time
and does not invoke canonical modules.

## Compose child labs

A child lab is a composed subgraph that a parent lab uses through explicit
external ports.

### Expose the child's ports

```yaml
schema_version: "2.0"
title: "E/I Microcircuit"

models:
  - path: ../../models/excitatory
    alias: exc
  - path: ../../models/inhibitory
    alias: inh

wiring:
  - from: exc.spikes
    to:
      - inh.input_spikes

runtime:
  duration: 1.0
  communication_step: 0.001

io:
  inputs:
    - name: external_current
      maps_to: exc.current
  outputs:
    - name: exc_spikes
      maps_to: exc.spikes
```

Without `io`, the child lab has no external ports for parent wiring.

### Reference the child from the parent

```yaml
children:
  - path: ../ei-microcircuit
    alias: circuit_a
    parameters: {}

wiring:
  - from: stim.current
    to:
      - circuit_a.external_current
```

A child uses either `path` or `package` + `version`, not both. The old `lab_id`
form is rejected.

### How children are flattened

During execution, Biosimulant flattens child labs with the same rules as the
open-source `biosimulant` runtime. Child model aliases are scoped as
`child_alias.model_alias`, and parent wiring references to
`child_alias.external_port_name` are remapped through the child lab's `io.maps_to`
contract. Child lab nesting is limited to depth 5, circular child references are
rejected, and unresolved child labs fail validation/execution instead of being
silently skipped.

Package-backed child Labs require a sibling `biosimulant.lock` entry with the
same `package`, exact `version`, and the Hub archive's `artifact_sha256`. See
[Compose Hub Labs Locally](/how-to/compose-hub-labs-locally).

## Validate

```bash
biosimulant labs validate ./my-lab
```

To validate and build release packages, see [Package & Publish](/how-to/package-and-publish).

## Next steps

- [lab.yaml Schema Reference](/references/lab-manifest)
- [Compose Hub Labs Locally](/how-to/compose-hub-labs-locally)
- [Use a BioWorld](/how-to/use-bioworld)
