<!-- Source: https://docs.biosimulant.com/references/library/concepts -->

# How Biosimulant Works

`Biosimulant` is an open-source Python engine for composing biological simulators out of small modules with explicit typed ports. The current kernel is a communication-step orchestrator: every module advances across the same window, and outputs are committed atomically at the boundary.

If you want a runnable introduction, start with the [Library Quickstart (Python)](/overview/library-quickstart). This page explains the architecture behind that example.

## The five core primitives

| Primitive | Role |
|-----------|------|
| `BioModule` | A runnable simulation component |
| `BioWorld` | The communication-step orchestrator |
| `SignalSpec` | A declared port contract |
| Typed `BioSignal` subclasses | Runtime payloads crossing module boundaries |
| `WiringBuilder` | A validated graph builder for named module ports |

## BioWorld: communication windows

Create a world with a required communication step:

```python
world = biosim.BioWorld(communication_step=0.1)
```

For each window `[t, t + communication_step]`:

1. the world reads committed signals at `t`
2. it delivers those inputs to every connected target module
3. every module advances across the same window via `advance_window(start, end)`
4. the world commits all outputs atomically at `t + communication_step`

This means:

- signal exchange is synchronized at shared communication boundaries
- state signals use hold-last-value semantics until replaced
- event signals are delivered once per connection per source timestamp
- stale reads are checked from the consuming port's `SignalSpec`

The kernel does not expose an execution-order scheduling contract, a global external tick parameter, rollback, or algebraic-loop solving.

Final outputs are still committed at the final boundary. Downstream modules see
those outputs on a later communication turn. For workflow-style labs with report,
export, or visualization modules downstream of a final producer, runners can use
`settle_steps` / `world.settle()` to perform zero-time propagation turns after
the simulation duration without changing simulated time.

## BioModule: the runnable unit

Modules no longer participate in a per-module due-time scheduler. The world-facing hook is:

```python
def advance_window(self, start: float, end: float) -> None:
    ...
```

The full module surface includes:

- `setup(config=None)`
- `set_inputs(signals)`
- `advance_window(start, end)`
- `get_outputs()`
- `inputs()` / `outputs()` returning `port -> SignalSpec`
- `snapshot()` / `restore(snapshot)`
- optional `visualize()`

`reset()` still exists as a convenience method on the base class, but snapshot/restore is the kernel durability mechanism for branching and replay.

## Signals: declared contracts plus typed payloads

Port declarations use `SignalSpec`:

```python
def outputs(self):
    return {
        "membrane_potential": biosim.SignalSpec.scalar(
            dtype="float64",
            emitted_unit="mV",
        )
    }
```

Runtime payloads are emitted as one of:

- `ScalarSignal`
- `ArraySignal`
- `RecordSignal`
- `EventSignal`

All runtime signals carry:

- `source`
- `name`
- `value`
- `emitted_at`
- optional bound `SignalSpec`

`BioSignal` itself is an abstract base class. Do not instantiate it directly.

## Signal freshness and event semantics

The world preserves `emitted_at` when routing signals. Consumers can declare:

- `max_age`: how stale a state signal may be before it is considered too old
- `stale_policy`: `warn`, `error`, or `ignore`

Event signals are handled differently from state signals:

- state signals remain available until replaced by a later non-empty output mapping
- event signals remain in the store but are delivered once per connection per source timestamp

`SignalSpec.interpolation` is currently a declared policy, not a runtime interpolation engine. The world reads committed boundary values.

## Wiring and composition

Graphs are built explicitly:

```python
builder = biosim.WiringBuilder(world)
builder.add("eye", Eye()).add("lgn", LGN())
builder.connect("eye.visual_stream", ["lgn.retina"])
builder.apply()
```

Connection validation happens against declared `SignalSpec` contracts. That keeps the orchestration layer generic: a module can be an ODE solver, an ONNX wrapper, or a bridge to an external simulator, and the world still treats it the same way.

## Snapshots and branching

`BioWorld.snapshot()` captures:

- current simulation time
- committed signal store
- connection delivery state
- per-module snapshots
- setup config

`restore()` rehydrates that world state, and `branch()` deep-copies the world so two futures can diverge from the same communication boundary.

## Manifests and packaged composition

The current manifest surface matches the current kernel:

- models declare `biosim.entrypoint` and required `biosim.communication_step`
- labs declare required `runtime.communication_step`
- labs may declare optional `runtime.settle_steps` for final graph propagation
- labs should not declare legacy per-entry timing, ordering, or external tick fields
- port metadata should prefer structured typed `io` entries over bare strings

## Labs Serve UI and platform layers

Labs Serve UI and the hosted platform sit above the same kernel. They consume:

- world lifecycle events such as `STARTED`, `STEP`, and `FINISHED`
- transport-safe visualization specs from `visualize()`
- snapshots and status payloads for streaming or replay

Those layers do not change the kernel semantics. They expose them through browser and API surfaces.

## See Also

- [BioWorld API](/references/library/bioworld-api)
- [BioModule API](/references/library/biomodule-api)
- [BioSignal & SignalSpec](/references/library/signals)
- [How to Serve a Local Lab](/how-to/library/serve-local-lab)
