Building & Embedding an LMCode Host
Any CLI tool or application in C++17, Rust, Go,
Java 21, C#/.NET 8, JavaScript (ESM), or
Python 3 can become an _lmcode_ host by embedding its runtime SDK, registering
its domain types/functions, and honoring three CLI flags.
1. The Three Mandatory Host CLI Flags
| Flag | Required Behavior |
|---|---|
HOST --lmcode-schema --json |
Print the exact raw bytes of the host's dsl.schema: 2 JSON document to stdout and exit 0. The compiler hashes these exact bytes into the DSLB v6 header. |
HOST --lmcode-signatures |
Print the sorted canonical signatures registered in the host's runtime Registry (one per line: context ..., fn ..., method ...) and exit 0. |
HOST --lmcode FILE.dslb |
Load, verify, and execute the compiled DSLB v6 bytecode file (may be repeated for multi-slot builders) and write one terminal LMCODE_RETURN envelope to stdout. |
2. Registering Types & Running Bytecode Across All 7 Runtimes
#include "dslvm.hpp"
#include "stdlib/std.hpp"
struct Money { int64_t cents; };
int main(int argc, char** argv) {
dslvm::Registry reg;
dslvm::stdlib::register_all(reg); // registers the 206-sig lmcode_std_v3 catalog
reg.fn("money", {"int"}, "Money", [](dslvm::Args& a) {
return dslvm::Value::obj("Money", std::make_shared<Money>(Money{a.i(0)}));
});
reg.method("Money", "cents", {}, "int", [](dslvm::Args& a) {
return dslvm::Value::from_int(a.self<Money>()->cents);
});
reg.context_type("shop", "Shop");
auto prog = dslvm::Program::load(bytecode_bytes, raw_schema_bytes, reg);
dslvm::Context ctx;
ctx.bind("shop", dslvm::Value::obj("Shop", std::make_shared<Shop>()));
prog.run(ctx);
}
use dslvm::{Context, Program, Registry, Value};
fn main() -> Result<(), dslvm::Error> {
let mut reg = Registry::with_std(); // includes lmcode_std_v3
reg.register_fn("money", &["int"], "Money", |args| {
Ok(Value::obj("Money", Money { cents: args.int(0) }))
});
reg.register_method("Money", "cents", &[], "int", |recv, _args| {
let m = recv.downcast_ref::<Money>().unwrap();
Ok(Value::Int(m.cents))
});
let prog = Program::load(&bytecode, SCHEMA_BYTES, ®)?;
let mut ctx = Context::new();
prog.run(&mut ctx)?;
Ok(())
}
package main
import lmcode "github.com/lmctl-ai/lmcode-go"
func main() {
reg := lmcode.NewRegistryWithStd()
reg.Func("money", []string{"int"}, "Money", func(a *lmcode.Args) (lmcode.Value, error) {
return lmcode.Obj("Money", &Money{Cents: a.Int(0)}), nil
})
prog, err := lmcode.Load(bytecode, schemaBytes, reg)
if err != nil { panic(err) }
ctx := lmcode.NewContext()
_ = prog.Run(ctx)
}
import ai.lmctl.lmcode.*;
Registry reg = Registry.withStd();
reg.fn("money", List.of("int"), "Money", args ->
Value.obj("Money", new Money(args.getInt(0))));
reg.method("Money", "cents", List.of(), "int", args ->
Value.ofInt(args.self(Money.class).cents()));
Program prog = Program.load(bytecodeBytes, schemaBytes, reg);
Context ctx = new Context();
prog.run(ctx);
using Lmcode.DslVm;
var reg = Registry.WithStd();
reg.Fn("money", new[] { "int" }, "Money", args =>
DslValue.Obj("Money", new Money(args.Int(0))));
var prog = DslProgram.Load(bytecodeBytes, schemaBytes, reg);
var ctx = new DslContext();
prog.Run(ctx);
import { Registry, Program, Context, InvocationResults } from "lmcode-js";
const reg = Registry.withStd();
reg.fn("money", ["int"], "Money", (args) =>
({ __type: "Money", cents: args.int(0) })); // BigInt int64 at boundary
const prog = Program.load(bytecodeBuffer, schemaBytes, reg);
const ctx = new Context();
prog.run(ctx);
from lmcode import Registry, Program, Context, Value
reg = Registry.with_std()
reg.fn("money", ["int"], "Money", lambda a: Value.obj("Money", Money(a.int(0))))
prog = Program.load(bytecode_bytes, schema_bytes, reg)
ctx = Context()
prog.run(ctx)
3. Private Invocation Metadata & Terminal LMCODE_RETURN Envelope
When run_lmcode or run_lmcode(b) spawns a host process, it passes the path to a private
JSON manifest in LMCODE_INVOCATION_FILE:
{"version":1,"mode":"single","dry":false,"parts":[{"index":0,"file":"/tmp/lmcode-slot-0.dslb"}]}
After running all submitted slots (and any domain validation), the host writes exactly one terminal frame at the end of stdout:
# 1. Single-program success (exit 0)
LMCODE_RETURN_BEGIN
{"ok":true,"result":{"zone_count":1,"domain":"lmscript.io"}}
LMCODE_RETURN_END
# 2. Multi-slot builder success (exit 0, includes "parts": N)
LMCODE_RETURN_BEGIN
{"ok":true,"result":[{"step":"bucket"},{"step":"cloudfront"}],"parts":2}
LMCODE_RETURN_END
# 3. Controlled failure (exit 2=cli, 3=load/run, 4=app)
LMCODE_RETURN_BEGIN
{"ok":false,"phase":"run","code":"RUN_HOST","message":"bucket name cannot be empty","line":12,"column":3}
LMCODE_RETURN_END
4. Discovering & Invoking @Rule Entries from the Host
Compiled @annotation fn name(...) implements Interface rules live in Section 6 of the DSLB v6 file.
After running the body region (`prog.run(ctx)`), the host can inspect prog.entries() and call any entry
with typed arguments via prog.call(entry_name, ctx, args).
5. Conformance Suite & Signature Drift Verification
# Diff the compiler's canonical signature list against the host's runtime registry:
diff -u <(lmscript --lmcode-signatures host.schema.json) \
<(./my-host --lmcode-signatures)