Leumas LMX
Write, run or extend an LMX script — the 16 interpreters, incl. lma (call any tool), lmi (ask the install), lmq (query), lmai (model), lmg (grids). Read before writing one or adding a mode.
Writing LMX
LMX (shared/engines/lmx, @leumas/lmx) runs 16 small languages in a sandboxed, time-bounded worker thread. Nine are general computing. Five speak Leumas, and two of those are dynamic — their vocabulary is a live registry, so what they can do grows with the ecosystem:
| Want to… | Use | Needs |
|---|---|---|
| call any tool this install has | lma dynamic | the tools bridge → POST /api/lmx/run |
| ask what this deployment can do | lmi dynamic | the install bridge → POST /api/lmx/run |
| read stored rows | lmq | the db bridge |
| drive a language model | lmai | the ai bridge |
| compose a programming grid | lmg | nothing — runs anywhere |
| general logic + one adapter call | lmx | adapters for adapter() |
| a quick calculation | lmy · lmm · lmc | |
| events, notifications | lmz · lmn | |
| shell, GUI, network, timers | lms lmk lmd lmnet lmt | admin + an env flag |
Full generated list with syntax and snippets: shared/engines/lmx/src/INTERPRETERS.md. Deep references: reference/lma.md · reference/lmi.md · reference/lmq.md · reference/lmai.md · reference/lmg.md.
Reach for lma first
Before writing a bespoke script, remember lma can already call every adapter tool, every stored functioncall and user integration, every automation action, every MCP tool and every vendor-neutral capability — ~1,800 verbs, resolved live:
PLANES # what planes exist here
DISCOVER "invoice" # search all of them
DESCRIBE a-text.Base64Encode # declared params
USE a-text.Base64Encode { "text": "{{input}}" } -> e
EACH row IN rows USE a-text.Summarize { "text": "{{row.body}}" } -> summaries
TRACE # what actually ran, and which provider served a cap:
And lmi answers "what can this install do" rather than "what can Leumas do":
INSTALL
CAN ai.text.generate # this USER's entitlement, three-state
PROVIDERS FOR email.send # who would actually serve it here
LIST tools WHERE category = "media" LIMIT 20
Run one
leumas-lmx ./script.lmg # lmg and the general modes need no host
curl -s -X POST localhost:3000/api/lmx/run -H 'content-type: application/json' \
-d '{"mode":"lmi","source":"INSTALL"}' # lma/lmi/lmq/lmai need bridges
GET /api/lmx/interpreters is the live catalog for a given install; GET /api/lmx/demos returns the bundled scripts with source — the fastest working example of any mode.
The six traps
- [critical] An
lmaireply is not proof a model ran.resolve()never fails —autofalls back to an
echo stub. Branch on usage.stub. Worst case, verified live: a shaped ask on the stub returns your own SHAPE example as a flawless-looking answer, with stub:true, tokens:0 the only tell.
- [critical]
lmimust never answer "yes" by default. No entitlement engine wired ⇒unknown, never
true. An unknown reported as yes is a confident wrong claim about someone else's deployment.
- [critical] Entitled ≠ fulfillable.
CANandPROVIDERS FORare different questions; a user can be
entitled to a capability this install has nothing connected to serve.
- [critical]
lmgcells go inspecialConfig, notprops. Both execute; onlyspecialConfig(and a
top-level code) is read by the dependency graph, so a props-authored grid prints an empty GRAPH.
lmqtimestamps are epoch milliseconds. Usedays_ago(n), not a date string.- A bridge-needing mode REFUSES without its bridge rather than returning empty. *"needs the …
bridge"* means you are on the CLI or the lmx adapter — use POST /api/lmx/run.
What bounds a script
Everything a script can reach is a named channel the host wired. Nothing else escapes the worker.
| Channel | For | Bounded by |
|---|---|---|
adapter | the adapter() builtin | — (unmetered; see below) |
db | lmq | read-only by construction · /db's deny lists · per-row canRead · 5000 rows |
ai | lmai | resolveKeyForUser · 20 calls · 60s |
tools | lma | host scope + USING · 50 calls · passnode.charge · 30s |
install | lmi | read-only; there is no method that changes anything |
All are factories called per request with req.user. Never cache one across users.
[critical] Metering asymmetry. /api/adapters/:system/:fn is priced by passnode.guard, but that lives in the HTTP router — registry.run() is not metered, so non-HTTP callers are free. lma closes this for itself by charging the same tool: target; the older adapter() builtin is still unmetered.
Adding an interpreter
| File | Add |
|---|---|
src/interpreters/<id>/{index,parser,runner}.js | run({ filename, source, bridges, inputs }) |
src/host/interpreters.catalog.js | one entry — lights up the API, Playground, tooltips and Reference tab |
src/host/worker.js | one case in dispatch() (+ a channel proxy if it needs one) |
src/host/demos.js | one EXT_TO_MODE entry |
src/index.js | resolveMode() + CLI dispatch |
Prefer a dynamic design. If the new language needs a vocabulary, ask whether a registry already holds it — planes.js turns any registry's exported functions into verbs, and install.js takes the index-service source map. A language built that way never goes stale. Put policy in the mounting app, never in the engine: @leumas/lmx depends only on express and @leumas/grids.
Then a demo in src/scripts/, a case in ops/infra/scripts/check-lmx.mjs, tests in src/test/, and
pnpm docs:lmx # regenerate INTERPRETERS.md from the catalog
pnpm smoke:lmx # every safe mode, every bridge, every gate, + docs drift
node --test src/test/*.test.js
pnpm skills:sync && pnpm check:skills
INTERPRETERS.md is generated. Editing it by hand is how it came to document 7 of 11 modes.