# Loader Configuration

The module loader decides which modules a process loads and how it reacts to load failures. It is driven entirely by settings, so the same image becomes a monolith or a single-module service by configuration alone.

## Settings

| Setting | Meaning |
| --- | --- |
| `SANDLOOM_MODULES` | modules to load explicitly (JSON list of module names or import paths) |
| `MODULE_LOAD_ENTRYPOINTS` | also load every installed module package (the `sandloom.modules` entry-point group) |
| `MODULE_WHITELIST` | allow only listed modules |
| `MODULE_BLACKLIST` | skip listed modules |
| `MODULE_REQUIRED` | modules whose load failure aborts startup |
| `MODULE_FAIL_FAST` | raise on first required failure vs aggregate at the end |

See the [Configuration Reference](<https://sandloom.dev/docs/reference/configuration/index.md>) for defaults.

## Explicit list

```
SANDLOOM_MODULES='["identity", "tenancy", "billing"]'
```

`create_app()` reads `SANDLOOM_MODULES`, imports each module, and calls its `register(core)`. Only the listed modules' routes, event schemas, and services exist in that process. An entry is a module name (`identity`, the `sandloom.modules` entry-point name of an installed module package) or an import path (`sandloom_identity`, `myapp.modules.tickets`); `MODULE_REQUIRED`, the whitelist and the blacklist accept the same spellings.

`sandloom.main:app`, the framework's own composition root, composes every installed module package (sorted by name) when `SANDLOOM_MODULES` is unset: with `sandloom[standard]` installed that is identity, tenancy and billing. The kernel names no module.

An application generated by `sandloom new` needs no list: `create_app` loads every module of the app's `modules` package, so `sandloom add module <name>` is all it takes to add one. Set `SANDLOOM_MODULES` only to select a subset (a single-module service in the microservices profile, for example).

## Module packages

A distribution ships a module by registering it in the `sandloom.modules` entry-point group. The entry-point name is the module name; the value is the import path:

```
# in the module package's pyproject.toml
[project.entry-points."sandloom.modules"]
tickets = "acme_tickets"
```

Install it and name it in `SANDLOOM_MODULES` (`"tickets"`), or load every installed module package:

```
MODULE_LOAD_ENTRYPOINTS=true
```

A requested module name must have one provider. Two installed packages registering the same name, or an installed package and an app-local module with the same name (`myapp.modules.tickets` next to `acme_tickets`), are refused before anything loads ([SANDLOOM-B020](<https://sandloom.dev/docs/reference/error-codes/#sandloom-b020>)): uninstall the package or remove (rename) the local module. See [Module packages](<https://sandloom.dev/docs/modules/guarantees/module-internals/#module-packages>).

`MODULE_FS_SCAN` no longer exists (it scanned `sandloom.*`, which holds no module); setting it to true fails startup.

## Required modules and fail-fast

A module is required unless it says otherwise in its `define_module` call:

```
from sandloom.sdk import define_module

register = define_module(__name__, version="1.0.0")                 # required
# register = define_module(__name__, version="1.0.0", optional=True)  # may fail to load
```

- A required module is only enforced when it is actually present in `SANDLOOM_MODULES`, so a billing-only service does not need identity co-deployed.
- With `MODULE_FAIL_FAST=true` the loader re-raises on the first required failure; otherwise it aggregates and raises `ModuleLoadError` at the end.

## Migration chain keys

Each loaded module with a `migrations/` directory owns a chain keyed by its module name (the entry-point name of a module package, else the last dotted segment of its import path), or by `ModuleDescriptor.migrations_chain`. Two loaded modules with the same key (`app_a.modules.notes` and `app_b.modules.notes`) are refused instead of silently sharing `alembic_version_notes` — set `migrations_chain` on one of them. See [Migrations Model](<https://sandloom.dev/docs/modules/database/migrations/index.md>).

## Diagnostics

Duplicates and whitelist/blacklist skips are recorded in the load summary, and the configured modules are reported at `/diagnostics/*` (requires `INTERNAL_API_TOKEN`).
