A declarative language for LLM-driven state machines (reference interpreter).
Project description
mklang
A declarative language for LLM-driven state machines. A .mk file (mk =
machine) describes an agent as a set of states; an LLM is the runtime that
executes generative steps. The document is the program; the host supplies the
interpreter, optional tools, and optional code-hook gates.
mklang : LangGraph :: a declarative spec : Python code
See it in action
| CLI: check, lint, and run | Console: self-consistency fan-out | Agent: free-language chained flows |
|---|---|---|
| HITL: suspend and resume | Search: live web tool | Tests: no API key |
|---|---|---|
All recordings run the real surfaces against DeepSeek — the agent and search demos also hit the live web — except the test demo, which is fully deterministic and needs no API key. See the full WebM recordings, transcripts, and reproducibility notes.
The idea
Each state has four faces:
| Face | Answers | Example | Ref. interpreter |
|---|---|---|---|
structure |
what shape? | "The output is an email reply, max 150 words" | → system |
execution |
how to act? | "Do not invent policies not in the KB facts" | → system |
prompt |
what to think? | "Write a reply to {{ticket.body}}…" | → user (+ {{…}}) |
gates |
when to exit? | see below | separate judge call |
Sticky policy goes in execution; turn data and {{context}} go in prompt.
There is no system: keyword — see Best practices §3.
Real side effects (search, send, calc) are tool: states — host callables,
not prose in execution. See examples/react.mk and
examples/triage.mk.
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_review
Policies: ok (advance), repair(N) (self-correct with feedback), escalate
(route to a handler), fail (abort). A global step budget prevents runaway loops.
Design commitments
- 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).
- 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
.mknever 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 contract.
- Language-agnostic runtime — the spec assumes only "some host with an LLM".
Files
SPEC.md— the full language specification.schema/mklang.schema.json— JSON Schema that validates the structure of a.mkfile (add# yaml-language-server: $schema=../schema/mklang.schema.jsonat the top of a.mkfor editor validation).config/runtime.example.yaml— host-side runtime config: thetier → modelmap for each provider (schema).src/mklang/— the reference interpreter (Python, multi-provider).docs/—guides/(best practices, patterns, authoring, console, install),reference/(CLI, stdlib, cheatsheet, architecture),demos.md, and the ADR index; plusROADMAP.md.examples/— runnable machines:triage.mk— branching FSM + realsearch_kb/send_replytools.research.mk— looping FSM (iterative Q&A, training knowledge).research_web.mk— research loop withtool: search(host-bound).research_compress.mk— same + explicit notes compression.news_search.mk— topic →tool: search→ news brief (today+ recency).expense_approval.mk— divergent terminals +fail.self_consistency.mk— fan-outsample+ reducer.map_reduce.mk+summarize_doc.mk—over+call.react.mk— reason/act/observe loop withaccumulate.hook_gates.mk— deterministic code-hook gates (exact policy).
Runtime configuration
The .mk picks a tier; a host-side config picks the model. This is the
whole of "make it multi-provider":
active: deepseek # deepseek | anthropic | openai | google | openrouter | xai | mistral | local
providers:
deepseek:
base_url: https://api.deepseek.com
tiers:
{
fast: deepseek-chat,
balanced: deepseek-chat,
reasoning: deepseek-reasoner,
}
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 (the path we live-test against). Flip
active: anthropic (or openai / local / …) and every example runs unchanged.
Blocks ship for Anthropic, OpenAI, Google, DeepSeek, OpenRouter, xAI (Grok),
Mistral, and local (Ollama/vLLM) — every non-Anthropic one is OpenAI-compatible,
so a single adapter serves them all. OpenRouter is a meta-provider: its
vendor/model ids let each tier target a different vendor through one endpoint.
Per-tier params (Anthropic adaptive-thinking + effort, OpenAI/xAI
reasoning_effort, …) live under params. Full map:
config/runtime.example.yaml.
Reasoning architectures
Every modern reasoning/agentic pattern maps onto the core (states + gates + prose +
tiers + the optional faces). Full skeletons in SPEC.md §10; operating
guidance in docs/guides/patterns.md.
Eight of these ship as ready, general-purpose std_* machines — parameterized
by context, callable from your machines (call: std_refine), runnable by name:
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.
| Architecture | mklang constructs |
|---|---|
| Chain-of-Thought | reason: true |
| ReAct | think → tool state (host callable) → observation accumulated |
| Reflexion / self-refine | produce → self-judge gate → repair |
| Self-consistency | sample: N → reducer state (majority) |
| Tree-of-Thought | sample: k → score/select reducer → loop (depth via budget) |
| Plan-and-Execute | planner parse: list (0.3) → over: {{steps}} → reducer |
| Debate / ensemble | over: {{personas}} → synthesizer |
| Map-Reduce | over: {{chunks}} → reducer |
| Router-of-experts | classify → call specialists |
| Speculative cascade | tier: fast draft → escalate → tier: reasoning |
| Exact policy checks | gate hook: host (ctx, output) -> bool (no LLM) |
Install
pipx install 'mklang[console,mcp]' # or: pip install 'mklang[console,mcp]'
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 console
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 .mk 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.
Quickstart (from a checkout)
Working on mklang itself, or preferring the repo? Clone it and drive everything through uv — no install step needed:
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.mk
uv run mklang lint examples/self_consistency.mk # + static analysis
uv run mklang run examples/self_consistency.mk \
--set question.text="What is the capital of Australia?"
# default provider is deepseek; override with --provider anthropic|openai|…
# pause on budget, resume later (exit code 3 = suspended):
uv run mklang run examples/self_consistency.mk --max-tokens 300 --checkpoint ck.json
uv run mklang resume ck.json --max-tokens 5000
# human-in-the-loop: escalate gates suspend; resume with the human decision:
uv run mklang run examples/expense_approval.mk --checkpoint ck.json --hitl
uv run mklang resume ck.json --set human.reply="approved, cost center 42"
For an installed package outside this repository, follow the Getting started guide; host layout details are in the installation guide. Every command, flag, and exit code: CLI reference.
The .mk picks tiers; config/runtime.example.yaml maps them to models (active: deepseek by default); the key comes from .env. Same machine, any provider.
Test your machine without API keys
mklang test runs your machine against a script of named scenarios with a
scripted LLM (produce texts, judge picks) and scripted tools/hooks — fully
deterministic, no provider or key. It pins the paths you care about before you
spend a token on a live run.
uv run mklang test examples/triage.mk --script examples/triage.test.yaml
# PASS happy-path
# PASS kb-empty-escalates
Each scenario declares a scripted llm:/tools:/hooks: and an expect:
(status, error, result, at, trace skeleton, context keys) — the same case
format the conformance suite uses. A mismatch prints a
minimal diff (the first differing key, expected vs actual) and exits 1. See
examples/triage.test.yaml.
MCP server (agentic hosts)
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-mcp --config /abs/path/to/runtime.yaml
The server exposes commissioning tools (run / resume), discovery
(list_machines / describe_machine), and check (ADR 0011 + 0013). run
accepts inline .mk source or a path, with inputs merged into the context;
resume takes an opaque handle or checkpoint file (e.g.
{"human.reply": "…"} for HITL). Live engine events stream as mklang.event
logging notifications (ADR 0019). In-memory sessions hold suspensions unless
you pass checkpoint_path. Provider keys resolve server-side from the
environment, never over the wire.
Console (interactive)
mklang console (extra mklang[console]) is the agent-first front door: type
what you want, the console's agent authors or picks a machine, commissions it,
and streams the run state-by-state — escalations and tool consent come back to
you inline. The agent itself is a machine
(agent.mk) — read it, lint it, swap it
with --agent your_brain.mk (ADR 0015). Agent replies render as Markdown; the
brain declares host clocks today / now for wall-clock questions. Details:
docs/guides/console.md.
pip install 'mklang[console]'
mklang console
Status
Language v0.3 / package 0.10.0 — 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; conformance suite;
machine stdlib (std_*); MCP host; console TUI (M1–M3); 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);
best practices. Gate judging follows the state tier
by default.
- Live: DeepSeek (default) and OpenAI green in the latest (0.9.3) release matrix,
including gate-divergence agreement 1.0 on the synthetic
harness — see
docs/experiments/gate-divergence.md. Anthropic unit-tested; live may be billing-blocked. - 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.
- Open / later: Anthropic live when the account has credit;
on_truncate=continuestitching;std_research/ language-level context zones (ROADMAP). - Roadmap and full release notes:
ROADMAP.md,CHANGELOG.md.
License
Apache-2.0. Contributions welcome — see
CONTRIBUTING.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mklang-0.10.0.tar.gz.
File metadata
- Download URL: mklang-0.10.0.tar.gz
- Upload date:
- Size: 333.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
40f8c99bd739442821022d727ee4c0b8d88649c726806b3b952a803ff186e341
|
|
| MD5 |
5424af2549979413664aad889158d330
|
|
| BLAKE2b-256 |
62a19f37999d50b807cc80b5b075841ac865369f2e5a5c90b120aeef64302afc
|
Provenance
The following attestation bundles were made for mklang-0.10.0.tar.gz:
Publisher:
release.yml on gianlucamazza/mklang
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mklang-0.10.0.tar.gz -
Subject digest:
40f8c99bd739442821022d727ee4c0b8d88649c726806b3b952a803ff186e341 - Sigstore transparency entry: 2194240969
- Sigstore integration time:
-
Permalink:
gianlucamazza/mklang@47ac14945fdf8015a27aca5e0973597ef42d8364 -
Branch / Tag:
refs/tags/v0.10.0 - Owner: https://github.com/gianlucamazza
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@47ac14945fdf8015a27aca5e0973597ef42d8364 -
Trigger Event:
release
-
Statement type:
File details
Details for the file mklang-0.10.0-py3-none-any.whl.
File metadata
- Download URL: mklang-0.10.0-py3-none-any.whl
- Upload date:
- Size: 131.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c3df32d1277ac84398238b01e7e5089fa89cad2017b992cad57fbfc6c6194553
|
|
| MD5 |
3362363bb0fb35b00af160f9c4116840
|
|
| BLAKE2b-256 |
dae5b630df9a8c48325295e4b01344120785a3e9306c34fc47f1617a2a7d6464
|
Provenance
The following attestation bundles were made for mklang-0.10.0-py3-none-any.whl:
Publisher:
release.yml on gianlucamazza/mklang
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mklang-0.10.0-py3-none-any.whl -
Subject digest:
c3df32d1277ac84398238b01e7e5089fa89cad2017b992cad57fbfc6c6194553 - Sigstore transparency entry: 2194240991
- Sigstore integration time:
-
Permalink:
gianlucamazza/mklang@47ac14945fdf8015a27aca5e0973597ef42d8364 -
Branch / Tag:
refs/tags/v0.10.0 - Owner: https://github.com/gianlucamazza
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@47ac14945fdf8015a27aca5e0973597ef42d8364 -
Trigger Event:
release
-
Statement type: