LLMOL — LLM-Optimized Language

A programming language
designed for LLMs.

JSONL syntax, deterministic C# runtime, structured contracts on every method, uniform Result envelopes on every return, MCP-native, embeddable. The language an AI agent can write, run, reason about, and extend without ever guessing.

See LLMOL code → CLI & MCP
13
Line Types
10
Built-in Providers
329
Tests Green
.NET 9
C# Runtime

Why LLMOL exists

Optimized for the LLM, not the human.

Every property of LLMOL was decided by asking: "What does an LLM need to write this correctly the first time, and reason about it later without re-reading the code?" Three answers shaped the language.

PILLAR 01

Required structured contracts

Every def and every provider method MUST declare a structured @hint with intent, inputs, output, edge cases, tags. Parse-time error if missing. Documentation is not optional in LLMOL because optional documentation is, in practice, absent documentation.

PILLAR 02

Uniform Result envelope

Every method returns a tagged envelope with IsSuccess, Value, and Error {Kind, Message, Data}. There is no "throws or returns?" ambiguity. An LLM reading any method's signature knows exactly how to handle success and failure.

PILLAR 03

Strict failure handling

A bare bind:r requires $r.IsSuccess or $r.Error to be referenced before it goes out of scope. The deliberate opt-out is unwrap:true. Forgetting failure is a parse error, not a runtime bug.

04

Deterministic runtime

A .llmol program runs end-to-end with no LLM in the loop. LLMs author programs; the C# runtime executes them. Calling an LLM at runtime is just another provider call, no more privileged than HTTP.

05

JSONL surface, every line standalone

One JSON object per line. Comments are //-prefixed. An LLM emitter can stream programs incrementally and have each line validated as it arrives — no whole-program parse needed for feedback.

06

MCP-native

llmol mcp exposes the entire language surface as an MCP server over stdio. Any MCP-aware agent can parse, describe, validate, and run LLMOL programs without invoking the CLI directly. Safety-gated execution.

Architecture

Four subsystems with sharp boundaries.

LLMOL's runtime is a single C# library, LLMOL.Core, structured into four cleanly separated subsystems. The CLI and SDK are thin layers over Core.

📥

Parser / Loader

Reads JSONL, ignores blanks and // comments, produces a strongly-typed Program AST. Lints unused result bindings and missing @hints at parse time. Pure — no execution side effects.

⚙️

Interpreter

Iterative state machine — no recursion through C# methods for control flow. The frame stack is serializable, enabling checkpoint/resume mid-program. Walks the AST top-to-bottom, dispatches every call through the Provider Host.

🔌

Provider Host

Registry of ILLMOLProvider instances by namespace. Resolves dotted call targets to (provider, method) pairs. Wraps every dispatch in try/catch so .NET exceptions never leak into the interpreter — they become Fail envelopes.

🛡️

Attribute Engine

Validates the Tier-1 vocabulary at parse time. Enforces @type, @shape, @pre, @post, @invariant, @throws at runtime. Statically verifies @pure, @deterministic, @allows, @forbids by walking call graphs.

Language reference

13 line types. Nothing more.

The grammar is fixed and small. New capabilities are added by registering providers, not by inventing new line types. An LLM that has learned 13 shapes has learned the entire language.

opPurposeNotes
use Declare a provider dependencyValidated at load; use http fails fast if the provider isn't registered.
let Bind a literal value to a nameAdd "mut": true for a mutable cell.
call Invoke a provider methodDefault: bind envelope. "unwrap": true: unwrap or raise.
if Conditional branchcond is an expression; then/else are body arrays.
while Loop until cond is falseOptional @invariant checked each iteration.
for Iterate over an arrayLoop variable is immutable per iteration.
match Dispatch on a valueObject of {caseValue: body}; _ is the default.
try Catch a raised errorcatch binds the failure envelope to a name.
raise Produce a failureBecomes a Fail envelope at the def boundary.
parallelRun branches concurrentlyCross-branch binds must be unique (parse-time check).
def Define a procedureRegisters as local.<name>. MUST have @hint.
return Exit a def with a valueFalling off the end returns null.
assert Runtime checkRaises AssertionError if the cond is falsy.

Plus a tiny expression sublanguage for cond & on: literals, $ref chains, + - * / % == != < <= > >= && || ! (). Anything more complex is a provider call.

Worked examples

The language, in code.

Every LLMOL program is JSONL — one JSON object per line. The examples below show the shapes you'll write most often. Hover to read the captions.

01 · Hello world

.llmol
// Print a greeting via the core provider. {"op":"use","provider":"core"} {"op":"call","target":"core.print","args":{"text":"hello, llmol"}}
The first line declares the provider dependency. The second invokes core.print. No bind: means we don't need to inspect the result — pure side-effect calls are exempt from the unused-bind lint.

02 · Result envelope — explicit failure handling

.llmol
// Default: every call binds the envelope, caller MUST inspect. {"op":"use","provider":"http"} {"op":"call","target":"http.get","args":{"url":"https://api.example.com/users/42"},"bind":"r"} {"op":"if","cond":"$r.IsSuccess","then":[ {"op":"let","bind":"body","value":"$r.Value.body"} ],"else":[ {"op":"call","target":"core.log","args":{"level":"error","msg":"$r.Error.Message"}} ]}
Strict lint: a non-unwrap:true call's bind must be inspected via $r.IsSuccess or $r.Error in the same scope. Bare $r doesn't count. $r.Value alone doesn't count — that's "I'm assuming success," which is the precise pattern the lint prevents.

03 · unwrap:true — delegating failure to try/catch

.llmol
// For arithmetic chains, unwrap:true keeps the code terse. // Failures bubble to the enclosing try/catch. {"op":"use","provider":"core"} {"op":"try","body":[ {"op":"call","target":"core.add","args":{"a":1,"b":2},"bind":"sum","unwrap":true}, {"op":"call","target":"core.mul","args":{"a":"$sum","b":3},"bind":"r","unwrap":true}, {"op":"call","target":"core.print","args":{"text":"$r"}} ],"catch":{"bind":"err","handler":[ {"op":"call","target":"core.log","args":{"level":"error","msg":"$err.Message"}} ]}}
unwrap:true is a deliberate token — the LLM author has explicitly declared "I'm not handling failure at this call site; my outer block handles it." Two visually distinct call patterns result, both auditable.

04 · A procedure with full contract

.llmol
// Every def MUST declare @hint with at least intent. // Optional contracts: @type, @pre, @post, @throws, @pure, @example. {"op":"def","name":"local.score_user","params":[ {"name":"user","attrs":{"@type":"User","@assumes":"user.email is non-empty"}} ],"returns":{"attrs":{"@type":"float"}},"attrs":{ "@hint":{ "intent":"Compute a heuristic conversion score from user history.", "inputs":[{"name":"user","note":"must have non-empty email"}], "output":"a number in 0..1; higher = more likely to convert", "edge_cases":["missing email raises ValidationError"], "tags":["users","scoring"] }, "@pure":true, "@allows":["core","flow"], "@pre":"$user.email != \"\"", "@post":"$return >= 0", "@throws":["ValidationError"] },"body":[ {"op":"return","value":0.5} ]}
Static verification: @pure means the body may not call any non-pure method (checked at load by walking the call graph). @allows restricts which provider namespaces the body may invoke. @pre/@post/return @type are runtime-enforced on every call.

05 · Concurrent fetches with try/catch

.llmol
// Parallel branches run concurrently. Cross-branch binds must be unique // (parse-time check). On any branch failure, sibling branches are cancelled. {"op":"use","provider":"http"} {"op":"try","body":[ {"op":"parallel","branches":[ [{"op":"call","target":"http.get","args":{"url":"https://a.example/x"},"bind":"resp_a","unwrap":true}], [{"op":"call","target":"http.get","args":{"url":"https://b.example/y"},"bind":"resp_b","unwrap":true}] ]}, {"op":"call","target":"core.print","args":{"text":"both succeeded"}} ],"catch":{"bind":"err","handler":[ {"op":"call","target":"core.log","args":{"level":"warn","msg":"$err.Message"}} ]}}
The interpreter uses Task.WhenAll over per-branch sub-runs. Cancellation tokens are linked: any branch failure cancels the others. Useful for fan-out where you know the branches at write-time.

06 · Long-running task: spawn now, await later

.llmol
// task.spawn returns a token immediately. Do other work. Await later. {"op":"use","provider":"task"} {"op":"call","target":"task.spawn","args":{"target":"http.get","args":{"url":"$u"}},"bind":"tok","unwrap":true} {"op":"call","target":"core.print","args":{"text":"task running, doing other work..."}} // ...other lines... {"op":"call","target":"task.await","args":{"token":"$tok"},"bind":"r"} {"op":"if","cond":"$r.IsSuccess","then":[ {"op":"call","target":"core.print","args":{"text":"$r.Value.body"}} ],"else":[ {"op":"call","target":"core.log","args":{"level":"error","msg":"$r.Error.Kind"}} ]}
Tokens are opaque UUID strings — no new value type was added to the language. task.await passes the spawned target's envelope through directly: $r.Error.Kind is either a task-system kind (UnknownToken, Timeout) or whatever the spawned method raised.

07 · Declarative event handlers

.llmol
// Subscribe a local def as a handler. emit invokes it synchronously. {"op":"use","provider":"events"} {"op":"def","name":"local.on_progress","params":[{"name":"data"}],"returns":{},"attrs":{ "@hint":{"intent":"Print step progress to the console."} },"body":[ {"op":"call","target":"core.print","args":{"text":"$data.step"}} ]} {"op":"call","target":"events.subscribe","args":{"topic":"progress","handler":"local.on_progress"},"bind":"sub","unwrap":true} {"op":"call","target":"events.emit","args":{"topic":"progress","data":{"step":1,"of":3}}} {"op":"call","target":"events.emit","args":{"topic":"progress","data":{"step":2,"of":3}}} {"op":"call","target":"events.unsubscribe","args":{"topic":"progress","handler":"local.on_progress"},"bind":"un","unwrap":true}
Handlers run synchronously in registration order. A handler's failure is logged but does not abort the emit — events are robust by design. Host code can also subscribe C#-side via the SDK's SubscribeEvents.

08 · Checkpoint and resume

.llmol
// Save state mid-program. Resume later via: // llmol run --from-checkpoint state.json {"op":"use","provider":"core"} {"op":"call","target":"http.get","args":{"url":"https://expensive/api"},"bind":"r"} {"op":"if","cond":"$r.IsSuccess","then":[ {"op":"let","bind":"data","value":"$r.Value.body"}, {"op":"call","target":"core.checkpoint","args":{"path":"state.json"}} ],"else":[ {"op":"call","target":"core.log","args":{"level":"error","msg":"$r.Error.Message"}} ]} // ...resume from here next time, no need to re-fetch the expensive API call
The interpreter is an iterative state machine, so its frame stack is serializable. SHA-256 of the source file is recorded in the checkpoint — resumes against a modified program refuse with ProgramDrift unless --force is passed.

Built-in providers

10 providers, every method documented.

Every provider method declares a structured @hint with intent, inputs, output, edge cases, and tags. llmol describe emits these as JSON; llmol help providers renders them readable.

NamespacePurposeSelected methods
core Print, log, math, string, JSON, time, checkpointadd, mul, concat, json_parse, checkpoint
local Procedure dispatch — every def registers here(method names come from defs)
flow Iteration helpersrange, map, filter, reduce, sleep
task Futures by token — spawn, await, gather, racespawn, await, is_done, cancel, gather, race
events Fire-and-forget signals + in-program handlersemit, subscribe, unsubscribe
http HTTP client with mockable transportget, post, put, delete
llm HTTP-based LLM client — opt-in via LLMOL_LLM_ENDPOINTprompt
csharp Invoke .NET assemblies via AssemblyLoadContextinvoke
python Embedded Python via Pythonnet (when available)eval, exec, call
(custom)Implement ILLMOLProvider — plug into the hostany methods you declare

CLI & MCP

JSON-everywhere. MCP-native.

The primary CLI consumer is an LLM. Every byte of CLI output — including --help — is structured JSON. Errors are JSON to stderr with stable error codes. Exit codes are part of the contract.

llmol — CLI commands

cli
llmol parse <file> llmol describe <file> llmol run <file> [--trace <path>] [--from-checkpoint <state>] llmol replay <trace> llmol checkpoint-describe <state.json> llmol mcp // start MCP server over stdio llmol help [topic] // topics: lines, attributes, // result-envelope, providers, // expressions, references, // extending, samples, commands
Exit codes: 0 ok, 1 help-shown-without-explicit-request, 2 usage error, 3 parse error, 4 IO error, 5 runtime error. Branch on these without parsing the JSON when only success/failure matters.

MCP client config

json
{ "mcpServers": { "llmol": { "command": "llmol", "args": ["mcp"], "env": { "LLMOL_MCP_ALLOW_RUN": "1" } } } }
Drops into Claude Desktop, Cursor, or any MCP-aware client. Safety: llmol_run is disabled by default — set LLMOL_MCP_ALLOW_RUN=1 to enable execution. Pure analysis tools (parse, describe, validate, help, list_providers, get_method_contract, list_samples, read_sample, parse_line) are always allowed.
🛠️

10 MCP tools

parse, describe, validate, run, list_providers, get_method_contract, help, list_samples, read_sample, parse_line. Tool inputs are JSON-Schema typed.

📚

22+ MCP resources

Every help topic, every sample file, the provider catalog, and the design spec are addressable as llmol:// URIs.

💡

1 MCP prompt

write_llmol_program(task) bundles the language reference + provider catalog + sample patterns into a system message ready for "write LLMOL for X."

🔒

Safety boundary

Hand-rolled JSON-RPC 2.0 over newline-delimited stdio. No SDK preview dependency. Logs to stderr. llmol_run is opt-in via env var.

Embedding

Drop LLMOL into any C# application.

The LLMOL.Sdk NuGet package provides a fluent LlmolHost facade that pre-wires the standard provider set, exposes events to host code, and accepts custom providers.

Embedding LLMOL in C#

csharp
using LLMOL.Sdk; using var host = new LlmolHost(new LlmolHostOptions { LlmEndpoint = "https://...", RegisterCSharp = true, }); host.SubscribeEvents((topic, data, ct) => { Console.WriteLine($"event {topic}: {data}"); return Task.CompletedTask; }); host.RegisterProvider(new MyCustomProvider()); var result = await host.RunFileAsync("program.llmol"); // or: var validation = host.Validate(source); var report = host.Describe(source);
LlmolHost wraps ProviderHost + Interpreter. Per-provider opt-in/out via LlmolHostOptions. CSharp and Python are off by default for safety. The Validate path returns a structured ValidationResult — never throws on parse errors.

Implementing a custom provider

csharp
using LLMOL.Core.Attributes; using LLMOL.Core.Providers; using LLMOL.Core.Runtime; using LLMOL.Core.Values; public sealed class MyProvider : ILLMOLProvider { public string Namespace => "my"; public IReadOnlyDictionary<string, MethodSpec> Methods { get; } = new Dictionary<string, MethodSpec> { ["greet"] = new MethodSpec( Name: "greet", Params: new[] { new MethodParamSpec("name", null, AttributeBag.Empty, false, null) }, Returns: null, Attrs: AttributeBag.Empty, Hint: new LLMHint( Intent: "Returns a greeting for the given name.", Inputs: new[] { new HintInput("name", "the recipient") }, Output: "a greeting string", Tags: new[] { "text" })), }; public Task<LLMOLResult> InvokeAsync(string method, LLMOLArgs args, CallContext ctx) { if (method == "greet") { var name = args.GetRequired<LLMOLValue.String>("name").Value; return Task.FromResult(LLMOLResult.Ok(LLMOLValue.Of($"hello, {name}"))); } return Task.FromResult(LLMOLResult.Fail("UnknownMethod", $"my.{method}")); } }
Every MethodSpec MUST have a non-null LLMHint with at minimum a non-empty Intent. InvokeAsync never throws for expected failures — return LLMOLResult.Fail(...). Genuine .NET exceptions are caught at the dispatch boundary and wrapped as Fail("InternalError", ...).

Build with LLMOL.
Optimized for the AI that writes it.

Available as a .NET 9 library, a CLI binary, an MCP server, and an embeddable SDK.

Get in touch → Re-read the examples