First of three iterations of the standalone loop/recur milestone
(spec 97c1ed1). loop/recur become real parseable / printable /
round-trippable / hash-stable strictly-additive AST nodes across
every no-wildcard exhaustive Term match in all six crates, plus
parse/print, prose arms, DESIGN.md + form_a.md schema blocks,
drift/coverage/hash anchors, and the spec's worked sum_to program
as a green round-trip fixture. NO typecheck semantics and NO real
codegen this iter by design: synth and lower_term are stubbed
CheckError::Internal / CodegenError::Internal exactly as mut.1
(real typecheck = iter 2, real loop-header/phi/back-edge = iter 3).
Two binding Boss design calls recorded in the plan header and the
journal: (1) verify_tail_positions "byte-unchanged" = its tail-app
verification role is unchanged, not its source — it is an
exhaustive no-wildcard match so two descent-only arms are
mandatory; acceptance evidence is the tail-app non-regression test.
(2) codegen is in iter-1 scope as stubs/pass-throughs because the
workspace must compile for cargo test --workspace.
Three plan-pseudo-vs-reality substitutions (serde Type::Con
literal, bare Int -> (con Int), unqualified LoopBinder) and one
recon-undercount integration-test site, all intent-preserving and
journalled. Boss forward-fix folded in: the committed specs
themselves wrote bare Int in the loop-binder snippets (parses as
Type::Var) — corrected the three headline snippets to (con Int) for
doc-honesty (no design/schema change).
cargo test --workspace 600 -> 608 / 0 red (Boss-reran
independently); iter13a + new loop_recur hash pins green.
9.7 KiB
LLM-Surface Discipline — Design-Principles Spec
Date: 2026-05-17
Status: Principles distillation — the meta-principle is ratified
orchestrator guidance; the candidate decisions in §6 are NOT
ratified and each still requires the normal brainstorm +
Feature-acceptance gate before it touches DESIGN.md or is
implemented.
Authors: Brummel (orchestrator) + Claude
What this spec is
A distillation of one design session that walked from "what are
effects" down through input, world-passing, statefulness,
closures, recursion, and content-addressing. It is not a
milestone spec: no feature is being proposed for implementation
here. It records (a) one meta-principle that recurred five times
and is now treated as standing orchestrator guidance, (b) a
separability analysis that re-aims the project's cost/benefit
scrutiny, and (c) a list of open forks with their epistemic
status so a later brainstorm can pick them up without
re-deriving the reasoning.
What this spec is NOT
It does not ratify loop/recur, closure-private state, the
removal of mut, argv-into-main, or any change to the cycle
/ content-addressing commitments. Those are §6 candidates. It
does not relitigate Decision 10. It carries no implementation
plan.
1. The meta-principle (ratified guidance)
The LLM author writes intent in a disciplined surface form. The raw, mechanical, invariant-threatening transformation stays in the compiler / IR, where the author never hand-manages it. Exposing the raw mechanism to the author is the consistently wrong move for this audience.
This is the operational reading of DESIGN.md's Feature-acceptance criterion and the "machine readability over human ergonomics" goal, specialised to control/state/effect constructs. It is not "hide complexity"; it is "the author asserts what, the compiler bears the mechanical invariant, and a violated invariant surfaces as a compile error rather than as a silent deep-input failure."
2. Evidence — the five instances
Each row: the disciplined surface AILang keeps (or should keep) vs. the raw mechanism it must NOT push onto the author.
- Effects. Surface: an effect label on the signature, the
checker reconciles it. Raw: the author hand-threads a
Worldtoken through every call (world-passing). — World-passing is sound under Decision 10's uniqueness machinery, but it relocates the bookkeeping onto the author. Effect label kept. - Stateful closures. Surface: a closure that simply is a
stateful value, its cell born in its own env. Raw: the author
manages capture + escape + the
mut→move transport. Closure- private state needs no cycles, no weak refs, no linearity-pass extension; it is the simplest of the three options examined. - Move-capture. Surface: state declared where it is used.
Raw: a
move-list, a linearity-pass scope expansion, use-after-move diagnostics, per-var-vs-per-block residence. Move-capture exists only to transport state from an enclosing owner to a closure; if the state has no reason to exist anywhere but the closure, the transport problem dissolves and so does the machinery. - Iteration / recursion. Surface: recursion (LLM-natural for
structural descent) and a single iteration construct. Raw:
the author hand-compiles a naturally-recursive algorithm into
an explicit-stack worklist loop. Stack-safety of non-tail
recursion is the compiler's job (cf. the existing
(drop-iterative)escape hatch for the drop cascade), not an author obligation discharged by banning recursion. - Jump. Surface:
loop/recur— re-iteration is the only thingrecurcan mean, the jump is structural. Raw: either the author-assertedtailflag ontail-app(a forgotten flag silently degrades to a stack-growing call: correct on small inputs, overflow on deep ones — a hallucination-shaped failure), or a raw labeledgoto(kills local reasoning, forces mutable loop state, breaks the expression model and the structured-AST hash).
Five-for-five, the right answer was "disciplined surface, mechanism stays in the compiler." This is the through-line.
3. The robustness corollary
For this audience an invariant guaranteed by construction /
structurally strictly dominates one asserted by the author
via a flag or annotation. Rationale: the failure mode of a
forgotten assertion is silent, input-size-dependent, and
indistinguishable from correct code in a small test — exactly
the hallucination-shaped failure the language exists to prevent.
Canonical anti-example: the tail-app tail: true flag.
Canonical positive: recur, which has no non-tail spelling.
4. The separability analysis (re-aims cost scrutiny)
DESIGN.md bundles three logically distinct commitments as if
they were one. They are separable:
- Content-address definitions. Needs only a canonical serialisation + a hash of a finite definition AST. Cheap. Delivers the audience-specific wins (verify a symbol without spending context; semantic def-diff; trust a signature without transitive reading). Worth it. Keep.
- Total equational purity of every runtime value. The commitment that forbids the LLM-natural stateful closure. Expensive.
- RC without a cycle collector. Requires acyclic runtime values. Expensive.
The cost this session repeatedly hit belongs to (2)+(3), not to (1). AILang already content-addresses only structure / definitions, never live runtime values, so (1) does not require the no-cycle memory model — (2) and (3) do. The productive question for any future brainstorm is therefore not "is content-addressing worth it" (it is, and it is cheap) but "must value-purity be total, or does the weaker line acyclic + unaliased suffice?" Closure-private state sits exactly on that weaker line: not equationally pure, but acyclic and unaliased — it keeps (1) and (3) intact while unblocking the construct the LLM reaches for.
5. Concrete surface anchors
These pin the shape the principles imply. They are illustrative, not ratified syntax.
Single iteration construct (loop/recur as pure tail-recursion
sugar; loop state is rebound parameters, not mutation):
(fn sum_to
(type (fn-type (params (con Int)) (ret (con Int))))
(params n)
(body
(loop (acc (con Int) 0) (i (con Int) 1)
(if (app > i n)
acc
(recur (app + acc i) (app + i 1))))))
Closure-private state (state born in the closure; no enclosing
mut, no move, no transport):
(fn make_counter
(type (fn-type (params) (ret (fn-type (params) (ret (con Int))))))
(params)
(body
(lam (state (var counter Int 0)) (params) (ret (con Int))
(body (seq (assign counter (app + counter 1)) counter)))))
Cheapest usefulness move (argv into main; main(args) stays
pure, the impurity stays at the trampoline C-ABI seam — no
effect, no Decision touched):
(fn main
(type (fn-type (params (con List (con Str))) (ret (con Int))))
(params args)
(body ...))
6. Open forks — candidate decisions, NOT ratified
Each needs brainstorm + the Feature-acceptance gate before it
becomes DESIGN.md or a plan. Priority order reflects usefulness
per cost.
argvintomain(highest usefulness per cost). Moves the language out of the closed-program vacuum. Touches no Decision;main(args)is a pure function, impurity confined to the trampoline. One real implementation cost: the trampoline must build an RC-correctList<Str>(Str slabs + cons cells with valid refcount headers) — the in-the-small form of the marshalling-into-RC-heap problem. Bounded, local, no language-semantics change.loop/recuras the single iteration surface. Re-attempt of the reverted Iteration-discipline centrepiece, but standalone — de-bundled from the structural-guardedness checker and theDivergeeffect that entangled and sank the original milestone. Honest cost to spec explicitly: retiringtail-appwholesale loses general proper-tail-calls (mutual recursion, tail-call into a different fn) with a stack guarantee. State the cost in the spec; do not retire it silently.- Closure-private mutable state, possibly replacing
mut. Sealed scalarmutwithout loop and without capture is largely redundant withlet/if/match; its only on-record justification is the narrow ergonomic mut-local fieldtest finding. Removing a shipped, fieldtested feature requires the project's own empirical gate: re-author the mut-local fieldtest tasks with closure-private state + plainlet; if the reported hallucination friction does not return,mutis scaffolding and can fall, and the move-capture milestone is then unnecessary (it solved a transport problem that only existed becausemutbirths state in the wrong place). - Cycles / content-addressing (deepest; deliberately not opened). Allowing cycles to get "real" closures negates Decision 10 and transitively Decision 1/2. Per §4 it is the wrong target anyway — the cheap, high-value commitment (content-address definitions) is not the expensive one. Named here as the deepest fork so it is not stumbled into; a near-term milestone must not open it.
Cross-references
docs/DESIGN.md— Decision 1/2 (source = data, content- addressed), Decision 3 (pure core + effects), Decision 8 (verified tail calls), Decision 9/10 (RC + uniqueness, no collector), Feature-acceptance criterion.docs/specs/2026-05-15-mut-local.md— the sealed-mut milestone whose redundancy §6.3 questions.docs/specs/2026-05-16-iteration-discipline-revert.md— why the bundled loop/recur milestone was backed out; §6.2 is the de-bundled re-attempt.