Docs
/
the any-tool language

lma

Call anything this install has. Safe mode, needs the host's tools bridge — so POST /api/lmx/run or Studio's LMX Playground, not the CLI.

lma — the any-tool language

Call anything this install has. Safe mode, needs the host's tools bridge — so POST /api/lmx/run or Studio's LMX Playground, not the CLI.

PLANES
DISCOVER "base64"
DESCRIBE a-text.Base64Encode

USE a-text.Base64Encode { "text": "{{input}}" } -> encoded
PRINT "encoded: {{encoded}}"
TRACE
RETURN encoded

The idea: there is no verb table

Every other LMX language has a vocabulary someone typed into a file. lmq knows FROM. lmai knows ASK. Add an adapter to Leumas and neither gains anything.

lma has no verbs of its own. Its vocabulary is read from live registries on every single call:

PlanePrefixWhere the verbs come from
Tools(none — default)toolRegistry() — adapter tools, stored functioncalls, user integrations, grid cells
Actionsaction:actions.listManifests() / actions.get(id).run(props)
MCPmcp:the MCP server map + executeTool
Capabilitiescap:CAPABILITY_LIST + the connectors runCall pipeline

Register an adapter, write a functioncall in Studio, connect an integration, publish an app — and every existing lma script can call it. Nothing is rebuilt, no language code changes, no redeploy. The planes are read as getters, never memoised, precisely so a script cannot be pinned to the vocabulary that existed at boot.

cap: is the interesting one. A capability names an outcome, not a vendor: cap:email.send resolves at call time to whichever connected provider declares it fulfills that capability, using whichever of this caller's credentials apply. Connect a new provider and scripts that already asked for email.send start working, with nothing edited.


Statements

# starts a comment.

StatementDoes
PLANESwhat planes this install has, and how many verbs each holds
DISCOVER "query"search every plane; empty query lists everything (capped at 40)
DESCRIBE <ref>one verb's description and declared parameters
USE <ref> { …args } -> varcall it; { … } and -> var are both optional
EACH <item> IN <list> USE <ref> { … } -> varfan the call over a list, 4 at a time, order preserved
SET <name> = <value>a variable; JSON-coerced
WHEN <cond> THEN <statement>one guarded statement
PRINT <path> · PRINT "… {{slot}} …"
TRACEevery call made so far — ref, ok, ms, metered
USING <ref>, <plane>, …narrow this script's own scope
RETURN <path>the script's value; stops the script

References

a-text.Base64Encode          the default (tool) plane
action:notify                an automation action
mcp:github.create_issue      <server>.<tool>
cap:email.send               a vendor-neutral outcome

{{slots}}

Arguments are a JSON literal, filled from inputs, SET variables and prior results before being parsed. That ordering is what lets a slot carry a whole structure:

USE a.b { "rows": {{results}} }     →   { "rows": [{…}, {…}] }

An unresolved slot is an error, and a literal that stops being valid JSON after filling is an error — never a silently-empty {}, which would be a tool call that runs and does the wrong thing.


What bounds a script

GateEffect
Host scopeThe mounting app may hand each caller an allow-list. Out-of-scope verbs are invisible to DISCOVER and DESCRIBE, not merely unusable.
USINGThe script's own narrowing. It can only ever narrow further, never widen.
Call budget50 USE calls per script — a loop cannot drain a wallet.
MeteringEach call charges passnode.charge({ target: 'tool:<ref>' }).
Timeout30s default.

[critical] On metering

POST /api/adapters/:system/:fn is priced by passnode.guard. That guard lives in the HTTP routerregistry.run() itself is not metered, so every non-HTTP caller of an adapter tool has always been free, the in-script adapter() builtin included.

That is a small gap while scripts call a tool occasionally and a large one for a language whose entire purpose is calling tools. lma therefore charges the same tool: target string the HTTP route charges, so a script and an HTTP client are priced alike. A refused gate stops the call before the tool runs.

(The older adapter() builtin remains unmetered. Changing it would alter the behaviour of existing scripts, so it is left alone and flagged here instead.)


TRACE, and why it exists

A language whose verbs resolve at run time has to be able to say what it actually called:

--- trace ---
  ✓ a-text.Base64Encode 3ms · metered
  ✓ cap:email.send 210ms

For a cap: ref this is the only record of which provider served the call — the script named an outcome, and the ecosystem chose. Without it a run is not reproducible.


What it deliberately cannot do

  • Validate your arguments. DESCRIBE surfaces the declared schema for a human; nothing enforces

it. A registry's schema describes intent, and rejecting a call against a stale or partial one turns a working tool into a broken language feature.

  • Loop, or define functions. EACH fans one call over one list. Anything more wants lmx.
  • Register anything. Every plane is read-only from here.
  • Run from the CLI. No tools bridge there.

Running it

curl -s -X POST localhost:3000/api/lmx/run \
  -H 'content-type: application/json' \
  -d '{"mode":"lma","source":"PLANES\nDISCOVER \"base64\""}'

Bundled examples: shared/engines/lmx/src/scripts/demo.lma, shared/engines/lmx/src/scripts/summarize-rows.lma. Tests: shared/engines/lmx/src/test/lma.test.js. Guard: pnpm smoke:lmx.

Source .claude/skills/leumas-lmx/reference/lma.md (no-git)markdownjson
Generated from the Leumas repository. Every page cites the file it came from.leumas.techllms.txt