A declarative language for LLM-driven state machines, with an agent-first
console to author and run them. A .mkl file (mklang) describes an
agent as a set of states; an LLM is the runtime that executes generative
steps. The document is the program; the mklang console is how
you drive it. The host supplies the interpreter, optional tools, and optional
code-hook gates.
Models generate. Machines decide what happens next. mklang makes probabilistic LLM computation governable through declarative control flow: states produce, gates judge, and the machine routes to the next state, repair, escalation, tool, or failure. The topology is explicit and traceable; the judge's accuracy and cross-provider stability remain empirical questions.
mklang : LangGraph :: a declarative spec : Python code
Two things to look at first: the language — states with prose faces and natural-language gates as transitions — and the console — an agent-first TUI that authors, commissions, and traces machines for you. Everything else (CLI, MCP, scenario tests) is scaffolding around those two.
The two product surfaces — the console and the language:
| Console: agent-first TUI, commissioning, trace | Language: gates, tools, reasoning loop |
|---|---|
![]() |
![]() |
Both recordings run the real surfaces against DeepSeek (the agent demo also hits the live web). See the full WebM recordings, transcripts, and reproducibility notes.
Each state has four faces: structure (what shape?), prompt (what to
think? — the {{…}}-interpolated task), execution (how to act? — sticky
policy, never side effects), and gates (when to exit?). Sticky policy goes in
execution; turn data and {{context}} go in prompt. The one-page version
of the whole language is the cheatsheet; the
face → LLM-channel mapping is Best practices §3.
Real side effects (search, send, calc) are tool: states — host callables,
not prose in execution. See examples/react.mkl and
examples/triage.mkl.
The output of a state is stored in the shared context under its output: key, so
later states read it via {{key}}. Four optional faces unlock richer reasoning:
reason (traced chain-of-thought), accumulate (append to a list), fan-out
(sample: N / over: {{list}}), and call (run another machine) — see
Reasoning architectures.
Gates are the transitions. A state's gates list is its transition table:
each gate is a natural-language condition the LLM judges, plus what happens next.
gates:
- when: the reply resolves the request and is in the required tone
then: ok
to: send
- when: information from the KB is missing
repair: 2 # re-run this state with feedback, up to 2 times
to: gather
- when: the request needs a human
escalate: true
to: human_reviewPolicies: ok (advance), repair(N) (self-correct with feedback), escalate
(route to a handler), fail (abort). A global step budget prevents runaway loops.
mklang console is the agent-first front door and the primary way to use the
language: type what you want, and the console's agent authors or picks a machine,
commissions it, and streams the run state-by-state as a live trace tree —
escalations and tool consent come back to you inline (human-in-the-loop is a
first-class part of the flow, not an afterthought).
pip install mklang # the console ships in the core package since 0.15.0
mklang consoleThe agent itself is a machine
(agent.mkl) — read it, lint it, swap it
with --agent your_brain.mkl (ADR 0015). It has no privileged powers the language
lacks: it commissions the same .mkl machines you write, over the same gates and
tiers. Sessions persist (--continue), agent replies render as Markdown, and the
brain declares host clocks today / now for wall-clock questions. Details:
docs/guides/console.md.
For project or folder analysis, the default brain first inspects the configured
workspace through bounded read-only tools (list_workspace,
search_workspace, read_workspace_file). It excludes hidden/build/vendor
directories, reports truncation, and separates observed facts from inferences;
it still has no shell, git, or generic write access.
- Document-first — readable without the interpreter; prose-first for the common path. Production machines still need developer judgment for tools, hooks, and untrusted inputs (see SPEC threat model); the runtime delimits untrusted context structurally (SPEC §6), it does not judge it for you.
- LLM-as-runtime — non-deterministic by design; gates (prose + optional code hooks + budgets + trace) are the reliability mechanism. Prose-gate accuracy is an empirical claim, not a free lunch.
- Prose, not types —
structureand gate conditions are natural language, judged by the LLM at runtime; optionalhook:gates add host bool checks. - Provider-agnostic — a
.mklnever names a provider or model. States route by capability tier (fast/balanced/reasoning); the runtime maps each tier to a concrete model. Portability of the document is syntactic; whether different providers fire the same gates on the same run is measurable (seescripts/gate_divergence.py). - Spec + conformance — an implementation-neutral conformance suite pins interpreter semantics so a second runtime can match the language's mechanical contract: same machine, same oracle verdicts, same trace. Cases run against a scripted LLM, so conformance says nothing about which gate a model will fire — two conformant runtimes can still diverge in production (what conformance does and does not guarantee).
- Language-agnostic runtime — the spec assumes only "some host with an LLM".
Every modern reasoning/agentic pattern — chain-of-thought, ReAct,
self-consistency, Tree-of-Thought, plan-and-execute, debate, map-reduce,
router-of-experts, speculative cascade — maps onto the core (states + gates +
prose + tiers + the optional faces). The construct-by-construct map is the
cookbook in SPEC.md §10; operating guidance in
docs/guides/patterns.md.
Ten of these ship as ready, general-purpose std_* machines — parameterized
by context, callable from your machines (call: std_refine), runnable by name
(from the CLI or the console's /run):
mklang run std_self_consistency --set task="Estimate the risk of X"See the stdlib catalog (ADR 0012). The patterns that need host
tools/hooks or static call: targets (ReAct, router, exact policy) stay as
authored examples.
The .mkl picks a tier; a host-side config picks the model. This is the
whole of "make it multi-provider":
active: deepseek # anthropic | openai | google | openrouter | xai | mistral | local
providers:
deepseek:
tiers: { fast: deepseek-v4-flash, balanced: deepseek-v4-flash, reasoning: deepseek-v4-flash }
anthropic:
tiers: { fast: claude-haiku-4-5, balanced: claude-sonnet-5, reasoning: claude-opus-4-8 }
local:
base_url: http://localhost:11434/v1
tiers: { fast: qwen3:8b, balanced: qwen3:32b, reasoning: deepseek-r1:70b }The example config defaults to DeepSeek V4 Flash (the path we live-test
against); flip active: and every example runs unchanged. Blocks ship for
Anthropic, OpenAI, Google, DeepSeek, OpenRouter, xAI, Mistral, and local
(Ollama/vLLM); per-tier params (adaptive thinking, reasoning_effort, …) live
under params. Full map, per-provider notes included:
config/runtime.example.yaml.
pipx install 'mklang[mcp]' # console TUI is in the core package; [mcp] adds the MCP server
mklang init --user # scaffold config, .env, and a sample machine
# set DEEPSEEK_API_KEY (or another provider key) in the .env that init reported, then:
mklang consolepip install mklang works too. The full walk-through — including a first run
that needs no API key — is the
Getting started guide. A one-shot
scripts/install.sh and an Arch Linux
PKGBUILD
are also available.
Editor validation for .mkl files works out of the box via the JSON Schema —
point yaml-language-server at
https://raw.githubusercontent.com/gianlucamazza/mklang/main/schema/mklang.schema.json.
The console is the interactive surface; the mklang CLI is the scriptable one —
same interpreter, same machines. Drive a checkout through
uv with no install step:
git clone https://github.com/gianlucamazza/mklang && cd mklang
cp .env.example .env # set DEEPSEEK_API_KEY=… (or another provider key)
uv run mklang check examples/self_consistency.mkl
uv run mklang lint examples/self_consistency.mkl # + static analysis
uv run mklang test examples/triage.mkl --script examples/triage.test.yaml # no key needed
uv run mklang run examples/self_consistency.mkl \
--set question.text="What is the capital of Australia?"mklang test pins the paths you care about against a scripted LLM —
deterministic, no provider or key — before you spend a token on a live run.
Suspensions are first-class: budget exhaustion and escalate gates can
checkpoint (--checkpoint, --hitl; exit code 3) and mklang resume picks the
run back up, human reply included. Every command, flag, and exit code:
CLI reference.
Agent hosts that speak MCP (Claude Code and
other clients) can commission a machine instead of embedding the library
(ADR 0011): the host requests a run and
gets back the result with full provenance (trace + usage).
pip install 'mklang[mcp]'
claude mcp add mklang -- mklang-mcpThe server auto-discovers config and keys through the same chain as the CLI
(ADR 0023) and exposes commissioning tools (run / resume, inline source or
path), discovery (list_machines / describe_machine), and check
(ADR 0011 + 0013). Live engine events stream as mklang.event logging
notifications (ADR 0019); provider keys resolve server-side from the
environment, never over the wire.
The full documentation lives at docs.mklang.dev —
spec, guides, CLI and stdlib reference, ADRs. Runnable machines are in
examples/ (each pattern has one, with its scenario test; the
authoring guide says which to copy).
- Language spec:
.mkl= YAML validated by a JSON Schema; semantics fixed bySPEC.mdand an implementation-neutral conformance suite. - Reference interpreter: Python ≥ 3.11, dependencies
pyyaml,jsonschema,python-dotenv,openai(the OpenAI-compatible adapter serves every non-Anthropic provider),rich, andtextual(the console). - Providers: DeepSeek / OpenAI / Google / OpenRouter / xAI / Mistral / local via one OpenAI-compatible adapter, plus a native Anthropic adapter (extra).
- Surfaces: the
textualconsole TUI, themklangCLI, and an optional stdiomklang-mcpserver (extramklang[mcp]). - Quality:
ruff,mypy(zero suppressions, a growing strict tier),pytest+pytest-cov(coverage gate, offline via MockLLM/scripted LLM), and the conformance suite — on an ubuntu 3.11–3.13 + macOS + Windows matrix. - Packaging:
hatchling; published to PyPI via GitHub OIDC Trusted Publishing; Arch PKGBUILD.
Two version lines — the language spec and the reference interpreter are versioned independently (see SPEC §1 and ADR 0026):
| Line | Version | Meaning |
|---|---|---|
| Language spec | 0.4 | Additive changes allowed; the language surface is frozen pending evidence (ADR 0028) |
| Reference package | 1.3.1 | Stable interpreter, typed, zero mypy suppressions, 90%+ coverage, CI-gated |
A .mkl file declares its spec version via mklang: "0.4". The package version
is what you pip install; the spec version is what you write in your machine.
Core complete: states + gates + prose, tiers,
reason / accumulate / fan-out / call / tool / parse: list / code-hook
gates; multi-provider interpreter with entry-point plugins (tools, hooks,
providers, machines); resumable checkpoints + HITL; mklang check / lint
(--llm optional) / test / doctor; conformance suite;
machine stdlib (std_*); MCP host; console TUI (bundled by default);
structured web search (offline stub by default); host tool stub architecture for
search / search_kb / send_reply (ADR 0020); host clock conventions
context.today / context.now; sectioned produce system prompts from
structure+execution; output anti-cutoff + context budgets (ADR 0016–0019);
untrusted-context delimiting — provenance taint + <data-NONCE> fences in
produce and judge prompts (SPEC §6, ADR 0025); total gate transitions — the
fused judge may answer none of the above instead of being forced to name a
condition, and lint requires a catch-all (SPEC §5 Totality);
control-flow taint — a transition chosen by a judge reading external data is
marked, and reaching an effectful tool: under it is recorded or refused
(--untrusted-flow halt, SPEC §6 / ADR 0030);
best practices. Gate judging follows the state tier
by default.
- Live: DeepSeek (default) and OpenAI green through the 1.0.x release
matrices, including the blocking cross-provider gate-agreement check at
1.0 — the release gate runs the single
gate_divergencemachine; the four-machine suite measured 1.0 agreement per machine (DeepSeek + OpenAI ×3). Anthropic unit-tested; live e2e still blocked (key/credits). - Release policy: DeepSeek + OpenAI smoke and three-run gate agreement are
blocking; other configured providers are reported without blocking. PyPI
publication uses GitHub OIDC Trusted Publishing from the release workflow.
Arch: AUR
mklangtracks the PyPI sdist pin. - Spec posture: additive-only changes since 0.3; the language surface is
frozen pending evidence (ADR 0028).
Authoring-loop
blind_spot = 0.0167(notest_machineyet). - Evidence status: gate-divergence has a seven-machine DeepSeek/OpenAI boundary-corpus run, but results vary by date and Anthropic remains unmeasured; repair-convergence has one small DeepSeek run with negative observed lift. These are research observations, not reliability guarantees. The next milestone is the reproducible Evidence Release: provider matrix, raw JSONL, metrics, limits, and the five-reader comprehension test.
- Open / later: Anthropic live when the account has credit; five-reader
distribution test;
on_truncate=continuestitching; language-level context zones (ROADMAP). - Roadmap and full release notes:
ROADMAP.md,CHANGELOG.md.
Three sentences, and only these, describe the family (one name, two products):
- mklang is an open language for LLM-driven state machines (judgement).
- mklang platform is the durable host: orchestrator, credentials, signed deploy, human queue.
- A pilot is delivery-led: one real workflow cut over into production here — not self-serve.
This repository is (1). The language name stays usable by anyone under Apache-2.0; the commercial designation is mklang platform, not bare “mklang”. The other half — triggers, retries, credentials, who may deploy, and what happens when the process dies between an effect and the record of it — is deliberately not here. The platform is a separate commercial product (Phase 1: design partners, onboarding is a person). Its licence is stated on mklang.dev; the language stays Apache-2.0.
Apache-2.0. Contributions welcome — see
CONTRIBUTING.md.

