LLMOL — LLM-Optimized Language
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.
Why LLMOL exists
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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
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.
| op | Purpose | Notes |
|---|---|---|
| use | Declare a provider dependency | Validated at load; use http fails fast if the provider isn't registered. |
| let | Bind a literal value to a name | Add "mut": true for a mutable cell. |
| call | Invoke a provider method | Default: bind envelope. "unwrap": true: unwrap or raise. |
| if | Conditional branch | cond is an expression; then/else are body arrays. |
| while | Loop until cond is false | Optional @invariant checked each iteration. |
| for | Iterate over an array | Loop variable is immutable per iteration. |
| match | Dispatch on a value | Object of {caseValue: body}; _ is the default. |
| try | Catch a raised error | catch binds the failure envelope to a name. |
| raise | Produce a failure | Becomes a Fail envelope at the def boundary. |
| parallel | Run branches concurrently | Cross-branch binds must be unique (parse-time check). |
| def | Define a procedure | Registers as local.<name>. MUST have @hint. |
| return | Exit a def with a value | Falling off the end returns null. |
| assert | Runtime check | Raises 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
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.
core.print. No bind: means we don't need to inspect the result — pure side-effect calls are exempt from the unused-bind lint.
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.
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.
@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.
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.
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.
SubscribeEvents.
ProgramDrift unless --force is passed.
Built-in providers
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.
| Namespace | Purpose | Selected methods |
|---|---|---|
| core | Print, log, math, string, JSON, time, checkpoint | add, mul, concat, json_parse, checkpoint |
| local | Procedure dispatch — every def registers here | (method names come from defs) |
| flow | Iteration helpers | range, map, filter, reduce, sleep |
| task | Futures by token — spawn, await, gather, race | spawn, await, is_done, cancel, gather, race |
| events | Fire-and-forget signals + in-program handlers | emit, subscribe, unsubscribe |
| http | HTTP client with mockable transport | get, post, put, delete |
| llm | HTTP-based LLM client — opt-in via LLMOL_LLM_ENDPOINT | prompt |
| csharp | Invoke .NET assemblies via AssemblyLoadContext | invoke |
| python | Embedded Python via Pythonnet (when available) | eval, exec, call |
| (custom) | Implement ILLMOLProvider — plug into the host | any methods you declare |
CLI & MCP
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_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.
parse, describe, validate, run, list_providers, get_method_contract, help, list_samples, read_sample, parse_line. Tool inputs are JSON-Schema typed.
Every help topic, every sample file, the provider catalog, and the design spec are addressable as llmol:// URIs.
write_llmol_program(task) bundles the language reference + provider catalog + sample patterns into a system message ready for "write LLMOL for X."
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
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.
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.
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", ...).
Available as a .NET 9 library, a CLI binary, an MCP server, and an embeddable SDK.