metis-lang
The metis-lang mark: the organization's avatar

metis-lang is named for Metis: it equips small models, so a device need not be swallowed by the largest models to behave well.

In the myth, Zeus swallowed Metis while she carried their daughter, and Athena later sprang from his head, fully armed (Hesiod, Theogony 886–900, 924–926; Apollodorus, Library 1.3.6). In some tellings, Metis made that armour inside him.

Behavior programs that know how likely they are.

metis-lang is a small language, LLP, in which a program is a catalog of typed, weighted rules — and denotes a probability measure over its own runs. A certified runtime evaluates it exactly in milliseconds; a human-gated loop grows it; a small local model speaks for it.

A surprise closes the loop The world reports a visitor and the plant is moist again, which no run of the program explains; the likelihood drops to zero; a surprise record goes to the loop; a person approves a proposed rule as a diff; the catalog gains extensions/visitor.llp; the probability refills; the device says: A visitor has watered me; my soil is moist again. 1 · the world tick 1 tick 2 tick 3 + visitor 2 · the program dry_out : moist * sun -o dry water : dry * cmd_water -o moist … check_moist : cmd_check * $moist … visitor_waters : dry * visitor -o moist P(run) 3 · likelihood 0 — a surprise, recorded 4 · the loop, as a gate surprise record moist again: no path explains it +++ extensions/visitor.llp + %% A visitor has watered me; my soil is moist again. + visitor_waters [dyn] : dry * visitor -o moist. approve? [y/n] y 5 · the catalog grows → 6 · the device speaks extensions/visitor.llp served; P(run) refills next tick “A visitor has watered me; my soil is moist again.” the plant, from the rule's %% line
world → program → surprise → gated change → catalog → the device's words.

This is the purpose in one loop: the device's behaviour is a file people can read and approve, and it improves without retraining a model. (Rule names from domains/plant/plant.llp; the new rule and its words from domains/plant/extensions/visitor.llp, metis-catalog.)

Three layers, three papers

Layer 1 — the language

LLP: typed, staged, weighted linear-logic rules, read three ways at once — as proofs, as runs, as a measure.

The OCaml compiler metisc, the Python referee metispy, a header-only C++ runtime; exact parity at 10-9 across 200+ gates; a growing Lean 4 mechanization of the metatheory.

Paper: Behavior Programs as Measures over Proofs (draft 0.3, 2026-09-30)
Layer 2 — the catalog and the loop

A program becomes a package: one directory, one manifest, one loader, built and run by both toolchains in CI.

An agentic loop turns prose or a seed program into a package, exercises it against a simulated world, and proposes extensions — every write a diff a person approves. 29 packages today: story worlds, puzzles, planning, device domains, eight IPPC/RDDL worlds against pyRDDLGym.

Paper: Behavior Catalogs in Practice (draft 0.1, 2026-10-19)
Layer 3 — the agent

A small local model (Gemma 4 E2B, ~2 GB) does not own the device's state, procedure or safety; the runtime does.

The model maps an utterance to one typed intent, narrates, proposes, asks. Two agents share one kernel; the device gets smarter by catalog changes, never by retraining. A Python dev stack and a Go embedded runtime, at parity on recorded days.

Paper: Small Models, Certified Behavior (draft 0.1, 2026-10-19)

A fragment of formalization

catalog plant 1.
provenance text "a potted plant that dries out: the person waters it or asks how it is" "2026-10-15".
layers (dyn).
stages (main).
…
pred moist : plant.
pred dry : plant.
…
pred visitor : plant.        % the world's report: someone came by (the host, or the person's World tab)

stage main {
  %% The sun has been on me for a while; my soil dries out.
  dry_out [dyn] : moist * sun -o dry.
  %% When I am dry and you water me, my soil is moist again.
  water [dyn] : dry * cmd_water -o moist * done(water).
  …
}

A rule is a linear-logic clause with a weight; %% lines are what the agent says about it. From metis-catalog/domains/plant/plant.llp, verbatim; … marks lines left out.

catalog plant 1. — the header: a catalog has a name and a version; the loader lists it, CI builds it.
pred moist : plant. — a typed predicate: atoms are resources in a multiset, counted, not just true or false.
moist * sun -o dry — the rule: a linear implication. What is on the left is consumed, what is on the right is produced; $ marks a premise only read. A weight on a rule, or a weight port bound by the host, prices its choice.

The same file is what metisc compiles, what the REPL runs, what CI replays against a committed trace, and what the agent narrates from.

#init moist 1
#init sun 1
#init cmd_water 1
#init cmd_check 1
#steps 6
#seeds 3
#query done(water)
#query checked(moist)
#query checked(dry)

A case: the start, the horizon, the seeds, the questions. From goldens/cases/plant.llp, the plant's CI case, which includes the program above unchanged.

case plant steps=6 seeds=3
I moist 1
I sun 1
I cmd_water 1
I cmd_check 1
T 1 dry_out water check_moist
F 1 (done) checked(moist):1 done(water):1 moist:1
T 2 dry_out water check_moist
F 2 (done) checked(moist):1 done(water):1 moist:1
T 3 check_moist dry_out water
F 3 (done) checked(moist):1 done(water):1 moist:1
Q done(water) 3/3
Q checked(moist) 3/3
Q checked(dry) 0/3

Its trace, as CI commits and replays it: the start (I), the rules fired in order (T), the final state (F), the queries answered (Q). From goldens/traces/plant.case.txt.

The loop, as a gate

Every change to a catalog is a diff a person reads. Six story programs were ported this way for 0.06 USD, each program byte-equal to its seed but one, whose header comment had one line changed by hand (the loop could not make that edit). The diff above is the loop SDK's own dry run of plant's extensions/visitor.llp (python -m metis_loop_sdk.catalog apply … --dry-run); the proposal and approval lines are the gate's shape in the console.

A dialog with the agent

sit
intent {"verb": "LOCAL_ACTION", "args": {"action": "SIT_DOWN"}} → runtime Accepted.
I sit down.
go to the kitchen
intent {"verb": "NAVIGATE", "args": {"goal": {"type": "REGION", "description": "kitchen"}}} → runtime Accepted.
I can't go to the kitchen: the way to it is closed.
stand up
intent {"verb": "LOCAL_ACTION", "args": {"action": "STAND_UP"}} → runtime Accepted.
I stand up.
walk to april tag three
intent {"verb": "NAVIGATE", "args": {"goal": {"type": "TAG", "description": "tag"}, "tag_id": 3}} → runtime Accepted.
I set off for tag 3.
I have arrived at tag 3.
Nina, the dog of the catalog's dog package.

Simulated from recorded turns: the utterances are rows of domains/dog/intent/dog-corpus.tsv; the intent JSON and every reply are the recorded day metis-agent/go/internal/dagent/testdata/days/dog.out.jsonl, the fixture the Python and Go agents are held equal on; the dog's words are dog.llp's %% lines. The model's part is the first line of each pair, one typed intent; the rest is the runtime's. Its name is the persona of domains/dog/intent.yaml.

Papers

Behavior Programs as Measures over Proofs
the language — draft 0.3, 2026-09-30
PDF  PDF · LaTeX · repo
Behavior Catalogs in Practice
the catalog and the loop — draft 0.1, 2026-10-19
PDF  PDF · LaTeX · repo
Small Models, Certified Behavior
the agent — draft 0.1, 2026-10-19
PDF  PDF · LaTeX · repo

Get started

# metis-lang (README, "Using the compiler")
src/metisc --run corpus/wire_ports.llp     # replay a file's embedded
                                           # '#'-directive case (spec §8)
src/metisc --wire corpus/catalog/plant/plant.llp \
    --extension corpus/catalog/plant/extensions/visitor.llp
# metis-catalog (CATALOG.md)
python -m metis_catalog list [--domains] [--json]    # what both consoles list
# metis-loop (README)
cd DIR && metis-loop           # the human loop's console over DIR (./run.sh: the same, in the background)
METIS_CATALOG=DIR metis-loop   # the same, started elsewhere
# metis-agentic-loop (README)
./demo.sh                                    # mkdir ~/lamp-demo-<date>-<time>, the lamp's world in it, loop.sh there; or ./demo.sh DIR

Each line as its README writes it; the repos say what to install first.

Q&A

Three questions the project's architect answered for its author, kept as written.

Q1. What it buys us, practically

Ordinary programs do what they say and nothing else. If you want to know how likely a program is to reach some state, you have to build a second thing beside it: a model, a simulator, a statistical estimate from many runs. The program and the model drift apart the moment one of them is edited, and neither can tell you that the other is wrong.

The idea here is to refuse the split. A program is written as a catalog of rules over a state that is a bag of resources: each rule says what it consumes and what it produces, and carries a weight. Whenever several rules could fire, the weights say how likely each is. That single addition changes what the text is. The same rules, read one way, are an executable: pick a rule, fire it, repeat. Read another way, they are a model: every possible sequence of firings is a run, and the weights give every run a probability. There is no second artifact to keep in sync, because the model is the program.

What that buys, concretely:

  • A run can be priced. Any trajectory, your own or one the world hands you, has a likelihood under the program. So "was that expected?" is a number, not a feeling.
  • Surprise becomes a defined event. If the world does something the program gives zero or near-zero probability to, you know it, at the moment it happens, and you know which rule or which missing rule is implicated. Debugging a model of the world turns into reading a diff.
  • Consistency is checkable. Because every rule is a typed resource transaction, a wrong rule often fails before it runs: it consumes what cannot exist, or produces what nothing can consume. Many modeling errors become type errors.
  • Learning is local. The structure of the world is written by a person; only the weights are fitted from data. When the fit is bad, you can see which rule's weight is off, instead of staring at an opaque loss.
  • Exactness where it is affordable, sampling where it is not. Because the set of runs has a known shape, you can compute likelihoods exactly for small worlds and fall back to seeded sampling for large ones, and you always know which of the two you got.
  • Behaviour stays a text. The thing that decides what a device does is a file a person can read, diff, approve and version. Improvement is a change to the file, not a retraining.

Q2. How it derives from logic and probability

Start with the logic. Classical logic is about truth: a fact, once true, stays true, and you can use it as often as you like. That is the wrong logic for the physical world, where a cup of water used is a cup of water gone. Linear logic fixes this by treating facts as resources. A rule A, B ⊸ C does not say "if A and B then C"; it says "consume one A and one B, produce one C." A state is a multiset of such resources, and running the program is finding a proof: a sequence of rule applications that transforms the starting multiset into another. Every run is literally a derivation in the logic, and the logic guarantees that derivations respect conservation: nothing is used that was not there.

Now the probability. A proof search has choice points: several rules apply, and the logic alone does not prefer any. Classical proof search resolves them by strategy; here, each choice is a random draw with the rules' weights as odds. A run is a path through a tree of choices, and its probability is the product of the choices along it. Summing over all paths, the program defines a probability distribution on its derivations. That is the whole construction: logic supplies the space of possible histories, probability supplies a measure on it.

Two consequences are worth seeing clearly. First, this is exactly the mathematics of mass-action kinetics: rules are reactions, resources are molecules, weights are rate constants. A chemist would recognize the state space immediately, and the same machinery that gives chemistry its master equation gives these programs their measure. Second, the measure is on histories, not on states. Two runs that reach the same final state by different routes are different events with different probabilities. That is more information than a state-based model holds, and it is why a trajectory the world hands you can be priced as a whole.

There is a subtler point about what counts as "the same run." Two firings that do not touch each other's resources could have happened in either order, and nothing in the world distinguishes the orders. The honest object to assign probability to is the causal shape of the run, with independent firings unordered, rather than one arbitrary serialization of it. When a program's rules are such that this quotient is well-defined, the measure descends to it: runs weigh their causal structure, not their interleavings. That is where logic earns its keep: the proof-theoretic view sees the causal structure directly, where a simulator only ever sees one interleaving at a time.

Q3. Running in the real world: is uncertainty anticipated, or merely absorbed?

The old objection to logic in the real world was that logic decides truth, and the world does not deliver truths; it delivers noisy, partial, surprising observations. A rule-based system that insists on truth either refuses to act or acts wrongly with full confidence.

This approach dodges the objection by changing what the logic is asked to do. It is not asked to decide what is true. It is asked to enumerate what is possible and to say how likely each possibility is. Uncertainty enters in two places, and both are explicit in the text. At every choice point, the weights encode the program's own uncertainty about what it will do next. At the boundary with the world, some rules consume resources the program never produces: observations that arrive from outside. Those rules carry weights too, so an observation is not a truth that overrides the model; it is an event the model had a price for. If the price was reasonable, the run continues and the likelihood updates. If the price was zero, the model was wrong in a specific place, and that place is named.

So: yes, uncertainty is anticipated, but only within the vocabulary the program has. This is the honest limit. The program cannot be surprised by something it has no word for; an observation outside its predicate set is not "unlikely," it is unrepresentable, and detecting it needs machinery outside the measure. Likewise, the weights are only as true as whoever fitted them, and a model can be smoothly, confidently wrong if its rules are the wrong rules with well-fitted rates. What the approach guarantees is not correctness but accountability: when the world and the program disagree, the disagreement is located, priced, and expressible as an edit.

Is it a solution to the logic-versus-uncertainty problem, a step, or a different form of computation? I would say all three, in different senses.

  • It solves a specific version of the problem: a deterministic executable and a probabilistic model of the same behaviour are, by construction, the same object, so they cannot disagree. Everyone who has maintained a controller and a separate simulator of it knows what that removes.
  • It is a step on the larger problem, because it fixes the vocabulary in advance. The open-world problem, finding out that your predicates are the wrong predicates, is not solved by a measure over proofs; it is moved to the loop around the program, where a person or a model proposes new rules and a gate decides whether to admit them. The measure makes that loop possible, by telling you when and where you need it, but it does not close it alone.
  • And it is a different form of computation, in that execution and inference are the same act. Running the program forward is sampling a proof; scoring a trajectory is weighing a proof; learning is adjusting the weights on proofs you have seen. A conventional program computes a result; this kind computes a result and knows how likely it was to compute it. That is a smaller thing than general intelligence and a larger thing than a planner. It is a behaviour that carries its own model of itself, which is what you need before anything can be made responsible for what it does.

That last point is the practical reason it matters for devices: a system that can say "this is what I expected, this is what happened, and here is the gap, in your own words" can be supervised, by a person or by something much less trustworthy than a person, without handing either of them the controls.