Implementing an Interpreter: the osmol.py Walkthrough
This is the heart of Part IV. If you want Osmol in your language (Rust, Go, TypeScript, anything), the 375-line reference interpreter is your map, and this chapter walks it from end to end. Nothing in osmol.py is clever, and that is its virtue: it is the smallest honest implementation of the v0.1 semantics, small enough to port in a weekend and to audit in an evening.
Shape of the whole
One file, Python ≥ 3.9, standard library only. Three movements:
- Parse. A line-oriented regex parser builds twins from source.
- Model. Twins and their mesh: plain objects, no framework.
- Settle. The equilibrium engine fires flows until nothing clears a threshold.
The file's header says the rest:
There is no send.
(grep this file: the word appears only in the tombstone list.)
That grep is a real conformance property. In your port, the forbidden verbs should appear exactly once, in the tombstone set that exists to kill them:
TOMBSTONES = {"send", "notify", "broadcast", "blast",
"cc", "bcc", "forward", "reply"}
The model
A Twin is the whole of a participant's state. Compare the fields to the construct pages and you will find a one-to-one correspondence:
self.holds = {} # fact -> value
self.gaps = {} # fact -> {"by":..., "esc":...}
self.owes = [] # (fact, to, by)
self.decides = {} # fact -> {"among":[...], "by":...}
self.membrane = [] # (selector, party, cond, transform)
self.attention = {"interrupts": None, "quiet": None, "theta": {}}
self.bonds = {} # party -> trust
self.expressions = [] # (to, text)
self.ledger = []
self.lane = [] # human lane, verbatim
A Mesh is just twins plus one method: gapcount(), the sum of open gaps across all twins. That number is sacred. It is the quantity Theorem 1 proves strictly decreasing (every flow closes exactly one gap), so the initial gapcount is a hard ceiling on how many flows can ever fire. The engine measures it once, prints it, and enforces it (below).
Parsing
The parser is a table of regexes, RX, one per declaration form, applied line by line. Before anything else, each line is stripped of comments (-- to end of line) and handed to tombstone_check first, with string literals removed, because prose inside an express is payload, not a verb. A tombstone anywhere else dies immediately with the exact O-000 message. This mirrors the dissertation's rule that tombstones fire at the lexer: the fastest error the toolchain can produce.
Twins and their two sub-blocks are handled by a small state machine: seeing membrane { or attention { switches the parser into that block until the closing }; a bare } otherwise closes the twin. Everything else falls through the RX table in order (hold, seek, owe, decide, express, bond), and an unmatched line is a parse error. (assume role and stake are grammar-legal but v0.1-unparsed; see the pragmas.)
One semantic action happens at parse time: owe emits its structural status hold. owe review(atlas) to raj by fri also records hold status(atlas.review) = in-progress on the ower, because a commitment implies the counterparty's standing interest in its status. If you port nothing else faithfully, port this. The dinner mesh's third flow depends on it.
The engine, function by function
Six small functions, each one clause of the semantics:
sel_match(selector, fact): membrane selectors as patterns. A bare ident gets(*)appended,*becomes a regex wildcard, and the match must cover the whole fact. This is what makeseta(*)andstatus(atlas.*)work.audience_match(sender, party, receiver): the circles pragma.allmatches everyone;familyandteammatch any receiver the sender bondstrust high;othersmatches everyone who is not; a name matches itself.membrane_cast(sender, fact, receiver): walks the sender's rules in declaration order and returns the first match. ReturningNonemeans no rule matched, andNonemeans no flow: default-deny is a return value, not a policy option.cast_weight(tr): the M factor.exact1.0,coarse(…)0.7,category0.5,existence0.3,deny0.0, and 0.0 forNone.theta_for(receiver, sender): the receiver's bonds pick the sender's class (familyif the receiver bonds the sendertrust high, elseothers), then the receiver's ownthresholddeclarations override the defaultsfamily 0.3 / team 0.4 / others 0.8. Take note whose state is consulted here: the receiver's. Axiom III is a function signature here.pressure(...): the formula, with v0.1's constants visible. R is 1.0 (no semantic embedding yet), U is 1.3 if the gap has a deadline else 1.0, T comes from the receiver's trust table (high1.0 /medium0.6 /low0.3), C is 0.0. The load-bearing line is exact:
return R * U * T * M - C, (R, U, T, M)
apply_cast(value, tr): v0.1's textual rendering of granularity casts.exactpasses through,coarseyields~value [coarse(30m)],categoryyieldsvalue [class-level],existenceyields[exists].
settle()
The engine proper, in the order the trace prints:
Resolutions first. --resolve twin:fact=value arguments are applied before any flow: the named decide is deleted and the chosen value becomes a hold. Human judgment is an input type. The solver cannot choose a venue, not in v0.1 and not ever; that refusal is a permanent feature of the runtime contract.
The bound, announced. The engine records g0 = mesh.gapcount() and prints it with Theorem 1's promise: equilibrium in ≤ g0 flows.
The human lane delivers first, verbatim. Every express is appended to its recipient's lane and printed with its provenance label, before a single pressure calculation runs. Axiom V is not a special case inside the loop; it is a separate, earlier loop.
Then the loop. Each iteration scans all (receiver-gap, sender-hold) pairs. Note the direction: it iterates over receivers' gaps and looks for matching holdings, which is why unsolicited flow is unrepresentable rather than forbidden. For each pair it asks the membrane for a cast (skipping None and deny), computes P and the receiver's θ, and remembers the best P strictly above θ. If no candidate clears, the loop ends: equilibrium. Otherwise the best flow fires: the cast value is absorbed into the receiver's holds, the gap is popped, placement is ledger if the gap carried else escalate and silent otherwise. And then comes the confession:
assert flows <= g0, "Theorem 1 violated — impossible"
The proof guarantees this assert can never trip. It ships anyway, in every habitat, so that an implementation that betrays the semantics confesses instantly instead of drifting quietly.
The report. Equilibrium is announced against the bound; the zero counters (messages composed by humans: 0, interruptions: 0) are printed because they are the product; ledgers list what surfaced; surviving gaps are listed as open (with a note when else sync will schedule live time); unresolved decides wait as "awaiting human judgment." And when nothing remains at all: open gaps: 0 — silence is a fixpoint.
Porting checklist
A new implementation may call itself Osmol v0.1 when it can look the reference in the eye:
- Same trace text. Run
dinner.osmolwith the venue resolution; your output should match the reference byte for byte. That is the seed of the conformance suite, and differential testing againstosmol.pyis the cheapest oracle you will ever get. - Same constants. Trust weights, cast weights, default thetas, U = 1.3. All of them, exactly.
- Same pragmas. Honor every documented simplification in the v0.1 pragmas, or supersede them consciously via RFC, never silently. (One known sharp edge: the dissertation's determinism rule mandates a lexicographic (sender, receiver, fact) tie-break among equal-pressure flows; the reference resolves ties by scan order. Implementing the mandated tie-break is standing RFC-shaped work.)
- Same runtime assertion. Flows ≤ initial gapcount, asserted, in every habitat, forever.
- Then graduate. When the single file feels small, the five-stage compiler of the engineering dissertation (Ch. 2), that is lexer, parser, semantic analysis,
.odblowering, and verification hooks, is the road from interpreter to toolchain, and one engine, four habitats is the destination.
The reference will eventually be the slow implementation. It will never be the wrong one. Its retirement job is being the oracle that certifies its successors.