Host Composition
Understand how a Lenso Host composes Linked Modules and connects independently running Services.
Host composition is the place where a Lenso app decides which modules exist in the running host.
It is deliberately separate from module manifests:
- a manifest says what one module contributes;
- host composition says which linked modules the app owns;
- Service installation state says which providers supply Modules;
- runtime config can enable or disable modules at startup.
The generated host shape
Generated hosts expose one function used by the API, worker, and migration entrypoints:
mod modules;
use lenso::host::prelude::*;
pub fn host_composition() -> HostComposition {
HostBuilder::new()
.linked_module(modules::app::linked_module())
.build()
}
The API, worker, and migration binaries all call that same composition so HTTP routes, runtime handlers, event handlers, and migrations stay aligned.
Built-in profiles
LENSO_COMPOSITION_PROFILE selects the platform’s linked baseline.
| Profile | Built-in linked modules |
|---|---|
core |
platform-story |
demo |
auth, auth-anonymous, auth-oauth, auth-password, auth-github, auth-google, auth-oidc, platform-story |
Local development defaults to demo. Production-like environments must set the
profile explicitly:
APP_ENV=production
LENSO_COMPOSITION_PROFILE=core
Use core for product hosts that install modules intentionally. Use demo for
local examples and starter proof paths.
Host-owned linked modules
First-party modules that belong inside the app can be linked into the host:
use lenso::host::prelude::*;
pub fn host_composition() -> HostComposition {
HostBuilder::new()
.linked_module(builtins::auth())
.linked_module(builtins::auth_password())
.linked_module(modules::app::linked_module())
.build()
}
A linked module can provide:
- manifest metadata;
- migrations;
- linked HTTP routes;
- admin data, actions, and queries;
- runtime function handlers;
- event handlers;
- host-only contributions.
Services do not become a second Module source in HostBuilder. An independently
running Service owns its process, Store, and delivery; the Host connects to it
through provider and System Plane contracts.
Enable and disable modules
Every Linked Module gets a generated startup toggle:
modules.<module>.enabled
Environment values use the matching LENSO_MODULE_*_ENABLED form:
LENSO_MODULE_AUTH_PASSWORD_ENABLED=false
LENSO_MODULE_SUPPORT_ENABLED=false
These toggles are restart-only. Disabling a module stops loading its routes, runtime handlers, admin metadata, story display metadata, and migrations for the current startup path. It does not delete that module’s existing data.
Dependencies
Module dependencies are declared in the manifest:
ModuleManifest::builder("auth-password")
.dependencies(vec!["auth".to_owned()])
.build()
If a dependency is disabled, the dependent module is not loaded and Runtime Console shows that dependency state in module metadata.
Service providers
Use lenso service install <manifest> for provider installation and
lenso service doctor <provider> --json for readiness and drift. The Service
manifest declares its provided Modules; the Host does not reinterpret the
Service process as a Remote Module.
What to change where
| Need | Change |
|---|---|
| Add an app-owned Rust module | src/lib.rs host composition |
| Add built-in auth to a product host | HostBuilder::linked_module(builtins::auth()) |
| Install a Service provider | lenso service install <service-manifest-url> |
| Disable a module at startup | LENSO_MODULE_<MODULE>_ENABLED=false |
| Configure module boot values | LENSO_MODULE_<MODULE>__<KEY>=<value> |
| Make local catalog data discoverable | .env LENSO_MODULE_CATALOG_FILE=<path> |
Smoke check
After changing composition, run the real host path:
lenso serve
Then open /console and check:
- Modules shows the expected Linked Modules and installed Service providers;
- disabled modules have an explainable status;
- expected routes or admin surfaces appear;
- Runtime Stories still load.