# Runner scripts

Each stage ships as a thin class **plus a self-contained runner script**. The runner is
what actually does the science, and it is the only part of MatMelt that ever reaches a
compute backend: the stage declares it as an input artifact, the backend stages it into
the job's private directory, and the job executes it there.

Three properties follow from that design, and they are contracts rather than
conveniences:

- **A runner imports only the standard library and its own science packages** — never
  `matmelt`. This is what lets a remote machine run MatMelt jobs while holding none of
  MatMelt's code. A test asserts it for every runner by parsing the import graph.
- **Every science import is lazy**, inside a function body. A runner therefore loads —
  and answers `--help` — on a machine with no science stack at all, which is also why
  building these docs needs no SMACT, PyXtal, MACE, ASE or AiiDA installed.
- **A missing dependency exits 3** with the exact `pip install` command and the path of
  the interpreter that lacked it. Guessing the install name is not good enough: `mace`
  is installed as `mace-torch`.

## Running one by hand

Because a runner is self-contained, you can execute it directly on a material's
directory to debug a stage in isolation, without the orchestrator, the state store, or a
config file:

```console
$ cd ~/matmelt-work/Cl1Na1_3D-225
$ python3 ~/path/to/screen_runner.py --help
$ python3 ~/path/to/screen_runner.py --cif structure.cif --device cpu
```

The exit codes are uniform across all of them: `0` the work completed, `2` a usage error
or an unusable input, `3` a missing dependency. Note that "completed" is not "the science
succeeded" — a screening that rejects a structure still exits 0, because it *did* screen
it. The verdict lives in the file the runner wrote, which is exactly the separation the
stage relies on when it interprets the result.

```{eval-rst}
.. automodule:: matmelt.core.stages.compose_runner
   :members:
```

```{eval-rst}
.. automodule:: matmelt.core.stages.structure_runner
   :members:
```

```{eval-rst}
.. automodule:: matmelt.core.stages.screen_runner
   :members:
```

```{eval-rst}
.. automodule:: matmelt.core.stages.calculate_runner
   :members:
```

```{eval-rst}
.. automodule:: matmelt.core.stages.extract_runner
   :members:
```

```{eval-rst}
.. automodule:: matmelt.core.stages.cif_runner
   :members:
```
