# lmg — the grid-authoring language

Write a Leumas programming grid as text and run it. Safe mode, no bridge required — it works from the CLI, the API and the Playground alike.


Write a Leumas programming grid as text and run it. **Safe** mode, **no bridge required** — it works
from the CLI, the API and the Playground alike.

```lmg
GRID "pricing"

LAYER default
  A1 variable 1200
  A2 variable 0.08
  B1 code $A1 * (1 + $A2)
  C1 conditional $B1 > 1000 ? "high" : "low"
  D1 template "tier {{ $C1 }} at {{ $B1 }}"

RUN B1, C1, D1
GRAPH
```

```
grid "pricing" — 1 layer(s), 5 cell(s)
default.B1 = 1296
default.C1 = high
default.D1 = tier high at 1296
--- dependency order ---
  default.A1
  default.A2
  default.B1  ← default.A1, default.A2
  default.C1  ← default.B1
  default.D1  ← default.C1, default.B1
```

---

## What a grid actually is

**Not a spreadsheet.** A grid is layers of cells, and **a cell is an action**. `A1` is not a number in
a box; it is a `variable` action that produces one. `B1` is a `code` action. There are ~21 cell types.

Cells reference each other with `$refs`, and `@leumas/grids` owns everything hard about that: it
resolves refs concurrently, memoises so asking twice costs one run, checks for cycles, and evaluates
`code` cells in a `node:vm` sandbox with no `process`, `require` or `fetch`.

`lmg` writes no evaluator. It is a syntax for that document, and the document it produces is the same
shape the Studio grid editor persists.

---

## Syntax

| Line | Meaning |
|---|---|
| `GRID "name"` | names the grid (optional) |
| `LAYER <name>` | starts a layer; a name may contain spaces |
| `<REF> <type> <rest>` | a cell |
| `<REF> <type> do` … `end` | a cell whose body spans lines |
| `RUN a, b, c` | execute and print those cells |
| `DUMP` | print every cell computed so far |
| `GRAPH` | print the dependency order |

`#` starts a comment — **except inside a `do … end` body**, which is taken verbatim. A `#` in a code
cell is JavaScript, and eating it would corrupt exactly the cells most likely to have one.

Cell refs are uppercase letters then digits (`A1`, `B12`, `AA3`) — that is the ref grammar
`@leumas/grids` defines, not a choice made here. Cells declared before any `LAYER` go into a layer
called `default`.

### The one parsing rule

After `<REF> <type>`:

- if the rest of the line **starts with `--`**, it is parsed as flags and nothing else;
- otherwise the **entire** rest of the line is the cell's primary field, `--` and all.

So `B1 code $A1 - -2` means what it looks like, and `B1 json --input=$A1 --op=get --path=name` also
means what it looks like. Nothing has to guess.

Flag values are JSON-coerced (`--limit=5` is the number 5, `--keys=["a"]` is an array), and a bare
`--flag` is `true`.

---

## `$refs`

| Form | Resolves to |
|---|---|
| `$A1` | `A1` in the **same layer as the cell writing it** |
| `$default.A1` | `A1` in the layer literally named `default` |
| `$Layer 2.B2` | `B2` in the 2nd layer, 1-based |

`RUN` targets are different: a bare `RUN B1` has no owning layer, so it resolves to **the layer that
declares `B1`**. If two layers declare it, `lmg` refuses and asks you to qualify it rather than
picking one.

---

## Cell types

Every type in `allCellAdapters` is available. The ones with a bare-rest-of-line form:

| Type | Bare form fills | Example |
|---|---|---|
| `variable` / `value` | `value` | `A1 variable 1200` |
| `code` | `code` | `B1 code $A1 * 2` |
| `conditional` | see below | `C1 conditional $B1 > 100 ? "hi" : "lo"` |
| `template` | `template` | `D1 template "tier {{ $C1 }}"` |
| `aggregate` `map` `transform` `switch` `json` `csv` `regex` `validate` `hash` | `input` | `E1 json $D1` |
| `merge` `random` | `mode` | `F1 random float` |
| `datetime` | `op` | `G1 datetime now` |
| `state` | `key` | `H1 state cart` |
| `endpoint` `url` | `url` | *(needs `LMX_ENABLE_NET`)* |
| `media` | `items` | |

Any other type still works — name its fields with flags. A type with no bare form is an error rather
than a guess, because a wrong guess *runs*.

### `conditional` sugar

```lmg
C1 conditional $B1 > 1000 ? "high" : "low"
```

desugars to `--condition="$B1 > 1000" --trueResult=high --falseResult=low`. The explicit flags are
accepted too. `?` and `:` inside quotes are ignored, so
`conditional $A1 = "who? me:" ? "yes" : "no"` splits where you expect.

### `template` slots take `$refs`, not bare names

```lmg
D1 template "tier {{ $C1 }} at {{ $B1 | fixed:2 }}"
```

`{{ C1 }}` without the `$` is not a ref — it renders the literal text `C1`. Helpers after `|` come
from the template adapter: `upper`, `lower`, `json`, `fixed:n`, `default:x`.

---

## [critical] The network gate

Two cell types make outbound requests: **`endpoint`** and **`url`**. Every other LMX mode that can
reach the network (`lmnet`) is admin-gated behind `LMX_ENABLE_NET`, so `lmg` — a *free* mode — cannot
be the open door to the same reach.

Without `LMX_ENABLE_NET=1` those two adapters are **replaced**, not removed, and fail with:

```
"endpoint" cells reach the network — set LMX_ENABLE_NET=1 to allow them.
```

Removed, they would read as "unknown cell type", which sends the author hunting for a typo instead of
at the policy.

The privileged `lmx`, `adapter` and `exec` cell types that `leumas-api` injects into its own grids
domain are also out of reach here: a free script must not reach the ecosystem through a cell when it
cannot reach it through `adapter()`.

---

## One implementation detail worth knowing

`lmg` writes each cell's options into **`specialConfig`**, and a code cell's source into a top-level
**`code`** field — not into `props`.

Every adapter merges `props` over `specialConfig`, so either would execute correctly. But
`buildDependencyGraph` reads only `code` and `specialConfig`. A props-authored grid therefore *runs*
perfectly and reports an **empty dependency graph** — `GRAPH` prints every cell with no edges, and
nothing in the output says it is lying. Writing `specialConfig` also makes an `lmg` grid identical to
one the Studio editor produced.

---

## What it deliberately cannot do

- **Open or save a stored grid.** Inline grids only, so there is no ownership question and no write
  path around `/api/grids` and its PassNode meter.
- **Reach the ecosystem.** No `lmx`/`adapter`/`exec` cells.
- **Reach the network** without the flag.

---

## Running it

```bash
leumas-lmx ./pricing.lmg
```

```bash
curl -s -X POST localhost:3000/api/lmx/run \
  -H 'content-type: application/json' \
  -d '{"mode":"lmg","source":"LAYER default\n  A1 variable 4\n  B1 code $A1 * 3\nRUN B1"}'
```

Bundled examples: `shared/engines/lmx/src/scripts/demo.lmg`, `shared/engines/lmx/src/scripts/pricing-model.lmg`.
Tests: `shared/engines/lmx/src/test/lmg.test.js`. Guard: `pnpm smoke:lmx`.


---
Source: .claude/skills/leumas-lmx/reference/lmg.md
Canonical: https://docs.leumas.tech/p/skills/leumas-lmx/lmg
