Minimalist compiler targeting Erlang/BEAM with content-addressed function modules.
MIT. See LICENSE.
This compiles one .core and one .beam per function (module names are hashes), and writes metadata for introspection.
cd /home/sdancer/lux
cargo run -- examples/fib.luxGenerated artifacts live under target/lux/ by default:
cat target/lux/artifacts/fib.meta.jsonRun the compiled entry function (replace module hash if different):
erl -noshell -pa /home/sdancer/lux/target/lux/artifacts -eval "io:format(\"~p~n\", ['e8a56de9f0f836e0':apply()]), halt()."Expected output for examples/fib.lux:
55
Set LUX_HOME to use a different workspace:
LUX_HOME=/tmp/lux-dev cargo run -- examples/fib.luxStart the local HTTP service:
LUX_HOME=/path/to/durable/lux-state cargo run -- --serveLUX_HOME contains the SQLite source-of-record plus rebuildable runtime
materializations. Set it outside target/ for durable development; the default
target/lux location is intended for disposable local evaluation.
Health check:
curl -sS http://127.0.0.1:4002/healthThe durable development unit is an atomic function or type symbol in a database-backed
namespace. Create a function without creating or resubmitting a .lux file:
curl -sS -X POST http://127.0.0.1:4002/symbols/put \
-H 'content-type: application/json' \
--data '{"namespace":"dev","kind":"function","symbol":"fib","source":"fn fib(n) { if n <= 1 { n } else { fib(n - 1) + fib(n - 2) } }","expected_generation":0}'That single operation validates the symbol identity, compiles it against the current namespace, publishes generation 1, and creates a snapshot. To replace the function, send only its new definition plus the returned generation and revision ID:
curl -sS -X POST http://127.0.0.1:4002/symbols/put \
-H 'content-type: application/json' \
--data '{"namespace":"dev","kind":"function","symbol":"fib","source":"fn fib(n) { if n < 2 { n } else { fib(n - 1) + fib(n - 2) } }","expected_generation":1,"expected_revision_id":"REVISION_ID"}'Because artifacts contain immutable dependency hashes, a replacement also rebuilds the transitive callers of the old artifact in that same atomic generation. Their source revisions do not change, and unrelated bindings are carried forward.
Inspect symbol metadata or one symbol’s immutable source revision:
curl -sS 'http://127.0.0.1:4002/symbols?namespace=dev'
curl -sS 'http://127.0.0.1:4002/symbol?namespace=dev&kind=function&symbol=fib'Type symbols use kind: "type"; a type edit rebuilds affected published function
artifacts and commits all resulting bindings in the same generation. The older
changeset endpoints remain for bulk import and provenance compatibility, not normal
editing.
Use the namespace REPL for the same workflow interactively:
cargo run --bin repl -- --namespace devOr expose the same symbol-native operations over MCP stdio while the service is running:
cargo run --bin lux-mcp -- --server http://127.0.0.1:4002The authoring MCP surface is lux.list_symbols, lux.get_symbol, and
lux.put_symbol. lux.put_symbol is the atomic compile/publish/snapshot operation;
it never asks an agent to calculate source offsets or submit a whole namespace.
See Namespace-Native Development for atomic symbol revisions, optimistic concurrency, MCP tools, and inspection endpoints. The current Albion guest-account port and its remaining capability work are tracked in Albion Clientless Port.
Run published code from a snapshot:
curl -sS -X POST http://127.0.0.1:4002/run \
-H 'content-type: application/json' \
--data '{"snapshot_id":1,"target":"main"}'Deploy a long-running instance from a snapshot:
curl -sS -X POST http://127.0.0.1:4002/deploy \
-H 'content-type: application/json' \
--data '{"snapshot_id":1,"target":"main"}'List and inspect instances:
curl -sS http://127.0.0.1:4002/instancescurl -sS http://127.0.0.1:4002/instances/<instance_id>Stop an instance:
curl -sS -X POST http://127.0.0.1:4002/instances/<instance_id>/stopEvaluate an ephemeral snippet against a snapshot:
curl -sS -X POST http://127.0.0.1:4002/eval \
-H 'content-type: application/json' \
--data '{"snapshot_id":1,"source":"fn main() { fib(10) }"}'Execution limits can be set per request:
curl -sS -X POST http://127.0.0.1:4002/eval \
-H 'content-type: application/json' \
--data '{"snapshot_id":1,"source":"fn main() { main() }","limits":{"timeout_ms":50}}'curl -sS -X POST http://127.0.0.1:4002/eval \
-H 'content-type: application/json' \
--data '{"snapshot_id":1,"source":"fn main() { \"...large value...\" }","limits":{"output_limit_bytes":1024}}'Inspect recent execution summaries:
curl -sS http://127.0.0.1:4002/executionsInspect namespaces and frozen snapshots:
curl -sS http://127.0.0.1:4002/namespacescurl -sS http://127.0.0.1:4002/namespaces/devcurl -sS http://127.0.0.1:4002/snapshots/1Diff two namespace generations:
curl -sS 'http://127.0.0.1:4002/namespaces/dev/diff?from=1&to=2'Filter and page execution summaries:
curl -sS 'http://127.0.0.1:4002/executions?request_kind=eval&status=success&limit=10'Prune old execution summaries:
curl -sS -X POST http://127.0.0.1:4002/executions/prune \
-H 'content-type: application/json' \
--data '{"finished_before_ms":9999999999999}'Start the service first:
cargo run -- --serveUse a different port:
cargo run -- --serve --port 4010Then start the REPL client:
cargo run --bin replPoint the REPL at a specific port:
cargo run --bin repl -- --port 4010Minimal session:
:namespace repl
:symbol function fib 1
:paste
fn fib(n) { if n <= 1 { n } else { fib(n - 1) + fib(n - 2) } }
:end
:save
fib(10)
The REPL is a thin client over the HTTP service. Bare lines are evaluated as expressions by wrapping them in fn main() { ... }.
Lux stores credentials and device/session state as encrypted, versioned namespace
resources in SQLite. By default the service creates or loads
$LUX_HOME/resource-key.hex; on Unix it requires a private regular file and creates
it with mode 0600. LUX_MASTER_KEY_FILE selects another key file and
LUX_MASTER_KEY_HEX provides an explicit process-level override.
Back up the key with the database: losing either one loses access to the protected
values. Normal resource APIs and MCP tools expose metadata only. Plaintext reveal is
a local administrative path gated by LUX_ALLOW_RESOURCE_REVEAL=1, and is never an
MCP tool.
The intended security boundary for Lux is the language and type/capability system, not OS sandboxing.
- No syscall / network isolation is intended as the primary enforcement mechanism.
- No per-job OS sandboxing is intended as the primary enforcement mechanism.
- The goal is to make unsafe APIs unavailable at compile time by not admitting them into the typed execution environment.
- In other words, code should be unable to name or typecheck against capabilities that are not explicitly present in the environment.
This is meant to be a stronger and more principled model than relying on ad hoc runtime blocking. It should ultimately stand on the correctness of the language, type system, and capability surface, rather than on post hoc process restrictions.
Current runtime limits such as timeouts and output caps are still useful operational controls, but they are not the long-term security story.