IoT Rules DSL
Strict Synoema subset for LLM-generated rules — runs on Linux/sysfs and bare-MCU WASM
The Rules DSL is not a separate language. It is Synoema with a narrower grammar: same parser, same type checker, same code generator. The narrower grammar gives two guarantees:
- Portability. A rule that parses under the DSL grammar runs unchanged on a Linux IoT board (sysfs GPIO) and on an STM32 / ESP32 / nRF MCU (compiled to WASM, executed by wasm3).
- LLM safety. The corresponding GBNF (
synoema-iot-rules.gbnf) is fed to constrained decoding. The model literally cannot sample a non-conformant token —http_get,spawn,3.14are all unreachable at sampling time.
Rule form — prefix convention
A rule is an ordinary top-level function whose name starts with rule_. No new keyword, no annotation. Tooling enumerates rules by walking top-level bindings and grepping for the prefix.
rule_temp_fan temp =
? temp > 25 -> gpio_write 17 1 : gpio_write 17 0
Allowed constructs
| Category | Allowed |
|---|---|
| Literals | Int, Nat (10n), Bool, String, Char |
| Operators | + - * / %, == != < > <= >=, && ||, ++, |>, >>, -> |
| Conditional | ? cond -> then : else — ternary, chainable |
| Functions | f x = expr, multi-arg, pattern-match heads |
| Lambdas | \x -> expr |
| Lists | [1 2 3], comprehensions [x | x <- xs, x > 0], ranges [1 .. 10] |
| Tuples / Records | (a, b); { k = v, ... } |
| ADTs | type Status = Ok | Err Int |
| Pattern matching | function heads, let-like bindings, case via cond chain |
| GPIO builtins | gpio_read : Int -> Int, gpio_write : Int -> Int -> (), gpio_mode : Int -> String -> () |
| Pure helpers | max, min, arithmetic, comparisons, list HOFs (map, filter, fold) |
| Sleep | sleep : Int -> () — milliseconds |
Forbidden constructs (with reasons)
| Category | Forbidden | Why |
|---|---|---|
| Networking | http_get, http_post, tcp_* | No TCP/IP stack on bare MCU |
| Filesystem | file_read, file_write, fd_popen | No POSIX host on MCU target |
| Environment | env_get, args | Same |
| Concurrency | spawn, pmap, scope, channel_* | wasm3 is single-threaded |
| Floats | 3.14, any Float binding | WASM v2 has no float lowering yet |
| Rationals | (1 / 3) rational literal | Same |
| Bytes / multiline strings | b"...", """...""" | Surface kept small for the LLM's benefit |
| Modules | import, mod, use, trait, impl | Rule files are single-file by design |
Worked example — temperature-driven fan
-- Rule: fan on at >25 °C, off otherwise.
rule_temp_fan temp =
? temp > 25 -> gpio_write 17 1 : gpio_write 17 0
main =
m = gpio_mode 17 "out"
m |> \_ -> rule_temp_fan 28
Verifier lang/tools/constrained/verify_iot_rules_gbnf.sh applies four grep-based checks:
- At least one top-level
rule_*binding - No forbidden identifier appears
- No float literal pattern (
[0-9]+\.[0-9]+) - No rational literal (
(N / M)form)
Naming patterns — tactical hints for the LLM
- Single-threshold:
rule_<sensor>_<actuator>— e.g.rule_temp_fan,rule_pressure_pump - Pattern-match on digital input:
rule_<what>_<alarm>— e.g.rule_water_leak,rule_door_open - Hysteresis (two thresholds + previous state): signature
rule_xxx temp lo hi prev - Moving-average / duty-cycle: precompute
avgseparately; the rule takes a scalar input plus a trigger
Cross-references
- Full grammar:
lang/tools/constrained/synoema.gbnf - Rule subset GBNF:
lang/tools/constrained/synoema-iot-rules.gbnf - Bare-MCU runtime subset spec:
openspec/specs/bare-mcu-subset/spec.md - Linux/sysfs IoT capability spec:
openspec/specs/iot-runtime-linux/spec.md - 5 reference rule files:
lang/examples/iot/rules/ - IoT architecture: /iot — tier model + verticals
- LLM tooling: /llm — MCP, RAG, ReAct