Lambda Prelude is built for long-running backend services. The basic approach is to produce a single native binary and run it under a Linux init system, container, or orchestrator.

Build

lambda-prelude build src/main.lp --out my-service

The output is a real native binary linked by ocamlopt. No interpreter is required at runtime. Sends whose receiver class the type checker can pin down are lowered to direct OCaml calls; the rest go through the runtime dispatcher the binary links.

Cross-Platform Targets

Both are exercised before a feature is considered complete. A change that works only on one platform is treated as incomplete.

Plugins

PostgreSQL / MariaDB / MySQL, Redis / Valkey, SMTP, and TORM (the dao: / entity: keywords) are plugins, separate from the core. A program brings one in by either of two routes, and the AOT binary treats them differently.

Through imports: — a name with no .lp module of its own, such as imports: (Torm), resolves to a plugin. The interpreter opens it with OCaml's Dynlink as the program is loaded, which is what makes the plugin's DSL keywords (dao: and the like) available while the source is being read. In the AOT binary a plugin resolved this way is linked at build time and lives inside the binary: what ships is still one file, nothing has to find it at run time, nothing has to be placed beside the executable, and the profile it was built under stops being a deployment question.

Through Plugin load: 'name' — this is a run-time send, so under the interpreter and the AOT binary alike the .cmxs is searched for and opened with Dynlink at that point. The stdlib pools (PostgresPool / MariadbPool / RedisPool) and the Smtp module take this route, and the matching plugin has to be on the run-time search path.

Search order when opened at run time (first hit wins):

  1. $LAMBDA_PRELUDE_PLUGINS if set — single override directory
  2. ./plugins/ under the working directory
  3. <exe_dir>/plugins/ next to the running binary (installed layout)
  4. <exe_dir>/../plugins/ (Linux package layout)
  5. _build/default/plugins/ (dev workflow)

No plugin capability differs between the interpreter and the AOT binary; the only difference is that a plugin brought in through imports: is linked in.

Service Layout

A small service typically looks like:

my-service/
  main.lp
  config.lp
  service/
    XA001Save.lp
    XA001GetAll.lp
  tests/
    save_test.lp
    getall_test.lp

lambda-prelude new my-service scaffolds the entry point and tests directory.

lambda-prelude new my-service
cd my-service
lambda-prelude run main.lp
lambda-prelude test tests
lambda-prelude build main.lp --out my-service

Init Integration

The binary is a regular long-running process. Wire it into the host system the same way any other daemon would be:

No companion VM, no runtime image to upload, no JIT warmup window.

Crash Recovery

Within the binary, supervision trees handle actor-level failures. Across binaries, the host system's restart policy handles process-level failures.

Treat the supervisor tree as the inner layer (millisecond-scale, in-process restart) and the OS init system as the outer layer (second-scale, process-level restart). This is the same separation Erlang uses between supervisor and heart.