<!-- Source: https://docs.biosimulant.com/references/biomodule-api -->

# BioModule API

`BioModule` is the single runnable unit in the communication-step kernel. Temporal
modules advance state across windows; finite modules implement input-to-output
computation behind the same contract.

Invocation policies require `biosimulant>=0.0.26`. Model and Lab manifests stay
on the existing schema and do not declare the policy.

## Contract

```python
class BioModule:
    execution_policy = ExecutionPolicy.EACH_WINDOW

    def setup(self, config: dict[str, Any] | None = None) -> None: ...
    def reset(self) -> None: ...
    def set_inputs(self, signals: dict[str, BioSignal]) -> None: ...
    def execute(
        self,
        inputs: Mapping[str, BioSignal],
        *,
        context: ExecutionContext,
    ) -> Mapping[str, Any | BioSignal]: ...
    def advance_window(self, start: float, end: float) -> None: ...
    def get_outputs(self) -> dict[str, BioSignal]: ...
    def inputs(self) -> Mapping[str, SignalSpec]: ...
    def outputs(self) -> Mapping[str, SignalSpec]: ...
    def snapshot(self) -> dict[str, Any]: ...
    def restore(self, snapshot: Mapping[str, Any]) -> None: ...
    def visualize(self) -> VisualSpec | list[VisualSpec] | None: ...
```

New modules override the canonical `execute()` hook and explicitly declare a
policy. Existing temporal modules may continue overriding `advance_window()` and
normally `get_outputs()`. A module must not override both computation hooks.

## Required semantics

- `execute(inputs, *, context)` is the canonical hook for new finite and temporal
  modules.
- `advance_window(start, end)` is the supported temporal compatibility hook and is
  not deprecated.
- `ExecutionPolicy.EACH_WINDOW` is the compatibility default.
- Use `ONCE_BEFORE_RUN` for preprocessing, finite inference, and pure one-shot
  pipelines; use `ONCE_AFTER_RUN` for final analysis and export.
- Use canonical `EACH_WINDOW` when AI computation consumes evolving state or a new
  settle-independent temporal module advances through positive windows.
- A canonical module that implicitly inherits `EACH_WINDOW` is accepted with a
  registration warning; first-party and generated modules declare it explicitly.
- Canonical `get_outputs()` returns only the latest result successfully normalized
  and committed by BioWorld. Direct `execute()` calls do not update this cache.
- Existing temporal `get_outputs()` must return declared typed signals.
- `inputs()` and `outputs()` must return `port -> SignalSpec` mappings when the module participates in validated wiring.
- Signals should be emitted as `ScalarSignal`, `ArraySignal`, `RecordSignal`, or `EventSignal`.
- `snapshot()` / `restore()` should round-trip the module state needed for deterministic continuation from a communication boundary.

## Canonical temporal example

```python

class Gain(biosim.BioModule):
    execution_policy = biosim.ExecutionPolicy.EACH_WINDOW

    def __init__(self, gain: float = 1.0) -> None:
        self.gain = float(gain)

    def inputs(self):
        return {
            "x": biosim.SignalSpec.scalar(
                dtype="float64",
                max_age=0.2,
                stale_policy="warn",
            )
        }

    def outputs(self):
        return {"y": biosim.SignalSpec.scalar(dtype="float64")}

    def execute(self, inputs, *, context):
        latest = inputs.get("x")
        if latest is None:
            return {}
        assert context.window_start is not None
        assert context.window_end is not None
        return {"y": float(latest.value) * self.gain}

    def snapshot(self):
        return {"gain": self.gain}

    def restore(self, snapshot):
        self.gain = float(snapshot["gain"])
```

BioWorld stamps the result at `context.window_end`. Canonical modules never run
during zero-time `settle()` in 0.0.26; retain the temporal compatibility hook when
settling is scientifically required.

## Finite-computation example

```python
class Predictor(biosim.BioModule):
    execution_policy = biosim.ExecutionPolicy.ONCE_BEFORE_RUN

    def inputs(self):
        return {"features": biosim.SignalSpec.array(dtype="float32", shape=(4,))}

    def outputs(self):
        return {"score": biosim.SignalSpec.scalar(dtype="float64")}

    def execute(self, inputs, *, context):
        return {"score": float(predict(inputs["features"].value))}
```

BioWorld wraps raw results using the output specs and stamps them at the current
BioWorld boundary. Connected non-optional inputs must be available before
invocation. Completed once-policy modules are not polled or retimestamped.

Valid phase edges move from before-run to each-window to after-run. Cycles remain
valid only inside the each-window phase.

## ExecutionContext

`ExecutionContext` is immutable. `run_start` and `run_end` describe the positive
public `BioWorld.run()` call. `window_start` and `window_end` are present only for
`EACH_WINDOW`; `simulated_time` resolves to the start, window end, or run end for
before, window, or after invocation respectively. Wall-clock IDs, retries, and
platform scheduling metadata are deliberately excluded.

`BioWorld.run(duration <= 0)` remains setup-only, so once policies require a
positive-duration run. `ONCE_AFTER_RUN` runs only after all positive windows commit
successfully.

## Convenience subclasses

`BioModule` remains the escape hatch for full control. Two optional subclasses
remove common adapter boilerplate without changing the base contract.

### `SignalEmitterBioModule`

Use `SignalEmitterBioModule` when your module owns its lifecycle but wants
shared output storage and typed signal wrapping.

```python
class Reporter(biosim.SignalEmitterBioModule):
    def outputs(self):
        return {"summary": biosim.SignalSpec.record(schema={"count": "int"})}

    def advance_window(self, start, end):
        self.publish_outputs(end, {"summary": {"count": 3}})
```

It provides `source_name()`, `emit_signal()`, `publish_outputs()`,
`get_outputs()`, and `clear_outputs()`.

### `StatefulBioModule`

Use `StatefulBioModule` for fixed-step stateful models. Subclasses implement
biology hooks and let the base handle window stepping, input overrides, and
bounded history.

```python
class Counter(biosim.StatefulBioModule):
    def __init__(self):
        super().__init__(integration_step=0.1, record_initial_state=True)
        self.count = 0

    def outputs(self):
        return {"count": biosim.SignalSpec.scalar(dtype="int64")}

    def step(self, h):
        self.count += 1

    def record_state(self, t):
        self._history.append({"t": t, "count": self.count})

    def output_payload(self, t):
        return {"count": self.count}
```

## Port declarations

```python
def inputs(self):
    return {
        "state": biosim.SignalSpec.record(
            schema={"v": "float64", "u": "float64"},
            max_age=0.001,
            stale_policy="error",
        )
    }

def outputs(self):
    return {
        "spikes": biosim.SignalSpec.event(
            schema={"ids": "list[int]"},
            description="Discrete spike deliveries",
        )
    }
```

Outputs declare the emitted profile. Inputs declare the accepted profile and freshness policy.

## Durability

`reset()` is an optional convenience hook for UI-driven workflows you own. The kernel durability contract is `snapshot()` / `restore()`, which is what `BioWorld.branch()` and snapshot-based reruns rely on.

## Notes

- The world binds emitted outputs to the registered module name and the declared output port.
- Empty output mappings do not clear prior committed state outputs from that module.
- `visualize()` is optional and should return JSON-serializable visual specs.

## See Also

- [BioWorld API](/references/bioworld-api)
- [BioSignal & SignalSpec](/references/signals)
- [How to Create a BioModule](/how-to/create-biomodule)
