Skip to content

The Loop Closes

The Discipline ended with arc 170 still in flight and “i want to add argv to main” 277 commits deep. May 16 was supposed to be cleanup — a week of queued slices landing one after another. Instead it became the substrate’s biggest single day since the recognition cluster of chapters 36–44, and the day the substrate’s own types ran through the same primitive it uses to describe everything else.

The center of it: the strange loop closes. HolonAST — the structure minted three months ago to encode VSA expressions — got turned on the substrate’s own types. The same primitive the algebra was built on became the primitive its reflection runs through. The thing the substrate stores is the thing it uses to describe itself.

Around that recognition, arc 170 landed its bracket combinator — the concurrency surface the substrate had been missing.


The morning landed def-restricted (arc 198) — a primitive for capability-bounded bindings. The motivation was failure engineering at the binding layer: some primitives — kernel ops, raw *_join-result accessors, file-system calls — should only be callable from specific sites. A stylistic rule the docs request is a rule consumers ignore. The right shape is a primitive that refuses access from the wrong site.

(:wat::core::def-restricted my-binding
:allowed-callers [:my-namespace::caller]
value)
;; Sugar form:
(:wat::core::defn-restricted my-fn
:allowed-callers [:my-namespace::caller]
((x :i64) -> :i64)
body)

It shipped in two slices — the wat-side primitive plus defn-restricted sugar, then a Rust-side proc-macro #[restricted_to(...)] so the same discipline reaches Rust functions exposed to wat. Its first real use was the substrate’s own eval_kernel_*_join_result, and closing the arc let the ad-hoc walker rule arc 170 had introduced as an interim hack get deleted. Declarative access control on bindings, wat-side and Rust-side, by construction.

Two more failure classes fell the same day. Arc 202 added a compile-time walker for a deadlock-in-waiting: a parent that holds a spawned child’s stdin sender past the join point. The child waits for stdin to close; the parent holds the sender; nobody moves. The walker catches it before it runs. And arc 203 — sister to 198 — extended the :allowed-callers shape from bindings to struct fields, so each accessor can be locked the same way.

Three classes of bug, structurally unavailable, all minted between morning and late afternoon.

Then the recognition that anchors the day. The user opened arc 201:

structured type-AST reflection — pivot per any-defect-catastrophic

Reflection had been on the roadmap since arc 143 (define-alias, May 3) and arc 144’s uniform-reflection foundation. The pieces existed; arc 201 was the primitive that made them usable: a function can introspect its own argument types and return type as HolonAST. Five slices landed in one day — structured type-AST emission for every type the substrate carries; Bundle/children and Bundle/first to walk a type-AST; the signature-of-fn primitive; an honest rename (signature-ofsignature-of-defn); and extract-arg-types, the last piece Stone D2 had been waiting on.

The arc closed the same day. The inscription that sealed it:

Arc 057 minted HolonAST for VSA encoding. Arc 143 used it for signature reflection. Arc 201 uses it for type reflection. The substrate’s own internals run on themselves.

graph TB
    A057["arc 057 — Feb<br/>mints HolonAST<br/>(VSA storage)"]
    A143["arc 143 — May 3<br/>signature reflection"]
    A201["arc 201 — May 16<br/>type reflection"]
    A057 -->|same primitive| A143
    A143 -->|same primitive| A201
    A201 -. "storage IS reflection<br/>(loop closes)" .-> A057

One primitive, three uses across three months. Each use was the next caller demanding what the substrate had been ready to provide since the algebra closed under itself. The substrate isn’t a runtime that hides its work and then asks a separate reflection layer to describe it. Its storage primitive IS its reflection primitive.

The day’s other anchor: arc 170 — running since “i want to add argv to main” — landed Stones A through D2 in one stretch, and with them the surface its two-spawn discipline had been missing.

spawn-thread and spawn-process are the primitives; nobody wants to write multi-thread coordination directly against them. The bracket combinator is the lift. Stone A added drain-and-join helpers for clean, deadlock-free closure. Stone B hid the raw *_join-result accessors from user namespace — the structural enforcement is arc 198’s def-restricted, shipped the same day. Stones C1 and C2 minted the typed peer handles, :wat::kernel::ThreadPeer<I, O> and :wat::kernel::ProcessPeer<I, O> — the same shape across the fork boundary, client and server differing by a single type-param swap rather than a Client/Server pair. Stones D1 and D2 brought the bracket itself:

(run-threads
[worker (lambda (peer)
(loop
(case (Thread/readln peer)
(msg (Thread/println peer (process msg))))))]
coordinator)

Getting D2 to run took one more fix. The macro splice (arc 200) handled WatAST::List but not WatAST::Vector symmetrically, so a callsite using [...] bracket syntax tripped expansion. The substrate had locked brackets as its Clojure-faithful surface back in arc 168 but hadn’t taught the splice runtime to honor it; arc 200 relaxed it — Vector ↔ List symmetry at expand time, one commit, D2 unblocked. A sibling arc went the other way the same day: arc 199 proposed parametric keywords in defmacro, and the user’s verdict was REJECT — the existing surface already covered the case once the call form was honest.

Then the recognitions came in a cascade, each one deeper than the last. Mid-design, the user saw the first:

the bracket IS DI + OOP (Kay’s original framing)

The substrate hosts N worker threads, each handed a typed peer; the coordinator is a function the runtime invokes with the workers wired up. That’s dependency injection. It’s also Kay’s original OOP — the message goes to the worker, the worker’s handler responds, the type system enforces the interface at the surface. It compounded:

defn + ! + mini-TCP convergence

defn registers the handler; the ! suffix marks the side-effecting dispatch; mini-TCP (ch 76’s paired channels) is the transport underneath. Three primitives that had already shipped, composing into the bracket the arc had been pointing at since it opened. And then the deeper one:

main-fn returns T; we arrived at Erlang/OTP supervision

N workers, a supervising coordinator, typed channels between them, drain-and-join at shutdown — structurally Erlang/OTP. The user didn’t start from Erlang. He started from the substrate’s own concurrency primitives and the question “what’s the right surface?”, ran the four-question discipline (ch 80 — is the path obvious, simple, honest, good UX?), and got four yeses. Arriving at Erlang/OTP independently is the 13-convergences pattern the next post catalogs: the substrate’s design choices keep landing where the field landed decades ago, derived from the discipline rather than copied from the literature. The lattice was always actor-shaped; the bracket is where the substrate finally says so.

The counter-actor proof landed at both tiers — thread and process, same interface, different transport, same semantic. Universe-residency, in the language the next post will give it: a Counter program is universe-resident; the consumer picks whether it runs as a thread peer or a process peer; the program never knows which.

One day, ten arcs moving, roughly fifty commits and a stack of sealed inscriptions. Arc 057 minted HolonAST. Arc 143 made signatures reflectable. Arc 201 made types reflectable. Three months, one primitive, and the loop closes.

  • One primitive, storage and reflection both: HolonAST, minted to encode VSA expressions, became the structure the substrate introspects its own types and signatures through. There’s no translation tax between what the substrate stores and how it describes itself — the storage primitive is the reflection primitive.
  • Declarative capability-bounded access (def-restricted + #[restricted_to]): access control on bindings and struct fields enforced by construction, wat-side and Rust-side, so the wrong call site fails to compile rather than relying on a documented convention.
  • The bracket combinator: an actor-model surface (dependency injection + Kay’s original OOP) with typed peer channels, where Erlang/OTP supervision was arrived at independently via the four-questions discipline rather than copied from the literature.

PERSEVERARE.