Leia

Embedding Leia

The public Go import path is:

import leia "github.com/never-labs/leia"

The root package is the embedding API. It should stay small: VM construction, execution, module options, sandbox/resource controls, public value conversion, hot reload handles, and stable error types belong here. Compiler internals, runtime tables, and stdlib binding code remain under internal/.

Minimal host:

vm := leia.New(leia.WithLibs(leia.LibSafe))
err := vm.Exec(`print("hello")`)

VM Lifetime

VM is mutable and not goroutine-safe. Use one VM per request, session, or script instance. For concurrent host workloads, create multiple VMs or use Pool.

Compile reusable code once:

prog, err := leia.Compile(`func add(a, b) { return a + b }`)
if err != nil {
	return err
}

vm := leia.New(leia.WithVM())
if err := vm.Run(prog); err != nil {
	return err
}
out, err := vm.Call("add", 2, 3)

Do not run the same Program concurrently.

Sandbox First

Sandboxed hosts should start from SecuritySandbox(), then grant only the modules, capabilities, and budgets the script needs.

vm := leia.New(
	leia.SecuritySandbox(),
	leia.WithLibs(leia.LibSafe|leia.LibFS),
	leia.WithFilesystemRoot("/srv/app/scripts"),
	leia.WithFilesystemRead(true),
	leia.WithFilesystemWrite(false),
	leia.WithMaxSteps(100_000),
	leia.WithMaxNativeCalls(1_000),
)

File, environment, module loading, native call, step, goroutine, channel, and host-result-size budgets are separate controls. See the security reference.

A production embedding usually combines the sandbox, budgets, explicit host bindings, and hot reload in one place:

loader := leia.NewHotLoader(leia.WithHotLoaderVMOptions(
	leia.SecuritySandbox(),
	leia.WithLibs(leia.LibSafe),
	leia.WithGoImports(map[string]any{
		"go:host/safe": leia.Module{
			"label": safeLabel,
		},
	}),
	leia.WithMaxSteps(100_000),
	leia.WithMaxNativeCalls(1_000),
	leia.WithMaxHostResultBytes(1<<20),
))

inst, err := loader.LoadInstance("policy.leia")

The script can import only the allowlisted go:host/safe module. Reloading the HotInstance swaps changed function bodies while preserving compatible script state.

Host Modules

Expose Go capabilities explicitly:

type SafeMath struct{}

func (SafeMath) Double(n int) int { return n * 2 }

mod, err := leia.ModuleFrom(SafeMath{})
if err != nil {
	return err
}

vm := leia.New()
if err := vm.RegisterModule("safe/math", mod); err != nil {
	return err
}

WithGoImports allows require("go:...") only for host-provided entries:

vm := leia.New(leia.WithGoImports(map[string]any{
	"go:example.com/app/safe": SafeBinding{},
}))

Leia does not reflect arbitrary Go packages by import path at runtime.

Module-Aware Hosts

Project hosts can load module options from leia.mod:

opts := leia.ModuleOptionsForScript("scripts/main.leia")
vm := leia.New(opts...)

Use readonly or vendor module mode for reproducible deployment:

vm := leia.New(
	leia.WithModuleMode(leia.ModuleModeVendor),
	leia.WithModuleCache("./vendor"),
)

Hot Reload

Hot reload should preserve state by default where compatible. Source changes that alter incompatible state layout or active concurrency may require explicit host policy.

Use HotInstance for a persistent script instance:

loader := leia.NewHotLoader(leia.WithHotLoaderVMOptions(leia.WithVM()))
inst, err := loader.LoadInstance("logic.leia")
if err != nil {
	return err
}

_, err = inst.Call("tick")
if result, err := inst.ReloadIfChanged(); err == nil && result.Changed {
	_, err = inst.Call("tick")
}

Use HotLoader plus ModuleHandle when each request owns its own VM. See the hot reload reference.

Cancellation

Prefer context-aware host entrypoints in services:

ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()

err := vm.ExecContext(ctx, source)

Script-level context tables are separate values for script APIs such as time.sleep(ctx, seconds) and process.run(ctx, args).